使用__slots__
- 通常方法直接定义在class中,动态绑定允许程序运行过程中动态给class加上功能。
使用__slots__
- 定义class的时候,定义一个特殊的
__slots__
变量,限制该class实例能添加的属性。
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用的。- 子类实例允许定义的属性就是自身
__slots__
加上父类__slots__
。
使用@property
@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
s = Student()
s.score = 60 # OK,实际转化为s.set_score(60)
s.score # OK,实际转化为s.get_score()
- 定义只读属性:只定义getter方法,不定义setter方法
# birth为可读写属性;age为只读属性
class Student(object):
@property
def birth(self):
return self._birth
@birth.setter
def birth(self, value):
self._birth = value
@property
def age(self):
return 2015 - self._birth
- 属性的方法名不要和实例变量重名
多重继承
- 一个子类可同时获得多个父类的所有功能。
MixIn
- 设计类的继承关系,通常主线都是单一继承。
- 需要“混入”额外功能,通过多重继承实现。
- 给一个类增加多个功能,设计类时,优先考虑通过多重继承组合多个MixIn功能。
# 多进程模式的TCP服务
class MyTCPServer(TCPServer, ForkingMixIn):
pass
# 多线程模式的UDP服务
class MyUDPServer(UDPServer, ThreadingMixIn):
pass
# 协程模型
class MyTCPServer(TCPServer, CoroutineMixIn):
pass
定制类
- __xxx__特殊用途的函数,可以帮助我们定制类。
__str__
- 定义
__str__()
方法,返回字符串,用于print()。 - 直接显示变量调用的是
__repr__()
,
返回程序开发者看到的字符串,服务调试。
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name=%s)' % self.name
__repr__ = __str__
__iter__
定义
__iter__()
方法,返回迭代对象,用于for ... in
循环。__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__
- 定义
__getitem__()
方法,可实现按照下标取出元素
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
# 实现切片方法
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
- 如果把对象看成
dict
,__getitem__()
的参数也可能是一个可以作key的object,例如str。
__setitem__()
方法,把对象视作list或dict来对集合赋值。__delitem__()
方法,用于删除某个元素。
__getattr__
__getattr__()
方法,动态返回一个属性。- 当调用不存在的属性时,会试图调用
__getattr__()
来尝试获得属性。返回函数也可以。
# 返回属性
class Student(object):
def __init__(self):
self.name = 'Michael'
def __getattr__(self, attr):
if attr=='score':
return 99
# 返回函数
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
# 只响应特定属性,其他抛出AttributeError的错误
class Student(object):
def __getattr__(self, attr):
if attr=='age':
return lambda: 25
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
- 可以把一个类的所有属性和方法调用全部动态化处理,不需要任何特殊手段。
- 完全动态调用的特性可以针对完全动态的情况作调用。
# 利用完全动态的__getattr__,写出一个链式调用
# 针对REST API写SDK
# 无论API怎么变,SDK都可以根据URL实现完全动态的调用,而且,不随API的增加而改变
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'
__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参数不要传入
__call__()
还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样。callable()
可以判断一个对象是否是“可调用”对象。
使用枚举类
- 为枚举类型定义一个class类型,每个常量都是class的一个唯一实例。
Enum
类可实现。
from enum import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
# 直接使用
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
# 调用
print(Weekday.Tue) # Weekday.Tue
print(Weekday['Tue']) # Weekday.Tue
print(Weekday.Tue.value) # 2
Weekday(1) # Weekday.Mon
使用元类
type()
- 函数和类的定义,动态语言运行时动态创建的;静态语言编译时定义。
type()
查看一个类型或变量的类型,类类型就是type
,实例类型就是class类名
。
type()
既可以返回一个对象的类型,又可以创建出新的类型。
def fn(self, name='world'): # 先定义函数
print('Hello, %s.' % name)
Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
type()
创建class对象依次传入:class的名称;继承的父类集合;方法绑定函数。type()
允许动态创建类,动态语言支持运行期动态创建类。
metaclass
- metaclass控制类的创建行为。先定义metaclass(元类),然后创建类,最后创建实例。
# 给自定义MyList增加add方法。
# metaclass是类的模板,所以必须从`type`类型派生。
# 默认习惯,metaclass的类名总是以Metaclass结尾。
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
# 定义类时使用ListMetaclass来定制类,传入关键字参数metaclass.
# 创建MyList时,要通过ListMetaclass.__new__()来创建
class MyList(list, metaclass=ListMetaclass):
pass
-
__new__()
方法接收参数:准备创建的类对象;类名;父类集合;类的方法集合。 -
ORM(Object Relational Mapping),对象-关系映射。
-
关系数据库的一行映射为一个对象,一个类对应一个表。
# 编写一个ORM框架,所有的类都只能动态定义。
# 使用者才能根据表的结构定义出对应的类来。
# 预设调用接口
class User(Model):
# 定义类的属性到列的映射:
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
# 创建一个实例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到数据库:
u.save()
# 按接口实现ORM
# 保存数据库表的字段名和字段类型
class Field(object):
def __init__(self, name, column_type):
self.name = name
self.column_type = column_type
def __str__(self):
return '<%s:%s>' % (self.__class__.__name__, self.name)
# 定义各种类型的Field,比如StringField,IntegerField等
class StringField(Field):
def __init__(self, name):
super(StringField, self).__init__(name, 'varchar(100)')
class IntegerField(Field):
def __init__(self, name):
super(IntegerField, self).__init__(name, 'bigint')
# 编写元类
'''
1.排除掉对Model类的修改;
2.在当前类(比如User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性);
3.把表名保存到__table__中,这里简化为表名默认为类名。
'''
class ModelMetaclass(type):
def __new__(cls, name, bases, attrs):
if name=='Model':
return type.__new__(cls, name, bases, attrs)
print('Found model: %s' % name)
mappings = dict()
for k, v in attrs.items():
if isinstance(v, Field):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v
for k in mappings.keys():
attrs.pop(k)
attrs['__mappings__'] = mappings # 保存属性和列的映射关系
attrs['__table__'] = name # 假设表名和类名一致
return type.__new__(cls, name, bases, attrs)
# 基类
class Model(dict, metaclass=ModelMetaclass):
def __init__(self, **kw):
super(Model, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
def save(self):
fields = []
params = []
args = []
for k, v in self.__mappings__.items():
fields.append(v.name)
params.append('?')
args.append(getattr(self, k, None))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))