样解释成对象自身,换句话说只能传递一个参数c.m2("arg2")
,这显然有悖静态方法的编码方式。
在python中,要定义严格的类方法、静态方法,需要使用内置的装饰器函数classmethod()、staticmethod()来装饰,装饰后无论使用对象名去调用还是使用类名去调用,都可以。
例如:
class cls():
def m1(self,arg1):
print("m1: ", self, arg1)
@classmethod
def m2(self,arg1):
print("m2: ", self, arg1)
@staticmethod
def m3(arg1, arg2):
print("m3: ", arg1, arg2)
上面定义了普通方法、类方法和静态方法。如果尚不了解装饰器的用法,暂时只需知道上面的@xxx
将它下面的函数(方法)扩展成了类方法、静态方法即可。
调用实例方法:
>>> c = cls()
>>> c.m1("hello")
m1: <__main__.cls object at 0x000001EE2DA840B8> hello
注意输出的self是"...object...",和下面的类方法调用注意区分比较。
调用类方法。因为@classmethod
已经将m2包装成了类方法,所以m2的第一个self参数将总是代表类名,而无论是使用对象去调用m2还是使用类名去调用m2。
>>> c.m2("hello")
m2: <class '__main__.cls'> hello
>>> cls.m2("hello")
m2: <class '__main__.cls'> hello
如果输出m2方法,会发现它已经是绑定方法,也就是说和类名进行了绑定(这里不是和对象名进行绑定)。
>>> c.m2
<bound method cls.m2 of <class '__main__.cls'>>
>>> cls.m2
<bound method cls.m2 of <class '__main__.cls'>>
调用静态方法。
>>> c.m3("hello","world")
m3: hello world
>>> cls.m3("hello","world")
m3: hello world
静态方法都是未绑定的函数:
>>> c.m3
<function cls.m3 at 0x000001EE2DA789D8>
>>> cls.m3
<function cls.m3 at 0x000001EE2DA789D8>
一般来说,类方法用于在类中操作/返回和类名有关的内容,静态方法用于在类中做和类或对象完全无关的操作。一个比较好理解的例子是,一个Employee类,要检查员工的年龄范围在16-35,如果年龄在这范围内,就返回一个员工对象,可以将这个逻辑定义为类方法。如果只是检查年龄范围来决定True或False这样和类/对象无关的操作,则定义为静态方法。
class Employee:
@staticmethod
def age_ok(age):
if 16<age<35:
return True
else:
return False
@classmethod
def age_check(cls, age):
if 16<age<35:
return cls(...)
私有属性
python没有private关键字来修饰属性使其变成私有属性,但是带上双下划线前缀的属性且没有后缀下划线的属性(__X
)可以认为是私有属性。它仅仅只是约定性的私有属性,不代表外界真的不能访问。
实际上,使用__X
这样的属性,在类的内部访问时会自动进行扩展为_clsname__X
,也就是加个前缀下划线,再加个类名。因为扩展时加上了类名,使得这个属性在名称上是独属于这个类的。
例如:
class cls():
__X = 12
def m1(self,y):
self.__Y = y
print(self.__X)
print(self.__Y)
>>> print(cls.__dict__.keys())
dict_keys([..., '_cls__X', 'm1', ....])
>>> c = cls()
>>> c.m1(22)
12
22
>>> print(c.__dict__.keys())
dict_keys(['_cls__Y'])
因为已经扩展了属性的名称,所以无法在类的外界通过直接的名称__X
去访问对应的属性。
>>> c.__Y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'cls' object has no attribute
'__Y'
>>> c.__X
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'cls' object has no attribute
'__X'
>>> cls.__X
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'cls' has no attribute '__X'
前面说了,这种加双下划线前缀的属性仅仅只是一个约定形式,虽然在外界无法直接通过名称去访问,但是仍有不少方法去访问。例如通过扩展后的名称、通过字典__dict__
:
>>> cls._cls__X
12
>>> c._cls__Y
22
>>> c.__dict__['_cls__Y']
22
要想严格地声明属性的私有性,可以编写装饰器类,在装饰器类中完成属性的判断。
方法的默认可变参数陷阱
如果一个方法的参数给了默认参数,且这个默认参数是一个可变类型,那么这里有一个陷阱:使用这个默认参数的时候各对象会共享这个可变默认值。
例如:
class A:
def __init__(self, arg=[]):
self.data = arg
def add(self, value):
self.data.append(value)
# 两个不同对象,且都使用参数arg的默认值
a1