[譯]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)
相關文章
相關標籤/搜索