Class method differences in Python: bound, unbound and static
下面的类方法有什么区别?
一个是静态的,另一个不是?
1 2 3 4 5 6 7 8 9 10 | class Test(object): def method_one(self): print"Called method_one" def method_two(): print"Called method_two" a_test = Test() a_test.method_one() a_test.method_two() |
在Python中,绑定方法和未绑定方法之间存在区别。
基本上,对成员函数(如
1 | a_test.method_one() |
转换为
1 | Test.method_one(a_test) |
即对未绑定方法的调用。因此,调用您的
1 2 3 4 5 | >>> a_test = Test() >>> a_test.method_two() Traceback (most recent call last): File"<stdin>", line 1, in <module> TypeError: method_two() takes no arguments (1 given) |
可以使用decorator更改方法的行为
1 2 3 4 5 6 7 | class Test(object): def method_one(self): print"Called method_one" @staticmethod def method_two(): print"Called method two" |
decorator告诉内置的默认元类
现在,可以直接在实例或类上调用静态方法:
1 2 3 4 5 6 7 | >>> a_test = Test() >>> a_test.method_one() Called method_one >>> a_test.method_two() Called method_two >>> Test.method_two() Called method_two |
一旦理解了描述符系统的基本知识,python中的方法就变得非常简单。想象一下下面的课程:
1 2 3 | class C(object): def foo(self): pass |
现在让我们看看shell中的类:
1 2 3 4 | >>> C.foo <unbound method C.foo> >>> C.__dict__['foo'] <function foo at 0x17d05b0> |
正如您可以看到的,如果访问类的
1 2 | >>> C.__dict__['foo'].__get__(None, C) <unbound method C.foo> |
这是因为函数有一个使它们成为描述符的
1 2 3 | >>> c = C() >>> C.__dict__['foo'].__get__(c, C) <bound method C.foo of <__main__.C object at 0x17bd4d0>> |
为什么Python会这样做?因为方法对象将函数的第一个参数绑定到类的实例。这就是自我的来源。现在,有时您不希望类将函数设为方法,这就是
1 2 3 4 | class C(object): @staticmethod def foo(): pass |
1 2 | >>> C.__dict__['foo'].__get__(None, C) <function foo at 0x17d0c30> |
希望能解释清楚。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | >>> class Class(object): ... def __init__(self): ... self.i = 0 ... def instance_method(self): ... self.i += 1 ... print self.i ... c = 0 ... @classmethod ... def class_method(cls): ... cls.c += 1 ... print cls.c ... @staticmethod ... def static_method(s): ... s += 1 ... print s ... >>> a = Class() >>> a.class_method() 1 >>> Class.class_method() # The class shares this value across instances 2 >>> a.instance_method() 1 >>> Class.instance_method() # The class cannot use an instance method Traceback (most recent call last): File"<stdin>", line 1, in <module> TypeError: unbound method instance_method() must be called with Class instance as first argument (got nothing instead) >>> Class.instance_method(a) 2 >>> b = 0 >>> a.static_method(b) 1 >>> a.static_method(a.c) # Static method does not have direct access to >>> # class or instance properties. 3 >>> Class.c # a.c above was passed by value and not by reference. 2 >>> a.c 2 >>> a.c = 5 # The connection between the instance >>> Class.c # and its class is weak as seen here. 2 >>> Class.class_method() 3 >>> a.c 5 |
调用类成员时,Python会自动使用对对象的引用作为第一个参数。变量
要使其实际工作,可以将其附加到类定义中:
1 | method_two = staticmethod(method_two) |
或者可以使用
方法"2"不起作用,因为您定义的是成员函数,但没有告诉它该函数是什么成员。如果执行最后一行,将得到:
1 2 3 4 | >>> a_test.method_two() Traceback (most recent call last): File"<stdin>", line 1, in <module> TypeError: method_two() takes no arguments (1 given) |
如果要为类定义成员函数,则第一个参数必须始终为"self"。
上面Armin Ronacher的准确解释,扩展了他的答案,使像我这样的初学者能够很好地理解:
在一个类中定义的方法,无论是静态方法还是实例方法(还有另一个类型-类方法-这里没有讨论,所以跳过它),其区别在于它们是否以某种方式绑定到类实例。例如,假设方法是否在运行时接收到对类实例的引用
1 2 3 4 5 6 7 8 9 10 | 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 |
类对象的
1 2 | >>> C.__dict__['foo'] <function foo at 0x17d05b0> |
方法foo可以如上所述访问。这里需要注意的一点是,Python中的所有内容都是一个对象,因此上面字典中的引用本身就是指向其他对象的。让我把它们称为类属性对象——或者在我的简短回答范围内称为CPO。
如果cpo是描述符,那么python解释器调用cpo的
为了确定CPO是否是描述符,python解释器检查它是否实现了描述符协议。实现描述符协议就是实现3种方法
1 2 3 | def __get__(self, instance, owner) def __set__(self, instance, value) def __delete__(self, instance) |
例如
1 | >>> C.__dict__['foo'].__get__(c, C) |
在哪里?
self 是cpo(它可以是list、str、function等的实例),由运行时提供instance 是定义该cpo的类的实例(上面的对象‘c’),需要由我们明确提供。owner 是定义这个cpo的类(上面的类对象‘c’),需要我们提供。然而,这是因为我们在CPO上打电话。当我们在实例上调用它时,我们不需要提供这个,因为运行时可以提供实例或其类(多态性)。value 是CPO的预期价值,需要我方提供。
不是所有的cpo都是描述符。例如
1 2 3 4 5 6 | >>> 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引用,并且通过将实例对象形式的上下文传递到此函数中来实现与类实例的绑定。
这非常棒,因为如果您选择不保留上下文或不绑定到实例,那么所需要的就是编写一个类来包装描述符cpo,并重写其
1 2 3 4 | class C(object): @staticmethod def foo(): pass |
新包装的cpo
1 2 | >>> C.__dict__['foo'].__get__(None, C) <function foo at 0x17d0c30> |
静态方法的用例更多的是名称间距和代码可维护性(将其从类中去掉,并使其在整个模块中都可用,等等)。
如果可能的话,最好是编写静态方法而不是实例方法,除非您当然需要将这些方法(如访问实例变量、类变量等)进行上下文化。一个原因是通过不保留对对象的不需要的引用来简化垃圾收集。
请从guido头等舱阅读这些文档,一切都清楚地解释了如何产生未绑定、绑定的方法。
第二个方法不起作用,因为当您像python那样调用它时,内部会尝试使用a_test实例作为第一个参数来调用它,但是您的方法_2不接受任何参数,因此它不起作用,您将得到一个运行时错误。如果需要静态方法的等价物,可以使用类方法。在Python中,类方法比Java或C语言中的静态方法要少得多。通常最好的解决方案是在模块中使用一个方法,在类定义之外,这些方法比类方法更有效。
这是一个错误。
首先,第一行应该是这样的(注意大写字母)
1 | class Test(object): |
每当调用类的方法时,它将自身作为第一个参数(因此名为self),方法ou two给出此错误
1 2 3 4 | >>> a.method_two() Traceback (most recent call last): File"<stdin>", line 1, in <module> TypeError: method_two() takes no arguments (1 given) |
对方法"二"的调用将引发一个异常,即不接受自己的参数,python运行时将自动传递该参数。
如果要在python类中创建静态方法,请用
1 2 3 4 5 6 | Class Test(Object): @staticmethod def method_two(): print"Called method_two" Test.method_two() |
实例方法是一个有界函数,当您像