(轉)[Python]例項方法、類方法、靜態方法
原文:https://zhuanlan.zhihu.com/p/40162669
一、類(class)和例項(instance)
類是建立例項的模板,而例項則是一個一個具體的物件,各個例項擁有的資料都互相獨立,互不影響。以Dog類為例,類就像一個物件工廠,可以生產一個或多個例項物件。
>>> class Dog(object):
... pass
...
>>> Dog
<class '__main__.Dog'>
>>> dog = Dog()
>>> dog
<__main__.Dog object at 0x10cd194a8>
>>> dog2 = Dog()
>>> dog2
<__main__.Dog object at 0x10cd19518>
二、初探例項方法、類方法、靜態方法
先直接上程式碼:
class MyClass(object):
# 例項方法
def instance_method(self):
print('instance method called', self)
# 類方法
@classmethod
def class_method(cls):
print('class method called', cls)
# 靜態方法
@staticmethod
def static_method():
print('static method called')
上述MyClass類中分別定義了三種不同型別的方法。
這三種方法在定義上(形式上)有如下的不同之處:
- 例項方法是一個普通的函式,類方法和靜態方法都是通過函式裝飾器的方式實現的;
- 例項方法需要傳入self,類方法需要傳入cls引數,靜態方法無需傳入self引數或者是cls引數(但不等同於不能傳入引數)
注意:self引數和cls引數的區別,下文會提到。
下面分別嘗試呼叫三個方法:
my_class = MyClass() # 例項化
my_class.instance_method() # 例項方法
my_class.class_method() # 類方法
my_class.static_method() # 靜態方法
1️⃣當例項化時,my_class指向例項化物件:
2️⃣當呼叫例項方法(instance method)時,self引數指向的是剛剛例項化出的my_class例項物件:
3️⃣當呼叫類方法(class method)時,cls引數指向的是一開始定義的MyClass類物件(注意不是例項物件):
4️⃣當呼叫靜態方法(static method)時:
本小節內容,主要區別了例項方法、類方法、靜態方法的定義形式,以及區別了self、cls引數的指向物件。
注意:不管self引數,還是cls引數,都是一種約定俗成的用法,其實是可以使用其他的引數名代替。但是不建議使用其他引數名,畢竟程式碼不是隻是寫給自己看的。
三、再探例項方法、類方法、靜態方法
上文都是通過例項化出的物件,對三種方法進行的呼叫,並且都得到了我們想要的結果;如果不通過例項化物件,而是直接通過類物件(Python中一切皆物件,包括類本身)呼叫,會有什麼結果呢?
MyClass.instance_method() # 例項方法
MyClass.class_method() # 類方法
MyClass.static_method() # 靜態方法
1️⃣當通過類物件呼叫例項方法時,直接報錯,提示缺引數self。
因為點語法會將類物件MyClass傳給instance_method()方法,但是instance_method()引數需要指向的是一個例項物件,而非類物件。
TypeError: static_method() missing 1 required positional argument: 'self'
2️⃣當通過類物件呼叫類方法時,這裡的結果與通過例項物件呼叫是完全一樣的。
Python官方文件的解釋:It can be called either on the class (such asC.f()
) or on an instance (such asC().f()
). The instance is ignored except for its class. If a class method is called for a derived class, the derived class object is passed as the implied first argument.
大致意思,類方法可以通過類物件或者例項物件呼叫。如果是通過例項物件呼叫的,那麼例項物件會被忽略,而是轉而通過其類物件進行呼叫。類物件,會以引數形式傳給類方法(點語法),作為類方法的第一個引數,也就是cls引數。
3️⃣當通過類物件呼叫靜態方法時,這裡的結果與通過例項物件呼叫是完全一樣的。通過例項物件呼叫靜態方法時,例項物件也會被忽略,而是通過其類物件進行呼叫,這一點與類方法的呼叫是一樣的。
說明:為了簡化所舉的例子,本文中所定義的MyClass類並沒有採用self或者cls之外的引數,三種方法都可以使用額外的引數,跟函式定義引數是一樣的。
四、類方法使用場景
由於類方法無須建立例項物件呼叫,所以類方法的呼叫較例項方法更為靈活。以Django中View檢視類原始碼舉例:
class View(object):
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
"""
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
for key, value in six.iteritems(kwargs):
setattr(self, key, value)
@classonlymethod
def as_view(cls, **initkwargs):
"""
Main entry point for a request-response process.
"""
for key in initkwargs:
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r. as_view "
"only accepts arguments that are already "
"attributes of the class." % (cls.__name__, key))
def view(request, *args, **kwargs):
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
self.request = request
self.args = args
self.kwargs = kwargs
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
return view
def dispatch(self, request, *args, **kwargs):
# 省略部分程式碼