原文地址: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
不是function[譯註:我以爲這樣的術語仍是直接用英文比較準確],而且常常得不到一個function應該獲得的結果。partial
用純python很容易實現,但我只能猜測,考慮到性能,它是用C來實現的。來看一些例子: app
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>>
>>> 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.
>>> 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)
你能夠實現一個你本身的返回一個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)
就像個人朋友@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)