上面的阿明·罗纳彻(Armin Ronacher)的准确解释,扩展了他的答案,以便像我这样的初学者很好地理解:
在类中定义的方法的不同之处在于,无论是静态方法还是实例方法(还有另一种类型-类方法-此处未讨论,因此将其略过)都在于它们是否以某种方式绑定到类实例的事实。例如,说该方法在运行时是否收到对类实例的引用
class C:
a = []
def foo(self):
pass
C # this is the class object
C.a # is a list object (class property object)
C.foo # is a function object (class property object)
c = C()
c # this is the class instance
__dict__类对象的dictionary属性保存对类对象的所有属性和方法的引用,因此
>>> C.__dict__['foo']
<function foo at 0x17d05b0>
可以通过上面的方法访问foo方法。这里要注意的重要一点是python中的所有内容都是一个对象,因此上面字典中的引用本身指向其他对象。让我称它们为“类属性对象”-或简称为CPO。
如果CPO是描述符,则python解释器将调用__get__()CPO 的方法以访问其包含的值。
为了确定CPO是否是描述符,python解释器检查它是否实现了描述符协议。实现描述符协议就是实现3种方法
def __get__(self, instance, owner)
def __set__(self, instance, value)
def __delete__(self, instance)
例如
>>> C.__dict__['foo'].__get__(c, C)
哪里
self 是CPO(可以是list,str,function等的实例),由运行时提供
instance 是定义此CPO的类的实例(上面的对象'c'),需要由我们明确提供
owner是定义此CPO的类(上面的类对象'C'),需要由我们提供。但这是因为我们在CPO上调用它。当我们在实例上调用它时,我们不需要提供它,因为运行时可以提供实例或其类(多态)
value 是CPO的预期值,需要由我们提供
并非所有的CPO都是描述符。例如
>>> C.__dict__['foo'].__get__(None, C)
<function C.foo at 0x10a72f510>
>>> C.__dict__['a'].__get__(None, C)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__get__'
这是因为列表类未实现描述符协议。
因此自变量c.foo(self)是必需的,因为它的方法签名实际上就是这个C.__dict__['foo'].__get__(c, C)(如上所述,不需要C,因为它可以被发现或多态),这也是为什么如果不传递所需的实例参数就会得到TypeError的原因。
如果您注意到该方法仍通过类Object C进行引用,则通过将实例对象形式的上下文传递给该函数来实现与类实例的绑定。
这非常棒,因为如果您选择不保留上下文或不绑定到实例,则所需要做的只是编写一个类来包装描述符CPO并重写其__get__()方法以不需要上下文。这个新类称为装饰器,通过关键字应用@staticmethod
class C(object):
@staticmethod
def foo():
pass
新包装的CPO中foo没有上下文不会引发错误,可以通过以下方式进行验证:
>>> C.__dict__['foo'].__get__(None, C)
<function foo at 0x17d0c30>
静态方法的用例更多是一种命名空间和代码可维护性(将其从类中取出并在整个模块中使用)。
只要有可能,最好编写静态方法而不是实例方法,除非您当然需要使方法复杂化(例如访问实例变量,类变量等)。原因之一是通过不保留对对象的不必要引用来简化垃圾回收。