[译]python中的变量绑定

原文地址:http://alon.horev.net/blog/2013/10/20/argument-binding-in-python/ python

首发:http://www.everlose.info/notes/2013/10/26/Python%E4%B8%AD%E7%9A%84%E5%8F%98%E9%87%8F%E7%BB%91%E5%AE%9A/安全

在最近一次关于pythono中的变量绑定的争论以后,我决定从正反两方面列出一些在不一样方法中python的变量绑定状况。咱们先从可行的方法开始吧。 闭包

def add(x,y):
    return x + y

from functools import partial
add5_partial = partial(add, 5)
add5_partial(10)   # 15  

add5_lambda = lambda x: add(x, 5)
add5_lambda(10)    # 15

我对partial的抱怨

partial不是function[译注:我以为这样的术语仍是直接用英文比较准确],而且常常得不到一个function应该获得的结果。partial用纯python很容易实现,但我只能猜测,考虑到性能,它是用C来实现的。来看一些例子: app

1.Partial在methods里不能工做:

from functools import partial  

class Cell(object):
    def set_state(self, state):
        self._state = state  
    set_alive = partial(set_state, state=True)  
    set_dead = partial(set_state, state=False)

>>>Cell().set_alive()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: set_state() takes exactly 2 arguments (1 given)

这是为何?

你知道self老是在调用实例方法的时候被赋值为该实例吗?这是使用descriptors机制来是实现的。为了实现这个功能,function类型须要实现__get__方法。 python2.7

如下表示了方法调用是怎么工做的: 函数

class Person(object):
    def __init__(self, name):
        self._name = name
    def speak(self):  
        print 'My name is ', self._name  

>>> p = Person('Neo')  
>>> p.speak()   # 方法调用  
My name is Neo  

>>> # 那么函数(function)和方法(method)的区别是什么呢?  
>>> method = p.speak()  # 这个method封装了实例和函数  
>>> method  
<bound method Person.speak of <__main__.Person object at 0x109d7bb90>>  
>>> method.im_self    # 这是self隐藏的地方  
<__main__.Person object at 0x109d7bb90>>  
>>> method.im_func    # 这是function隐藏的地方  
<function Person.speak at 0x106163950>  
>>> method()          # 与method.im_func(method.im_self)结果相同  
My name is Neo  

>>> # 从function到method传递了些什么?  
>>> Person().speak()    # 触发 __getattribute__('speak')  
>>> # __getattribute__从实例的__dict__中搜索属性  
>>> # 而后__getattribute__从类的__dict__中搜索属性  
>>> # 当它找到以后,它会检查这个值(function)是否是实现了一个__get__方法  
>>> # 若是没有实现__get__,返回这个值  
>>> # 若是实现了__get__,返回__get__所返回的值,无论是什么  
>>> method = Person.speak.__get__(Person('Neo'))  
>>> method  
>>> <bound method ?.speak of <__main__.Person object at 0x7f8527ccbad0>>

2.partial不能检查:

>>> import inspect, functools  
>>> p = functools.partial(lambda x, y: x + y, 10)  
>>> inspect.getargspect(p)  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute 'getargspect'  

>>> print p.__doc__    # 没有保持被包裹的function的__doc__  
partial(func, *args, **keywords) - new function with partial application
    of the given arguments and keywords.

3.partial可以更安全,验证变量的数量和名称:

>>> from functools import partial  
>>> f = partial(lambda: None, 1, 2, 3)   # 为何在这里不检查信号?!  
>>> f()  
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: <lambda>() takes no arguments (3 given)

partial的替代品

你能够实现一个你本身的返回一个function的partial: 性能

from functools import wraps  

def partial(func, *a, **k):  
    @wraps(func)
    def new_func(*args, **kwargs):  
        return func(*(a + args), **dict(k, **kwargs))
    return new_func

备注:不要使用上面的代码,它没有保证键的变量惟一。 测试

你也可使用lambda: .net

class Cell(object):  
   def set_state(self, state):
       self._state = state  
   set_alive = lambda self: self.set_state(True)
   set_dead = lambda self: self.set_state(False)

关于lambdas,个人问题

就像个人朋友@EyalIl说的: 指针

Lambdas获取变量,partial获取值。
后者通常更有用。

这里有一个例子能够说清这个问题:

callbacks = []  
for i in xrange(5):
    callbacks.append(lambda: i)  
>>> print [callback() for callback in callbacks]
[4, 4, 4, 4, 4]

为何发生了这个?

由于python支持闭包(一个一般很好的东西):

var = 1
f = lambda: var
print f()  # 1
var = 2  
print f()  # 2
# 可是,可是python是怎么知道的?好吧,function可以hold住外部变量的一个引用  
print f.func_closure() #  (<cell at 0x101bdfb40: int object at 0x7fd3e9c106d8>,)  注:我不知道这做者是怎么得出来的,个人测试未得出这样的结果,而是抛出TypeError: 'NoneType' object is not callable的错误  

# 这些cells是什么?cell是一个指向某个外部范围某个名称的一个指针。它hold住了一个容许改变的反射,甚至是改变不可变的数据类型。  
print f.func_closure()[0].cell_contents  # 2

[注:上面这段代码我在python2.7.3和python3.3.1下测试都没获得做者所说的结果,若是有懂的望赐教.]

将不是函数(function)参数的变量绑定为函数(function)变量是一个解决方法:

callbacks = []  
for i in xrange(5):
    callbacks.append(lambda x=i:x)
>>> print [callback() for callback in callbacks]  
[0, 1, 2, 3, 4]

咱们能作到更好嘛?

我打算提一个跟JavascriptFunction.bind功能类似的一个机制。

这是我想它所起的做用(这只是一个建议,这些代码不能真正的工做):

def add(x, y):  
    return x + y

from functools import partial
add5_partial = partial(add, 5)     # 须要一次import
add5_lambda = lambda x: add(x, 5)  # 太长了  

add5_bind = add.bind(5)  # 最短的  

import inspect
>>> print inspect.getargspec(add)  
ArgSpec(args=['x', 'y'], varargs=None, keywords=None, defaults=None)  
>>> print inspect.getargspect(add5_bind)  # works with inspect
ArgSpec(args=['y'], varargs=None, keywords=None, defaults=None)
相关文章
相关标签/搜索