python后期绑定 - 动态地将locals放在范围内

Dus*_*etz 4 python monads functional-programming

我有一个函数m_chain,它引用了两个函数bind,unit但没有定义.我想在一些提供这些函数定义的上下文中包装此函数 - 您可以将它们视为我想动态提供实现的接口.

def m_chain(*fns):
    """what this function does is not relevant to the question"""
    def m_chain_link(chain_expr, step):
        return lambda v: bind(chain_expr(v), step)
    return reduce(m_chain_link, fns, unit)
Run Code Online (Sandbox Code Playgroud)

在Clojure中,这是用宏完成的.在python中执行此操作的一些优雅方法是什么?我考虑过:

  • polymorphism:将m_chain转换为引用的方法,self.bind并且self.unit其实现由子类提供
  • 实现with界面,以便我可以修改环境贴图,然后在完成后清理
  • 将m_chain的签名更改为接受单元并绑定为参数
  • 要求使用m_chain由装饰器包装,装饰器会做某事或其他 - 不确定这是否有意义

理想情况下,我根本不想修改m_chain,我想按原样使用定义,并且所有上述选项都需要更改定义.这有点重要,因为还有其他m_*函数引用了在运行时提供的附加函数.

我如何最好地构建这个,以便我可以很好地传递bind和unit的实现?尽管实现复杂,但m_chain的最终用法非常容易使用,这一点非常重要.

编辑:这是另一种有效的方法,这很丑陋,因为它需要m_chain被cur成一个没有args的函数.但这是一个最低限度的工作示例.

def domonad(monad, cmf):
    bind = monad['bind']; unit = monad['unit']
    return cmf()

identity_m = {
    'bind':lambda v,f:f(v),
    'unit':lambda v:v
}

maybe_m = {
    'bind':lambda v,f:f(v) if v else None,
    'unit':lambda v:v
}

>>> domonad(identity_m, lambda: m_chain(lambda x: 2*x, lambda x:2*x)(2))
8
>>> domonad(maybe_m, lambda: m_chain(lambda x: None, lambda x:2*x)(2))
None
Run Code Online (Sandbox Code Playgroud)

ste*_*eha 8

在Python中,您可以编写所需的所有代码,这些代码指的是不存在的东西; 具体而言,您可以编写引用没有绑定值的名称的代码.你可以编译该代码.唯一的问题是在运行时发生,如果名称仍然没有绑定到它们的值.

这是一个可以运行的代码示例,在Python 2和Python 3下进行了测试.

def my_func(a, b):
    return foo(a) + bar(b)

try:
    my_func(1, 2)
except NameError:
    print("didn't work") # name "foo" not bound

# bind name "foo" as a function
def foo(a):
    return a**2

# bind name "bar" as a function
def bar(b):
    return b * 3

print(my_func(1, 2))  # prints 7
Run Code Online (Sandbox Code Playgroud)

如果您不希望名称只是绑定在本地名称空间中,但您希望能够根据函数对它们进行微调,我认为Python中的最佳实践是使用命名参数.你总是可以关闭函数参数并返回一个新的函数对象,如下所示:

def my_func_factory(foo, bar):
    def my_func(a, b):
        return foo(a) + bar(b)
    return my_func

my_func0 = my_func_factory(lambda x: 2*x, lambda x:2*x)
print(my_func0(1, 2))  # prints 6
Run Code Online (Sandbox Code Playgroud)

编辑:这是你的例子,使用上述想法进行修改.

def domonad(monad, *cmf):
    def m_chain(fns, bind=monad['bind'], unit=monad['unit']):
        """what this function does is not relevant to the question"""
        def m_chain_link(chain_expr, step):
            return lambda v: bind(chain_expr(v), step)
        return reduce(m_chain_link, fns, unit)

    return m_chain(cmf)

identity_m = {
    'bind':lambda v,f:f(v),
    'unit':lambda v:v
}

maybe_m = {
    'bind':lambda v,f:f(v) if v else None,
    'unit':lambda v:v
}

print(domonad(identity_m, lambda x: 2*x, lambda x:2*x)(2)) # prints 8
print(domonad(maybe_m, lambda x: None, lambda x:2*x)(2)) # prints None
Run Code Online (Sandbox Code Playgroud)

请告诉我这对你有用.

编辑:好的,你的评论后还有一个版本.您可以m_按照此模式编写任意函数:它们检查kwargs密钥"monad".必须将其设置为命名参数; 没有办法将它作为位置参数传递,因为将*fns所有参数收集到列表中的参数.我所提供的缺省值bind()unit()在它们没有在单子所定义,或不提供单子情况; 那些可能不会做你想要的,所以用更好的东西替换它们.

def m_chain(*fns, **kwargs):
    """what this function does is not relevant to the question"""
    def bind(v, f):  # default bind if not in monad
        return f(v),
    def unit(v):  # default unit if not in monad
        return v
    if "monad" in kwargs:
        monad = kwargs["monad"]
        bind = monad.get("bind", bind)
        unit = monad.get("unit", unit)

    def m_chain_link(chain_expr, step):
        return lambda v: bind(chain_expr(v), step)
    return reduce(m_chain_link, fns, unit)

def domonad(fn, *fns, **kwargs):
    return fn(*fns, **kwargs)

identity_m = {
    'bind':lambda v,f:f(v),
    'unit':lambda v:v
}

maybe_m = {
    'bind':lambda v,f:f(v) if v else None,
    'unit':lambda v:v
}

print(domonad(m_chain, lambda x: 2*x, lambda x:2*x, monad=identity_m)(2))
print(domonad(m_chain, lambda x: None, lambda x:2*x, monad=maybe_m)(2))
Run Code Online (Sandbox Code Playgroud)