原文地址:http://alon.horev.net/blog/2013/10/20/argument-binding-in-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来实现的。来看一些例子:
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__
方法。
以下表示了方法调用是怎么工作的:
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:
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]
我们能做到更好嘛?
我打算提一个跟Javascript
的Function.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)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。