本文共 5540 字,大约阅读时间需要 18 分钟。
本文参考:
__slots__
果我们想要限制实例的属性,比如,只允许对Student
实例添加name和age属性。
class Student(object): __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
定义一个特殊的__slots__
变量,来限制该class实例能添加的属性:
>>> s = Student() # 创建新的实例>>> s.name = 'Michael' # 绑定属性'name'>>> s.age = 25 # 绑定属性'age'>>> s.score = 99 # 绑定属性'score'Traceback (most recent call last): File "", line 1, in AttributeError: 'Student' object has no attribute 'score'
对继承的子类是不起作用的:
>>> class GraduateStudent(Student):... pass...>>> g = GraduateStudent()>>> g.score = 9999
@property
装饰器(decorator)可以给函数动态加上功能,对于类的方法,装饰器一样起作用。Python内置的@property
装饰器就是负责把一个方法变成属性调用的:
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
把一个getter方法变成属性,只需要加上@property
就可以了,此时,@property
本身又创建了另一个装饰器@score.setter
,负责把一个setter方法变成属性赋值
>>> s = Student()>>> s.score = 60 # OK,实际转化为s.set_score(60)>>> s.score # OK,实际转化为s.get_score()60>>> s.score = 9999Traceback (most recent call last): ...ValueError: score must between 0 ~ 100!
在设计类的继承关系时,通常,主线都是单一继承下来的,但是,如果需要“混入”额外的功能,通过多重继承就可以实现,这种设计通常称之为MixIn。
class Dog(Mammal, RunnableMixIn, CarnivorousMixIn): pass
举个例子,Python自带了TCPServer
和UDPServer
这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn
和ThreadingMixIn
提供
class MyTCPServer(TCPServer, ForkingMixIn): pass
class MyUDPServer(UDPServer, ThreadingMixIn): pass
__slots__
我们已经知道怎么用了,__len__()
方法我们也知道是为了能让class作用于len()
函数。
Python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。
__str__
打印一个实例:
>>> class Student(object):... def __init__(self, name):... self.name = name... def __str__(self):... return 'Student object (name: %s)' % self.name...>>> print(Student('Michael'))Student object (name: Michael)
【注】不用print
,打印出来的实例:
>>> s = Student('Michael')>>> s<__main__.Student object at 0x109afb310>
这是因为直接显示变量调用的不是__str__()
,而是__repr__()
,两者的区别是__str__()
返回用户看到的字符串,而__repr__()
返回程序开发者看到的字符串,也就是说,__repr__()
是为调试服务的。(解决: __repr__ = __str__
)
__iter__
一个类想被用于for ... in
循环,类似list或tuple那样,就必须实现一个__iter__()
方法,该方法返回一个迭代对象。Python的for循环就会不断调用该迭代对象的__next__()
方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。
class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化两个计数器a,b def __iter__(self): return self # 实例本身就是迭代对象,故返回自己 def __next__(self): self.a, self.b = self.b, self.a + self.b # 计算下一个值 if self.a > 100000: # 退出循环的条件 raise StopIteration() return self.a # 返回下一个值
>>> for n in Fib():... print(n)...11235...4636875025
__getitem__
要表现得像list那样按照下标取出元素,需要实现__getitem__()
方法:
class Fib(object): def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a + b return a
>>> f = Fib()>>> f[0]1>>> f[1]1>>> f[2]2>>> f[3]3>>> f[10]89>>> f[100]573147844013817084101
新增切片方法:
class Fib(object): def __getitem__(self, n): if isinstance(n, int): # n是索引 a, b = 1, 1 for x in range(n): a, b = b, a + b return a if isinstance(n, slice): # n是切片 start = n.start stop = n.stop if start is None: start = 0 a, b = 1, 1 L = [] for x in range(stop): if x >= start: L.append(a) a, b = b, a + b return L
>>> f = Fib()>>> f[0:5][1, 1, 2, 3, 5]>>> f[:10][1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
没有对step参数作处理,也没有对负数作处理,所以,要正确实现一个__getitem__()
还是有很多工作要做的
__getattr__
正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错,要避免这个错误,除了可以加上这个属性外,写一个__getattr__()
方法,动态返回一个属性
class Student(object): def __init__(self): self.name = 'Michael' def __getattr__(self, attr): if attr=='score': return 99
当调用不存在的属性时,比如score,Python解释器会试图调用__getattr__(self, 'score')
来尝试获得属性
>>> s = Student()>>> s.name'Michael'>>> s.score99
只有在没有找到属性的情况下,才调用__getattr__
,已有的属性,比如name,不会在__getattr__
中查找。
这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全动态的情况作调用。
利用完全动态的__getattr__,我们可以写出一个链式调用:
class Chain(object): def __init__(self, path=''): self._path = path def __getattr__(self, path): return Chain('%s/%s' % (self._path, path)) def __str__(self): return self._path __repr__ = __str__
>>> Chain().status.user.timeline.list'/status/user/timeline/list'
如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。这样,无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变!
__call__
当我们调用实例方法时,我们用instance.method()
来调用,只需要定义一个__call__()
方法,就可以直接对实例进行调用
class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name)
调用方式如下:
>>> s = Student('Michael')>>> s() # self参数不要传入My name is Michael.
Python提供了Enum类来实现每个常量都是class的一个唯一实例
from enum import EnumMonth = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
更精确地控制枚举类型,可以从Enum派生出自定义类:
from enum import Enum, unique@uniqueclass Weekday(Enum): Sun = 0 # Sun的value被设定为0 Mon = 1 Tue = 2 Wed = 3 Thu = 4 Fri = 5 Sat = 6
@unique
装饰器可以帮助我们检查保证没有重复值。
>>> day1 = Weekday.Mon>>> print(day1)Weekday.Mon>>> print(Weekday.Tue)Weekday.Tue>>> print(Weekday['Tue'])Weekday.Tue>>> print(Weekday.Tue.value)2>>> print(day1 == Weekday.Mon)True>>> print(day1 == Weekday.Tue)False>>> print(Weekday(1))Weekday.Mon>>> print(day1 == Weekday(1))True
转载地址:http://tuhpi.baihongyu.com/