2011-07-11 63 views
11

請考慮Python中的策略模式示例(從示例here改編而來)。在這種情況下,替代策略是一種功能。python方法如何自動接收'self'作爲第一個參數?

class StrategyExample(object): 
    def __init__(self, strategy=None) : 
     if strategy: 
      self.execute = strategy 

    def execute(*args): 
     # I know that the first argument for a method 
     # must be 'self'. This is just for the sake of 
     # demonstration 
     print locals() 

#alternate strategy is a function 
def alt_strategy(*args): 
    print locals() 

以下是默認策略的結果。

>>> s0 = StrategyExample() 
>>> print s0 
<__main__.StrategyExample object at 0x100460d90> 
>>> s0.execute() 
{'args': (<__main__.StrategyExample object at 0x100460d90>,)} 

在上面的例子s0.execute是一種方法(未一個普通的功能),並且因此在args的第一個參數,如所預期,是self

以下是替代策略的結果。

>>> s1 = StrategyExample(alt_strategy) 
>>> s1.execute() 
{'args':()} 

在這種情況下是s1.execute一個普通的函數和作爲預期的,不接收self。因此args是空的。等一下!這怎麼發生的?

該方法和函數都以相同的方式調用。一個方法如何自動獲得self作爲第一個參數?當一種方法被普通的香草函數取代時,而不是如何得到self作爲第一個參數?

我能找到的唯一區別是當我檢查默認策略和替代策略的屬性時。

>>> print dir(s0.execute) 
['__cmp__', '__func__', '__self__', ...] 
>>> print dir(s1.execute) 
# does not have __self__ attribute 

是否__self__屬性對s0.execute(方法)上s1.execute(功能)的存在,但缺乏它在某種程度上解釋這種行爲差異?這一切如何在內部工作?

+1

你可以認爲instance.method(ARG)'作爲'InstanceClass.method的簡寫的'(實例,arg)'。 Python試圖讓事情儘可能簡單明瞭,我發現這是一種「透明」的方式來訪問實例,該實例稱爲函數 –

回答

3

您可以在「用戶定義的方法」下的python參考中閱讀完整說明here

如果您還有不明白的方法是如何工作的,一起來看看它的實現也許澄清事項:較短,更容易解釋可以在method objects蟒蛇教程的描述中找到。當引用不是數據屬性的實例屬性時,將搜索其類。如果名稱表示一個有效的類屬性,它是一個函數對象,則通過打包(指向)實例對象和在抽象對象中一起找到的函數對象來創建方法對象:這是方法對象。當使用參數列表調用方法對象時,會從實例對象和參數列表構造一個新參數列表,並使用此新參數列表調用函數對象。

基本上,會發生什麼在你的例子是這樣的:

  • 分到一個班的功能(如當你聲明的類體中的方法發生)是......的方法。
    • 當您通過該類訪問該方法時,例如。 StrategyExample.execute你會得到一個「unbound method」:它不會「知道」它屬於哪個實例,所以如果你想在實例上使用它,你需要自己提供實例作爲第一個參數,例如。 StrategyExample.execute(s0)
    • 當您通過實例訪問方法時,例如。 self.executes0.execute,你會得到一個「綁定方法」:它「知道」它「屬於」哪個對象,並且將作爲第一個參數被調用。
  • 然而,您直接分配給實例屬性的功能卻如self.execute = strategy甚至s0.execute = strategy是......只是一個普通的功能(相反的方法,它不通過類通)

爲了讓您的示例工作同在兩種情況下:

  • 要麼你把功能分爲一個「真正」的方法:你可以用types.MethodType做到這一點:

    self.execute = types.MethodType(strategy, self, StrategyExample) 
    

    (你或多或少告訴類當execute被要求對這種特定情況下,應該把strategy到綁定的方法)

  • 或者 - 如果您的策略並不真的需要訪問該實例 - 反過來將原始execute方法轉換爲靜態方法(使其成爲一個正常函數:它不會被調用與實例作爲第一個參數,所以s0.execute()會做完全一樣StrategyExample.execute()):

    @staticmethod 
    def execute(*args): 
        print locals() 
    
+0

非常明確的答案,並從Python教程太直了!表明我應該再次重新訪問該教程,以吸收我剛剛瀏覽過的較深的語言細節。非常感謝你! –

1

當你做self.execute = strategy設置屬性爲純方法:

>>> s = StrategyExample() 
>>> s.execute 
<bound method StrategyExample.execute of <__main__.StrategyExample object at 0x1dbbb50>> 
>>> s2 = StrategyExample(alt_strategy) 
>>> s2.execute 
<function alt_strategy at 0x1dc1848> 

綁定方法是調用傳遞一個實例作爲第一個參數,除了通過所有參數的函數調用對象它被稱爲。

參見:Python: Bind an Unbound Method?

+0

術語是,您有一個未綁定的方法。如果你/綁定/方法(你可以做),它會再次隱式傳遞實例作爲第一個參數。 – Arafangion

+0

感謝有關綁定未綁定方法的鏈接! –

6

的方法是用於該函數的包裝,並調用與該實例作爲第一個參數的函數。是的,它包含一個__self__屬性(在3.x之前的Python中也是im_self),用於跟蹤它所連接的實例。但是,將該屬性添加到普通函數不會使其成爲方法;你需要添加包裝器。 Here is how(儘管你可能需要使用MethodTypetypes模塊來獲取構造函數,而不是使用type(some_obj.some_method)

功能包,順便說一句,是通過該方法的__func__(或im_func)屬性訪問。

+0

非常有幫助的類比:「該方法是函數的包裝...」。並感謝鏈接! –

7

您需要將一個未綁定的方法(即使用self參數)分配給對象的類或綁定方法。

經由descriptor mechanism,你可以讓自己的綁定方法,這也是爲什麼當你指定(未綁定)功能的一類作品:

my_instance = MyClass()  
MyClass.my_method = my_method 

當調用my_instance.my_method(),查找將找不到輸入my_instance,這就是爲什麼它在稍後的時間最終會這樣做:MyClass.my_method.__get__(my_instance, MyClass) - 這是描述符協議。這將返回一個綁定到my_instance的新方法,然後在屬性後使用()運算符執行該方法。

這將在MyClass的所有實例之間共享方法,無論它們何時創建。但是,在分配該屬性之前,他們可能會「隱藏」該方法。

如果您只需要特定的對象有一個方法,只是手動創建一個綁定方法:

my_instance.my_method = my_method.__get__(my_instance, MyClass) 

有關的描述更詳細(導向),看到here

+0

有趣的是,實際上向您顯示的唯一答案是您可以製作綁定方法並嘗試解釋它,但沒有得到一個單獨的提示... – phant0m

+0

描述符訪問是一種創建我不知道的方法的方法。我一直使用實例方法構造函數完成它。 – kindall

+0

@ kindall:你的意思是像你鏈接到的文章中顯示的那樣?而不是通過'type()'獲得它,你可以導入它:'from types import MethodType' – phant0m

相關問題