Reproduce from
- python描述符(descriptor)、屬性(Property)、函數(類)裝飾器(decorator )原理實例詳解
- 如何正確地使用Python的屬性和描述符
- Python 描述符(Descriptor) 附實例
- Python 描述符簡介
- Descriptor HowTo Guide
- python對屬性的搜索優先級
- python對屬性的搜索優先級
- How Does Attribute Access Work?
- How does a classmethod object work?
- 利用描述符原理完成自定制@property、@classmethod、@staticmethod
- <Python Descriptors: Understanding and Using the Descriptor Protocol>
在 Python 眾多原生特性中,描述符可能是最好被自定義的特性之一,但它在底層實現的方法和屬性卻無時不刻被使用著,它優雅的實現方式體現出 Python 簡潔之美。
簡介
Python 描述符是一種創建對象屬性的方法。描述符具有諸多優點,諸如:保護屬性不受修改,屬性類型檢查,和自動更新某個依賴屬性的值等。
定義
- 一個描述符是一個有綁定屬性的對象屬性,它的訪問控制會被描述器協議方法重寫。(In general, a descriptor is an object attribute with “binding behavior”, one whose attribute access has been overridden by methods in the descriptor protocol. )
- 描述符協議是以下方法:
__get__()
,__set__()
,和__delete__()
。如果一個類定義了任意這些方法,那么這個類就是描述符。(Those methods are__get__()
,__set__()
, and__delete__()
. If any of those methods are defined for an object, it is said to be a descriptor.) - 屬性訪問的默認行為是從對象的字典(object's dictionary)中獲取,獲取(get),設置(set),或刪除(delete)屬性。例如,當訪問
a.x
時,有一個查找鏈,開始是a.__dict__['x']
,接著type(a).__dict__['x']
,之后繼續在基類中尋找,不包括元類 metaclasses。如果查找的目標屬性是一個定義了描述符的類對象,則 Python 會用描述符的方法覆蓋默認行為。這種情況發生在在優先級鏈中發生的時機取決于描述符的哪些方法被定義。(If the looked-up value is an object defining one of the descriptor methods, then Python may override the default behavior and invoke the descriptor method instead. Where this occurs in the precedence chain depends on which descriptor methods were defined.) - 描述符是一種功能強大的通用協議。它是
@properties
,methods
(方法),@staticmethod
(靜態方法),@classmethod
(類方法),和super()
背后的機制。(Descriptors are a powerful, general purpose protocol. They are the mechanism behind properties, methods, static methods, class methods, and super().)
描述符協議
- __get__(self, instance, owner)
def __get__(self, instance, owner): ''' :param self: 描述符對象本身 :param instance: 使用描述符的對象的實例 :param owner: 使用描述符的對象擁有者 '''
- __set__(self, instance, value)
def __set__(self, instance, value): ''' :param value: 對描述符的賦值 '''
- __delete__(self, instance)
實例
class Descriptor:
def __init__(self, name):
self._name = name
def __get__(self, instance, owner):
return self._name
def __set__(self, instance, value):
self._name = value
def __delete__(self, instance):
del self._name
class Person:
age = Descriptor('age')
為什么需要描述符
Python 是一個動態類型解釋性語言,不像 C / Java 等靜態編譯型語言,數據類型在編譯時便可以進行驗證,而 Python 中必須添加額外的類型檢查邏輯代碼才能做到這一點。
假設我們有這樣一個類:
class Movie:
def __init__( self,
title,
description,
score,
ticket):
self.title = title
self.description = description
self.score = score
self.ticket = ticket
這里,電影的分數不能是負分,這個是錯誤行為,希望 Movie 類可以預防這個問題。
class Movie:
def __init__(self,
title,
description,
score,
ticket):
self.title = title
self.description = description
self.ticket = ticket
if score < 0:
raise ValueError("Negative value not allowed:{}".format(score))
self.score = score
這樣修改可以防止初始化對象的時候給電影打負分,但是如果對于已經存在的類實例就無能為力了。如果有人試著運行 movie.score = -1
,那么誰也沒法阻止。
Getter & Setter
實現對于 score
的 getter()
和 setter()
方法來防止 score 小于 0。
class Movie:
def __init__(self,
title,
description,
score,
ticket):
self.title = title
self.description = description
self.ticket = ticket
if score < 0:
raise ValueError("Negative value not allowed:{}".format(score))
self._score = score
def set_score(self, score):
if score >= 0:
self._score = score
else:
self._score = 0
def get_score(self):
return self._score
但是,大量的 getter() 和 setter() 會導致類型定義的臃腫和邏輯混亂。從 OOP 思想來看,只有屬性自己最清楚自己的類型,而不是他所在的類,因此如果能將類型檢查的邏輯根植于屬性內部,那么就可以解決這個問題 -- @property
。
Property
注意,這里 self._score
才是對象的真正的屬性,而 type(Movie.score)
是 Property
。每次調用 object.score
實際就是在調用 Property
相應的 getter()
,setter()
,或是 deleter()
。如果在 setter()
中也寫的是 self.score = score
,則是自己調用自己,陷入不斷的遞歸中。
class Movie:
def __init__(self, ticket, score):
self.score = score
self.ticket = ticket
@property
def score(self):
return self._score
@score.setter
def score(self, score):
if score < 0:
raise ValueError("Negative value not allowed:{}".format(score))
self._score = score
@score.deleter
def score(self):
raise AttributeError("Can not delete score")
Property 的不足
對于 Property 來說,最大的不足就是它們不能重復使用。如果有多個屬性需要寫為 Property,那么代碼 / 重復的邏輯便會出現不少。雖然 Property 可以讓類從外部看起來借口整潔漂亮,但是卻做不到內部同樣整潔漂亮。
Descriptor
如何用描述符來解決上面 Property 邏輯重復的問題。
如果一個實例同時定義了 __get__()
和 __set__()
,那就被認為是數據描述符。如果描述符只定義了 __get__()
就被稱為非數據描述符。If an object defines both __get__()
and __set__()
, it is considered a data descriptor. Descriptors that only define __get__()
are called non-data descriptors (they are typically used for methods but other uses are possible).
數據描述符和非數據描述符不同在于「對于實例字典(dictionary)中的 items/entries 的計算的覆蓋(override)」。Data and non-data descriptors differ in how overrides are calculated with respect to entries in an instance’s dictionary.
如果實例的字典有一個和數據描述符一樣名稱的 item/entry,數據描述符優先。如果實例的字典有一個和非數據描述符一樣名稱的 item/entry,字典中的 item/entry 優先。
If an instance’s dictionary has an entry with the same name as a data descriptor, the data descriptor takes precedence. If an instance’s dictionary has an entry with the same name as a non-data descriptor, the dictionary entry takes precedence.
class Integer:
def __init__(self, name):
print ('descriptor __init__')
self.name = name
def __get__(self, instance, owner):
print ('descriptor __get__')
return instance.__dict__[self.name]
def __set__(self, instance, value):
print ('descriptor __set__')
if value < 0:
raise ValueError("Negative value not allowed")
instance.__dict__[self.name] = value
>>> class Movie:
... # class attribute
... score = Integer('score')
... ticket = Integer('ticket')
...
... def __init__(self):
... pass
descriptor __init__
descriptor __init__
>>> movie = Movie()
>>> movie.__dict__['ticket']
KeyError: 'ticket'
>>> movie.ticket = 1
descriptor __set__
>>> movie.ticket
descriptor __get__
1
>>> movie.__dict__['ticket']
1
在調用 movie.ticket = 1
時,descriptor 的 __set__()
使用 instance.__dict__[self.name] = value
在 Movie instance 中添加了新的 attribute 并且賦值。
但是這樣有些生硬,所以還缺一個構造函數。
class Integer:
def __init__(self, name):
print ('descriptor __init__')
self.name = name
def __get__(self, instance, owner):
print ('descriptor __get__')
return instance.__dict__[self.name]
def __set__(self, instance, value):
print ('descriptor __set__')
if value < 0:
raise ValueError("Negative value not allowed")
instance.__dict__[self.name] = value
class Movie:
# class attribute
score = Integer('score')
ticket = Integer('ticket')
def __init__(self, score, ticket):
# using self.attr to convert class attribute to object attribute
# and call descriptor __set__()
self.score = score
self.ticket = ticket
這樣在 get,set,和 delete 屬性的時候都會進入的 Integer
的 __get__
,__set__
,和 __del__
從而減少了重復的邏輯。
那么 Class 的屬性是怎么變為了 instance 的屬性呢?在 __init__
函數里訪問的是自己的 self.score
和 self.ticket
,怎么和類屬性 socre,ticket 關聯起來的?它們的調用順序是怎樣的?
Invoking Descriptors 調用描述符
這里我將翻譯 Python Descriptor 官方文檔,因為結合 MRO,和 Python 魔法方法,這段講解的已經比較詳細了。
描述符可以通過它的名字被直接調用。例如 d.__get__(obj)
,Movie.__dict__['ticket'].__get__(m, None)
。(A descriptor can be called directly by its method name. For example, d.__get__(obj)
.)
另外,一般的,描述符的調用自動作為屬性調用。例如,obj.d
在 obj
的字典里查找 d
,如果 d
定義了 __get__()
方法,那 d.__get__(obj)
就會根據優先原則被調用。(Alternatively, it is more common for a descriptor to be invoked automatically upon attribute access. For example, obj.d
looks up d
in the dictionary of obj
. If d
defines the method __get__()
, then d.__get__(obj)
is invoked according to the precedence rules listed below.)
調用細節取決于 obj 是實例還是類。(The details of invocation depend on whether obj is an object or a class.)
Python 魔法方法指南
先復習一下 Python 的魔法方法
__getattribute__(self, name)
__getattribute__
只能用新式類。當obj.attr
訪問實例屬性時,實際調用的是__getattribute__
。__getattr__(self, name)
當訪問一個根本不存在的(或者暫時不存在)屬性時,__getattr__(self, name)
會被調用。__call__(self, [args...])
當調用一個類時,例如obj = MyClass()
,實際就是調用MyClass.__call__()
。
對于實例,機制 object.__getattribute__()
中,將 b.x
的調用轉換為 type(b).__dict__['x'].__get__(b, type(b))
。這個實現是通過優先鏈給予數據描述符比實例的變量更高的優先級,實例的變量的優先級高于非數據描述符,而__getattr__()
的優先級最低。(For objects, the machinery is in object.__getattribute__()
which transforms b.x
into type(b).__dict__['x'].__get__(b, type(b))
. The implementation works through a precedence chain that gives data descriptors priority over instance variables, instance variables priority over non-data descriptors, and assigns lowest priority to __getattr__()
if provided. The full C implementation can be found in PyObject_GenericGetAttr()
in Objects/object.c.)
對于類,機制在 type.__getattribute__()
中,將 B.x
轉換為 B.__dict__['x'].__get__(None, B)
。(For classes, the machinery is in type.__getattribute__()
which transforms B.x
into B.__dict__['x'].__get__(None, B)
.)
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
- 描述符被
__getattribute__()
方法調用(descriptors are invoked by the__getattribute__()
method) - 覆寫
__getattribute__()
可以阻止自動的描述符調用(overriding__getattribute__()
prevents automatic descriptor calls) -
object.__getattribute__()
和type.__getattribute__()
對于__get__()
的調用不同(object.__getattribute__()
andtype.__getattribute__()
make different calls to__get__()
.) - 數據描述符會覆蓋實例字典(data descriptors always override instance dictionaries.)
- 非數據描述符會被實例字典覆蓋。(non-data descriptors may be overridden by instance dictionaries.)
被 super()
返回的 object 也有 __getattribute__()
方法用來調用描述符。調用 super(B, obj).m()
會使用 obj.__class__.__mro__
查找類 B 的基類 A,并返回 A.__dict__['m'].__get__(obj, B)
。如果返回的不是描述符,m 返回的就是無變化的(類 A 的變量)。如果不在類 A 的字典中,m 恢復使用 object.__getattribute__()
來搜索。(The object returned by super()
also has a custom __getattribute__()
method for invoking descriptors. The call super(B, obj).m()
searches obj.__class__.__mro__
for the base class A immediately following B and then returns A.__dict__['m'].__get__(obj, B)
. If not a descriptor, m is returned unchanged. If not in the dictionary, m reverts to a search using object.__getattribute__()
.)
以上的細節展示了描述符被調用的機制在 object,type,和 super()
中的 __getattribute__()
被實現。來源于 object 的類會繼承這個機制或元類提供了相似的功能。另外,類可以禁止描述符的調用通過覆寫 __getattribute__()
。(The details above show that the mechanism for descriptors is embedded in the __getattribute__()
methods for object, type, and super()
. Classes inherit this machinery when they derive from object or if they have a meta-class providing similar functionality. Likewise, classes can turn-off descriptor invocation by overriding __getattribute__()
.)
- 無論是實例還是類,實際都是在
type.__dict__['x']
找 descriptor;- 內部實際是按照 MRO 順序,順著類,父母類一路找,直到找到 descriptor;
- 找到后,判斷是否是 data descriptor;
- 如果不是 data descriptor, 在查找實例的 dict;
- 如果實例的 dict 沒有,則嘗試調用 descriptor 的
__get__()
;- 調用不成功,調用
__getattr__()
進行錯誤處理。
源碼分析
通過 CPython 源碼,可以驗證之前官方文檔中的說明。
PyObject_GenericGetAttr
PyObject *
PyObject_GenericGetAttr(PyObject *obj, PyObject *name)
{
return _PyObject_GenericGetAttrWithDict(obj, name, NULL, 0);
}
PyObject *
_PyObject_GenericGetAttrWithDict(PyObject *obj, PyObject *name,
PyObject *dict, int suppress)
{
/* Make sure the logic of _PyObject_GetMethod is in sync with
this method.
When suppress=1, this function suppress AttributeError.
*/
PyTypeObject *tp = Py_TYPE(obj);
PyObject *descr = NULL;
PyObject *res = NULL;
descrgetfunc f;
Py_ssize_t dictoffset;
PyObject **dictptr;
if (!PyUnicode_Check(name)){
PyErr_Format(PyExc_TypeError,
"attribute name must be string, not '%.200s'",
name->ob_type->tp_name);
return NULL;
}
Py_INCREF(name);
if (tp->tp_dict == NULL) {
if (PyType_Ready(tp) < 0)
goto done;
}
descr = _PyType_Lookup(tp, name);
f = NULL;
if (descr != NULL) {
Py_INCREF(descr);
f = descr->ob_type->tp_descr_get;
if (f != NULL && PyDescr_IsData(descr)) {
res = f(descr, obj, (PyObject *)obj->ob_type);
if (res == NULL && suppress &&
PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
}
goto done;
}
}
if (dict == NULL) {
/* Inline _PyObject_GetDictPtr */
dictoffset = tp->tp_dictoffset;
if (dictoffset != 0) {
if (dictoffset < 0) {
Py_ssize_t tsize;
size_t size;
tsize = ((PyVarObject *)obj)->ob_size;
if (tsize < 0)
tsize = -tsize;
size = _PyObject_VAR_SIZE(tp, tsize);
_PyObject_ASSERT(obj, size <= PY_SSIZE_T_MAX);
dictoffset += (Py_ssize_t)size;
_PyObject_ASSERT(obj, dictoffset > 0);
_PyObject_ASSERT(obj, dictoffset % SIZEOF_VOID_P == 0);
}
dictptr = (PyObject **) ((char *)obj + dictoffset);
dict = *dictptr;
}
}
if (dict != NULL) {
Py_INCREF(dict);
res = PyDict_GetItemWithError(dict, name);
if (res != NULL) {
Py_INCREF(res);
Py_DECREF(dict);
goto done;
}
else {
Py_DECREF(dict);
if (PyErr_Occurred()) {
if (suppress && PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
}
else {
goto done;
}
}
}
}
if (f != NULL) {
res = f(descr, obj, (PyObject *)Py_TYPE(obj));
if (res == NULL && suppress &&
PyErr_ExceptionMatches(PyExc_AttributeError)) {
PyErr_Clear();
}
goto done;
}
if (descr != NULL) {
res = descr;
descr = NULL;
goto done;
}
if (!suppress) {
PyErr_Format(PyExc_AttributeError,
"'%.50s' object has no attribute '%U'",
tp->tp_name, name);
}
done:
Py_XDECREF(descr);
Py_DECREF(name);
return res;
}
通過分析上面的源碼可以看到:
-
PyTypeObject *tp = Py_TYPE(obj);
填充了tp_dict
; - 之后
descr = _PyType_Lookup(tp, name);
找到 descriptor; -
descr->ob_type->tp_descr_get != NULL
和PyDescr_IsData(descr)
判斷數據描述符 - 不是數據描述符,且
dict != NULL
,返回PyDict_GetItemWithError(dict, name)
; -
dict
中沒有,descr->ob_type->tp_descr_get
返回非數據描述符的__get__()
方法。
_PyType_Lookup
/* Internal API to look for a name through the MRO.
This returns a borrowed reference, and doesn't set an exception! */
PyObject *
_PyType_Lookup(PyTypeObject *type, PyObject *name)
{
PyObject *res;
int error;
unsigned int h;
############
# 緩存部分代碼
############
/* We may end up clearing live exceptions below, so make sure it's ours. */
assert(!PyErr_Occurred());
res = find_name_in_mro(type, name, &error);
############
# 剩余代碼
############
}
可以看到之前的 descr = _PyType_Lookup(tp, name);
是來自于 find_name_in_mro(type, name, &error);
,descriptor 是根據 MRO 順序從類 / 父母類中找到的。
find_name_in_mro
/* Internal API to look for a name through the MRO, bypassing the method cache.
This returns a borrowed reference, and might set an exception.
'error' is set to: -1: error with exception; 1: error without exception; 0: ok */
static PyObject *
find_name_in_mro(PyTypeObject *type, PyObject *name, int *error)
{
Py_ssize_t i, n;
PyObject *mro, *res, *base, *dict;
Py_hash_t hash;
############
# 代碼
############
/* Look in tp_dict of types in MRO */
mro = type->tp_mro;
if (mro == NULL) {
if ((type->tp_flags & Py_TPFLAGS_READYING) == 0) {
if (PyType_Ready(type) < 0) {
*error = -1;
return NULL;
}
mro = type->tp_mro;
}
if (mro == NULL) {
*error = 1;
return NULL;
}
}
############
# 剩余代碼
############
}
Look in tp_dict of types in MRO,mro = type->tp_mro;
每個類都會有一個 tp_mro,通過這個確定遍歷的順序。
代碼的運行映證了上面文檔描述的調用描述符順序。
Property
class Property(fget=None, fset=None, fdel=None, doc=None)
這時我們再回來看比較常用的 Property。
Calling Property() 是一個構建數據描述符的簡單的方法,該數據描述符在訪問屬性時觸發函數調用。(Calling Property() is a succinct way of building a data descriptor that triggers function calls upon access to an attribute.)
Property 有兩種使用方式,一種是函數模式,一種是裝飾器模式。
函數模式
class C:
def __init__(self):
self._x = None
def getx(self):
return self._x
def setx(self, value):
self._x = value
def delx(self):
del self._x
x = Property(getx, setx, delx, "I'm the 'x' Property.")
要使用 Property()
,首先定義的 class 必須是新式類(object 的子類),Python 3 只有新式類。如果 c 是 C 的實例,c.x
將調用 fget()
在這里就是 getx()
,c.x = value
將調用 fset()
在這里就是 setx()
,del c.x
將調用 fdel()
在這里就是 delx()
,
使用 Property 的好處就是因為在訪問屬性的時候可以做一些檢查。如果沒有嚴格的要求,直接使用實例屬性可能更方便。
裝飾器模式
class C:
def __init__(self):
self._x = None
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
@x.deleter
def x(self):
del self._x
注意:三個函數的名字(也就是將來要訪問的屬性名)必須一致。
使用 Property 可以非常容易的實現屬性的讀寫控制,如果想要屬性只讀,則只需要提供 getter 方法。
class C:
def __init__(self):
self._x = None
@property
def x(self):
return self._x
對于描述符,只實現 get 函數的描述符是非數據描述符,根據屬性查找的優先級,非數據描述符的優先級是可以被實際屬性覆蓋(隱藏)的,但是執行如下代碼:
>>> c = C()
>>> c.x
>>> c.x = 3
Traceback (most recent call last):
File "<pyshell#39>", line 1, in <module>
c.x = 3
AttributeError: can't set attribute
從錯誤信息中可以看出,c.x = 3
的時候并不是動態產生一個實例屬性,也就是說 x 并不是被數據描述符,那么原因是什么呢?原因就在 Property,雖然表面上看屬性 x 只設置了 get()
,但其實 Property 是一個同時實現了 __get__()
,__set__()
,__del__()
方法的類(數據描述符)。因此使用 Property 生成的屬性其實是一個數據描述符!
使用 Python 模擬的 Property 代碼如下,可以看到,上面的 "At Property tributeError: can't set attribute” 異常其實是在 Property 中的 __set__()
中引發的,因為用戶沒有設置 fset:
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc
def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)
def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)
def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
classmethod 和 staticmethod
classmethod 和 staticmethod 的本質是什么?
class C:
def __init__(self):
pass
@classmethod
def foo():
pass
@staticmethod
def silly():
pass
>> C.__dict__
mappingproxy({'__module__': '__main__',
'__init__': <function __main__.C.__init__(self)>,
'foo': <classmethod at 0x10ed08da0>,
'silly': <staticmethod at 0x10ed08dd8>,
'__dict__': <attribute '__dict__' of 'C' objects>,
'__weakref__': <attribute '__weakref__' of 'C' objects>,
'__doc__': None})
可以看到,foo
成為了 classmethod,silly
成為了staticmethod。可以看到這和使用 Property 很像,上面代碼的 type(C.x)
是 property。函數都被重寫了。注意,這里用到了裝飾器方法,因為沒有使用 from functools import wraps
,所以函數會被重寫。
由于屬性搜索優先級,所以裝飾器的部分需要寫在 __get__()
中,這樣重寫后的函數 object 才會被自動調用。
這樣看來,classmethod 和 staticmethod 都是描述符,也是裝飾器,即作為裝飾器的描述符(descriptor that can be used as a decorator)。
classmethod
以下部分內容來自《Python Descriptors: Understanding and Using the Descriptor Protocol》,這本書比較有意思,在 AMAZON 上沒有評分,沒有 review。
classmethod
是另外一個作為裝飾器的描述符,但是不像 Property,沒有理由不以裝飾器的方法使用它(property 的使用方式有兩種)。(classmethod
is another descriptor that can be used as a decorator, but, unlike property, there's no good reason not to use it as one.)
class classmethod:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
return functools.partial(self.func, owner)
classmethod
是被數據描述符,所以他只實現了 __get__()
。__get__()
方法完全忽略了 instance
參數,因為就像名字寫的 classmethod,方法與類實例無關,只于類本身有關。最好的是,方法也可以被類實例調用。(classmethod is a non-data descriptor, so it only implements __get__()
. This __get__()
method completely ignores the instance
parameter because, as "class" in the name implies, the method has nothing to do with an instance of the class and only deals with the class itself. What's really nice is the fact that this can still be called from an instance without any issues.)
為什么 __get__()
返回了偏函數 functools.partial
并把 owner
傳入了? 為了理解這個,回憶一下被標記為 classmethod 的方法的參數列表。第一個參數為 class
參數,通常名為 cls
,(owner 被賦值給 cls)。這個 class
參數在偏函數方法 partial
中被填充,因此返回的函數可以在被調用時只傳入用戶想要明確提供的參數。真實的實現沒有使用 partial
,但是效果相似。(Why does the __get__()
method return a functools.partial
object with the owner passed in, though. To understand this, think about the parameter list of a function marked as a classmethod. The first parameter is the class parameter, usually named cls
. This class parameter is filled in the call to partial
so that the returned function can be called with just the arguments the user wants to explicitly provide. The true implementation doesn't use partial, but works similarly.)
同樣的,為了展示主要功能的運作,__name__
,__doc__
的代碼被忽略。
staticmethod
被標記為 staticmethod 的函數只是依附于類的函數。作為類的一部分,它只是有了特別的命名空間。因為 staticmethod 和 classmethod 都使用描述符實現,所以都可以被派生類繼承。(staticmethod 重要的特性)。(A method marked with staticmethod is strange in that it's a method that is really just a function, but it is "attached" to a class. Being part of the class doesn't do anything other than show users that it is associated with that class and giving it a more specific namespace. Also, interestingly, because staticmethod and classmethod are implemented using descriptors, they're inherited by subclasses.)
staticmethod 的實現比 classmethod 簡單,接受一個函數并當 __get__()
被調用的時候返回它。(The implementation of staticmethod is even simpler than that of classmethod; it just accepts a function and then returns it when __get__()
is called.)
class staticmethod:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
return self.func