Python面向对象编程中,类中定义的方法可以是 @classmethod
装饰的类方法,也可以是 @staticmethod
装饰的静态方法,用的最多的还是不带装饰器的实例方法,如果把这几个方法放一块,对初学者来说无疑是一头雾水,那我们该如何正确地使用它们呢?
先来看一个简单示例:
class A(object):
def m1(self, n):
print("self:", self)
@classmethod
def m2(cls, n):
print("cls:", cls)
@staticmethod
def m3(n):
pass
a = A()
a.m1(1) # self: <__main__.A object at 0x000001E596E41A90>
A.m2(1) # cls: <class '__main__.A'>
A.m3(1)
我在类中一共定义了3个方法,m1 是实例方法,第一个参数必须是 self(约定俗成的)。m2 是类方法,第一个参数必须是cls(同样是约定俗成),m3 是静态方法,参数根据业务需求定,可有可无。当程序运行时,大概发生了这么几件事(结合下面的图来看)。
第一步:代码从第一行开始执行 class 命令,此时会创建一个类 A 对象(没错,类也是对象,一切皆对象嘛)同时初始化类里面的属性和方法,记住,此刻实例对象还没创建出来。
第二、三步:接着执行 a=A(),系统自动调用类的构造器,构造出实例对象 a
第四步:接着调用 a.m1(1) ,m1 是实例方法,内部会自动把实例对象传递给 self 参数进行绑定,也就是说, self 和 a 指向的都是同一个实例对象。
第五步:调用A.m2(1)时,python内部隐式地把类对象传递给 cls 参数,cls 和 A 都指向类对象。
严格意义上来说,左边的都是变量名,是对象的引用,右边才是真正的对像,为了描述方便,我直接把 a 称为对象,你应该明白我说对象其实是它所引用右边的那个真正的对象。
再来看看每个方法各有什么特性
*
实例方法
print(A.m1)
#A.m1在py2中显示为<unbound method A.m1>
<function A.m1 at 0x000002BF7FF9A488>
print(a.m1)
<bound method A.m1 of <__main__.A object at 0x000002BF7FFA2BE0>>
A.m1是一个还没有绑定实例对象的方法,对于未绑定方法,调用 A.m1 时必须显示地传入一个实例对象进去,而 a.m1是已经绑定了实例的方法,python隐式地把对象传递给了self参数,所以不再手动传递参数,这是调用实例方法的过程。
A.m1(a, 1)
#等价
a.m1(1)
如果未绑定的方法 A.m1 不传实例对象给 self 时,就会报参数缺失错误,在 py3 与 py2 中,两者报的错误不一致,python2 要求第一个参数self是实例对象,而python3中可以是任意对象
A.m1(1)
TypeError: m1() missing 1 required positional argument: 'n'
静态方法
print(A.m3)
<function A.m3 at 0x000002BF7FF9A840>
print(a.m3)
<function A.m3 at 0x000002BF7FF9A840>
m3是类里面的一个静态方法,跟普通函数没什么区别,与类和实例都没有所谓的绑定关系,它只不过是碰巧存在类中的一个函数而已。不论是通过类还是实例都可以引用该方法。
A.m3(1)
# 等价
a.m3(1)
以上就是几个方法的基本介绍。现在把几个基本的概念理清楚了,那么现在来说说几个方法之间的使用场景以及他们之间的优缺点。
应用场景
静态方法的使用场景:
如果在方法中不需要访问任何实例方法和属性,纯粹地通过传入参数并返回数据的功能性方法,那么它就适合用静态方法来定义,它节省了实例化对象的开销成本,往往这种方法放在类外面的模块层作为一个函数存在也是没问题的,而放在类中,仅为这个类服务。
例如下面是微信公众号开发中验证微信签名的一个例子,它没有引用任何类或者实例相关的属性和方法。
from hashlib import sha1
import tornado.web
class SignatureHandler(tornado.web.RequestHandler):
def get(self):
"""
根据签名判断请求是否来自微信
"""
if self._check_sign(TOKEN, timestamp, nonce, signature):
self.write(echostr)
else:
self.write("你不是微信发过来的请求")
@staticmethod
def _check_sign(token, timestamp, nonce, signature):
sign = [token, timestamp, nonce]
sign.sort()
sign = "".join(sign)
sign = sha1(sign).hexdigest()
return sign == signature
类方法的使用场景有:
作为工厂方法创建实例对象,例如内置模块 datetime.date 类中就有大量使用类方法作为工厂方法,以此来创建date对象。
class date:
def __new__(cls, year, month=None, day=None):
self = object.__new__(cls)
self._year = year
self._month = month
self._day = day
return self
@classmethod
def fromtimestamp(cls, t):
y, m, d, * = _time.localtime(t)
return cls(y, m, d)
@classmethod
def today(cls):
t = _time.time()
return cls.fromtimestamp(t)
果希望在方法裡面调用静态类,那么把方法定义成类方法是合适的,因为要是定义成静态方法,那么你就要显示地引用类A,这对继承来说可不是一件好事情。
class A:
@staticmethod
def m1()
pass
@staticmethod
def m2():
A.m1() # bad
@classmethod
def m3(cls):
cls.m1() # good
其实也不算是什么深入理解吧,最多算是明白怎么用,真要深入理解恐怕还要另写一篇文章,有兴趣的可以去了解一下Python的描述符。
Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class能添加的属性:
>>> class Student(object):
... __slots__ = ('name', 'age')
效果
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'没有这个属性 所以报错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
使用__slots__要注意,__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的
:
除非在子类中也定义__slots__,这样,子类允许定义的属性就是自身的__slots__加上父类的__slots__
。
这样也不是很好所以用@property
这个修饰器
3、@property
如下面代码 设置和获得一个值 用起来比较麻烦吗
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
@property
的实现比较复杂,我们先考察如何使用。把一个getter
方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter
,负责把一个setter
方法变成属性赋值,于是,我们就拥有一个可控的属性操作:
就变成了了这样
class Student(object):
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
运行结果
>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
...
ValueError: score must between 0 ~ 100!
4、deleter修饰器
所以deleter就是对应着被deleter装饰的方法,这个方法并不是只为了删除这个属性,而是你在代码中执行什么就有什么效果。
class A:
def __init__(self):
self.__f = open(r'Files\a.txt','r')
@property
def f(self):
return self.__f
@f.deleter
def f(self):
self.__f.close() #先关闭文件
del self.__f #删除文件
a =A()
print(a.f)
print(a.__dict__)
del a.f#这样就是相当于调用@f.deleter下面的方法 执行里面代码
print(a.__dict__)
a =A()
print(a.f)