2013-04-17 87 views
2

定義對於下面的代碼:類方法和變量在類方法

import os.path 

class Test: 

    @classmethod 
    def foo(cls): 
     cls.exist = os.path.exists 
     print type(cls.exist) 
     print type(os.path.exists) 

def main(): 
    Test.foo() 

我正在輸出爲:

<type 'instancemethod'> 
<type 'function'> 

我只是將功能分配到os.path.exist類變量cls.exist。 但是,當我打印這兩個變量,我得到一個作爲實例方法和其他作爲函數。 爲什麼?

回答

1

您可以指定它作爲一個類變量,但它綁定到類:

Traceback (most recent call last): 
    File "<stdin>", line 12, in <module> 
TypeError: unbound method exists() must be called with Test instance as first argument (got str instance instead) 

你需要讓exist一個staticmethod,以便它是「獨立」之類的:

cls.exist = staticmethod(os.path.exists) 

現在,兩者是相同的類型的:

<type 'function'> 
<type 'function'> 

的d實際上你可以把它叫做:

>>> Test.exist('foo') 
False 
+0

感謝攪拌器! –

+0

這是對我的答案中「直觀但不是真正的」部分的更好的解釋(也更簡短!)。而且,即使它不是真的,你可以編寫代碼,就好像它是可行的。 – abarnert

1

首先,直觀的解釋:

當你分配到一個類中的函數,它被轉換成一個不受約束的方法。

未綁定的方法是爲每個類的實例創建綁定方法的神奇事物。

綁定方法是一個神奇的東西,它有特殊的self參數綁定到它。 (這在3.x中略有不同,因爲沒有未綁定的方法;函數本身就是神奇的東西,它爲每個類的實例創建了一個綁定的方法,但是你明確地問到了2.x.)

所以:

>>> class C(object): pass 
>>> def foo(self): print(self) 
>>> C.foo = foo 
>>> c = C() 

現在,你可以打電話c.foo()self參數將與c進行自動填充。但是,如果沒有參數,則不能撥打C.foo(),如果沒有參數,則可以調用foo()

>>> c.foo() 
<__main__.C object at 0x108df7e50> 
>>> C.foo() 
TypeError: unbound method foo() must be called with C instance as first argument (got nothing instead) 
>>> foo() 
TypeError: foo() takes exactly 1 argument (0 given) 

當然沒有什麼從調用C.foo()foo()有一個明確的說法self阻止你。你通常不會這麼做,但它工作正常:

>>> C.foo(1) 
1 
>>> foo(1) 
1 

如果你看一下功能,不受約束的方法,以及綁定的方法,他們很容易分辨:

>>> foo, C.foo, c.foo 
(<function __main__.foo>, <unbound method C.foo>, <bound method C.foo of <__main__.C object at 0x108df7e50>>) 
>>> type(foo), type(C.foo), type(c.foo) 
(function, instancemethod, instancemethod) 

如果您只是想要解決方法,可以使用staticmethod。這需要一個功能,並創建一個事情是,當分配到一類,就像一個函數,而不是一個不受約束的方法:

>>> bar = staticmethod(foo) 
>>> C.bar = staticmethod(foo) 
>>> c = C() 
>>> bar, C.bar, c.bar 
(<staticmethod at 0x109e990f8>, <function __main__.foo>, <function __main__.foo>) 

而現在,你可以調用它的類或實例作爲一個經常性功能,沒有魔法self方法:

>>> C.bar(1) 
1 
>>> c.bar(1) 
1 
>>> bar(1) 
TypeError: 'staticmethod' object is not callable 

但是,直觀的解釋是不是真的。它總是會引導你找到正確的答案,但事實並非如此。

要真正理解這一點,您需要了解descriptors的工作方式。

當您將一個函數作爲屬性分配給某個類時,不會發生任何不可思議的事情。什麼都存儲在類字典只是普通的老功能:

>>> C.__dict__['foo'] 
<function __main__.foo> 

而且當你創建一個實例,它不把任何東西的情況下字典,因爲屬性查找自動退回到類詞典:

>>> c.__dict__['foo'] 
KeyError: 'foo' 

真正的魔法發生在查找時間。每當你查找任何屬性(方法或其他)時,解釋器不會從字典中返回對象,它會調用該對象的方法,傳遞實例(如果在類對象上調用,則調用None)和類。

正常功能有__get__方法需要產生結合或未結合的方法:

>>> C.__dict__['foo'].__get__(None, C) 
<unbound method C.foo> 
>>> C.__dict__['foo'].__get__(None, C) == C.foo 
True 
>>> C.__dict__['foo'].__get__(c, C) 
<bound method C.foo of <__main__.C object at 0x108df7e50>> 
>>> C.__dict__['foo'].__get__(c, C) == c.foo 
True 

您可以手動做同樣的事情,並得到相同的結果:

>>> types.MethodType(foo, None, C) == C.foo 
True 
>>> types.MethodType(foo, c, C) == c.foo 
True 

,現在你可以大概猜到staticmethod看起來像什麼:它有一個__get__方法,它只是返回原始函數,不管你傳遞了什麼。

處理可設置的數據屬性等有一些複雜性,但實際上,這幾乎是整個交易,如果你理解了這麼多,你幾乎可以理解Python中的所有魔法。您可以自行構建staticmethod,classmethodproperty,由您自己構建類別等。

+0

謝謝abarnert! –

+0

+1:令人難以置信的解釋。 – Blender