一、使用__slots__
众所周知,Python的动态绑定允许我们在程序运行的过程中动态给class或实例加上功能。但是如果我们想要限制实例的属性,则可以在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
>>> class Student(object):
... __slots__ = ('name', 'age') #用tuple定义允许绑定的属性名称
...
>>> s = Student() #创建新的实例
>>> s.name = 'Michael'
>>> s.age = 25
>>> s.score = 99
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'
需要注意的是,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。除非在子类中也定义__slots__,这样子类实例允许定义的属性就是自身的__slots__加上父类的__slots__。
二、使用@property
在class中定义一个属性时,直接将属性暴露出去的方法会使得属性可被任意修改,这显然是不可取的。一般情况下,我们可以通过定义get和set的方法来加以限制:
class Student(object):
def get_score(self):
return self._score
def set_score(self, value):
if not isinstance(value, int):
raise ValueError('scoer must be an integet!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
而Python提供了另一种简洁的方法:@property装饰器负责把一个方法变成属性调用
class Student(object):
@property #相当于get
def score(self):
return self._score
@score.setter #相当于set
def score(self, value):
if not isinstance(vlaue, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value
#score方法可以当作属性一样调用
三、多重继承
在设计类的继承关系时,通常主线都是单一继承下来的。但是,如果需要“混入”额外的功能,通过多重继承就可以实现。这种设计通常称之为Mixln。
Python自带的很多库也使用了Mixln。举个例子,Python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixIn和ThreadingMixIn提供。通过组合,我们就可以创造出合适的服务来。
比如,编写一个多进程模式的TCP服务,定义如下:
class MyTCPServer(TCPServer, ForkingMixIn):
pass
编写一个多线程模的UDP服务,定义如下:
class MyUDPServer(UDPServer, ThreadingMixIn):
pass
这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
四、定制类
看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。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) |
__repr__() | 返回程序开发者看到的字符串 | >>> class Student(object): ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return 'Student object (name: %s)' % self.name ... __repr__ = __str__ ... >>> s = Student('Michael') >>> s Student object (name: Michael) |
__iter__() | 如果一个类想被用于for...in循环,类似list或tuple那样,就必须实现__iter__()方法。该方法返回一个迭代对象,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 #返回下一个值 |
__getitem__ | (1)让实例获得索引功能 (2)让实例获得切片功能 |
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 |
__getattr__ | 动态返回一个属性 | class Student(objcet): def __init__(self, attr): if attr = 'score': return 99 if attr='age': return lambda: 25 raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr) |
__call__ | 任何类,只需要定义一个__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官方文档。
五、使用枚举类
在Python中要定义枚举类型,可以定义一个class类型,每个常量都是class的一个唯一实例。Python提供了Enum类来实现这个功能:
from enum import Enum
Month = Enum('Month', ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'))
这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value) #value属性则是自动赋给成员的int常量,默认从1开始计数
如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:
from enum import Enum, unique
@unique #@unique装饰器可以帮助我们检查保证没有重复值
class Weekday(Enum):
Sun = 0 #Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
访问这些枚举类型可以有若干种方法:
>>> 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
>>> Weekday(7)
Traceback (most recent call last):
...
ValueError: 7 is not a valid Weekday
>>> for name, member in Weekday.__members__.items():
... print(name, '=>', member)
...
Sun => Weekday.Sun
Mon => Weekday.Mon
Tue => Weekday.Tue
Wed => Weekday.Wed
Thu => Weekday.Thu
Fri => Weekday.Fri
Sat => Weekday.Sat
可见,既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。