[Advanced Python] 11 - Implement a Class
元类
Ref: 使用元类
动态创建一个类
-
动态创建一个类 by type()
type()函数既可以返回一个对象的类型,又可以创建出新的类型。所以,不仅可以动态创建一个“对象”,也可以创建一个“类”。
比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义:
>>> def fn(self, name='world'): # 先预先定义好函数 ... print('Hello, %s.' % name) >>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class
使用这个动态创建的新类:Hello
>>> h = Hello() >>> h.hello() Hello, world. >>> print(type(Hello)) <class 'type'> >>> print(type(h)) <class '__main__.Hello'>
-
控制创建行为 by metaclass
先定义metaclass,就可以创建类,最后创建实例。
应用价值
-
增强传统模式
给我们自定义的MyList增加一个add方法,作为加强版的list。
# metaclass是类的模板,所以必须从`type`类型派生: class ListMetaclass(type): def __new__(cls, name, bases, attrs): attrs['add'] = lambda self, value: self.append(value) return type.__new__(cls, name, bases, attrs) # 继承了list类,在此基础上再做metaclass操作 # 它指示Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建 # 在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。 class MyList(list, metaclass=ListMetaclass): pass
>>> L = MyList()
>>> L.add(1)
>>> L.add(2)
>>> L
[1, 2]
-
实现ORM框架
要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。
1 class Field(object): 2 3 def __init__(self, name, column_type): 4 self.name = name 5 self.column_type = column_type 6 7 def __str__(self): 8 return '<%s:%s>' % (self.__class__.__name__, self.name) 9 10 11 class StringField(Field): 12 13 def __init__(self, name): 14 super(StringField, self).__init__(name, 'varchar(100)') 15 16 17 class IntegerField(Field): 18 19 def __init__(self, name): 20 super(IntegerField, self).__init__(name, 'bigint') 21 22 23 ###################### 24 # 编写类的模板 metaclass 25 ###################### 26 27 class ModelMetaclass(type): 28 29 def __new__(cls, name, bases, attrs): 30 31 if name=='Model': 32 return type.__new__(cls, name, bases, attrs) 33 34 # 加强 dict 35 print('Found model: %s' % name) 36 mappings = dict() 37 38 for k, v in attrs.items(): 39 # 利用了“父类”代理权的特性 40 if isinstance(v, Field): 41 print('Found mapping: %s ==> %s' % (k, v)) 42 mappings[k] = v 43 44 # Found mapping: email ==> <StringField:email> 45 # Found mapping: password ==> <StringField:password> 46 # Found mapping: id ==> <IntegerField:uid> 47 # Found mapping: name ==> <StringField:username> 48 49 for k in mappings.keys(): 50 attrs.pop(k) 51 52 attrs['__mappings__'] = mappings # 保存属性和列的映射关系 53 attrs['__table__'] = name # 假设表名和类名一致 54 55 return type.__new__(cls, name, bases, attrs) 56 57 58 ###################### 59 # 加强版的 dict: Model 60 ###################### 61 62 class Model(dict, metaclass=ModelMetaclass): 63 64 def __init__(self, **kw): 65 super(Model, self).__init__(**kw) 66 67 def __getattr__(self, key): 68 try: 69 return self[key] 70 except KeyError: 71 raise AttributeError(r"'Model' object has no attribute '%s'" % key) 72 73 def __setattr__(self, key, value): 74 self[key] = value 75 76 # 可以定义各种操作数据库的方法,比如save(),delete(),find(),update等等。 77 def save(self): 78 fields = [] 79 params = [] 80 args = [] 81 82 for k, v in self.__mappings__.items(): 83 fields.append(v.name) 84 params.append('?') 85 args.append(getattr(self, k, None)) 86 87 sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params)) 88 print('SQL: %s' % sql) 89 print('ARGS: %s' % str(args)) 90 91 92 93 ############################################ 94 # client 如何使用,示范如下 95 ############################################ 96 97 class User(Model): 98 # 定义类的属性到列的映射: 99 id = IntegerField('id') 100 name = StringField('username') 101 email = StringField('email') 102 password = StringField('password') 103 104 105 # 创建一个实例,保留了dict的初始化特性 106 u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd') 107 108 # 保存到数据库: 109 u.save()
装饰器类
Ref: https://python3-cookbook.readthedocs.io/zh_CN/latest/c09/p08_define_decorators_as_part_of_class.html

一、使用类中的函数
之前是直接使用函数,这里只是变为:使用类或者对象中的函数,没有本质区别。
from functools import wraps class A: # Decorator as an instance method def decorator1(self, func): @wraps(func) def wrapper(*args, **kwargs): print('Decorator 1') return func(*args, **kwargs) return wrapper # Decorator as a class method @classmethod def decorator2(cls, func): @wraps(func) def wrapper(*args, **kwargs): print('Decorator 2') return func(*args, **kwargs) return wrapper
一个是实例调用,一个是类调用。
# As an instance method a = A() @a.decorator1 def spam(): pass # As a class method @A.decorator2 def grok(): pass
给类或静态方法提供装饰器是很简单的,不过要确保装饰器在 @classmethod 或 @staticmethod 之前。
也就是@classmethod 或 @staticmethod写在靠上的位置。
二、类作为装饰器
目的:为某个函数添加新的属性/方法,例如 “被调用” 的次数。
这个代码逻辑复杂,只能当作模板去背下了。
import types from functools import wraps class Profiled: def __init__(self, func): wraps(func)(self) self.ncalls = 0 def __call__(self, *args, **kwargs): self.ncalls += 1 return self.__wrapped__(*args, **kwargs) def __get__(self, instance, cls): if instance is None: return self else: return types.MethodType(self, instance)
效果:函数成了类,或者说带有了“chain"的感觉。
@Profiled def add(x, y): return x + y class Spam: @Profiled def bar(self, x): print(self, x) # 在交互环境中的使用示例: >>> add(2, 3) 5 >>> add(4, 5) 9 >>> add.ncalls 2 >>> s = Spam() >>> s.bar(1) <__main__.Spam object at 0x10069e9d0> 1 >>> s.bar(2) <__main__.Spam object at 0x10069e9d0> 2 >>> s.bar(3) <__main__.Spam object at 0x10069e9d0> 3 >>> Spam.bar.ncalls 3
三、函数装饰某个类
先写装饰器,如下。因为增强类中的这个函数,所以修改之前,需要利用 orig_getattribute 先保存一下。
def log_getattribute(cls):
# 备份下类中原有的某个函数 orig_getattribute = cls.__getattribute__ # 定义替代品 def new_getattribute(self, name): print('getting:', name) # 在原有的函数基础上,在之前先打印日志 return orig_getattribute(self, name) # 重新赋予新的替代品 cls.__getattribute__ = new_getattribute return cls
增强这个类中的内置函数。
# Example use @log_getattribute class A: def __init__(self,x): self.x = x def spam(self): pass
下面是使用效果:
>>> a = A(42)
>>> a.x getting: x 42
>>> a.spam() getting: spam >>>
实战分析
datetime实现剖析
-
小白鼠选型
In [52]: import datetime In [53]: datetime.__file__ Out[53]: '/usr/local/anaconda3/lib/python3.7/datetime.py'
-
类初始化
自然地,也包括之后的@classmethod实现一部分构造函数,以及@property读取成员变量。
class datetime(date): """datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]]) The year, month and day arguments are required. tzinfo may be None, or an instance of a tzinfo subclass. The remaining arguments may be ints. """ # (1) 成员变量继承限制
__slots__ = date.__slots__ + time.__slots__ def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0):
# (2) 类型checker if (isinstance(year, (bytes, str)) and len(year) == 10 and 1 <= ord(year[2:3])&0x7F <= 12):
# Pickle support if isinstance(year, str): try: year = bytes(year, 'latin1') except UnicodeEncodeError: # More informative error message. raise ValueError( "Failed to encode latin1 string when unpickling " "a datetime object. " "pickle.load(data, encoding='latin1') is assumed.")
# (3) 技巧得到self self = object.__new__(cls) self.__setstate(year, month) self._hashcode = -1 return self
year, month, day = _check_date_fields(year, month, day) hour, minute, second, microsecond, fold = _check_time_fields(hour, minute, second, microsecond, fold)
_check_tzinfo_arg(tzinfo)
self = object.__new__(cls) self._year = year self._month = month self._day = day self._hour = hour self._minute = minute self._second = second self._microsecond = microsecond self._tzinfo = tzinfo self._hashcode = -1 self._fold = fold return self
新特性:__new__
Ref: python的__new__方法
最重要的 “有了返回值”,就是返回了self。
class Person(object): def __new__(cls, name, age): print '__new__ called.' return super(Person, cls).__new__(cls, name, age) def __init__(self, name, age): print '__init__ called.' self.name = name self.age = age def __str__(self): return '<Person: %s(%s)>' % (self.name, self.age)
if __name__ == '__main__': name = Person('xxx', 24) print(name)
什么时候用呢?
new方法主要是当你 继承一些不可变的class时 (比如 int, str, tuple), 提供给你一个自定义这些类的实例化过程的途径。还有就是实现自定义的metaclass。
具体我们可以用int来作为一个例子:这是一个不可变的类,但我们还想继承它的优良特性,怎么办?
class PositiveInteger(int):
def __new__(cls, value): return super(PositiveInteger, cls).__new__(cls, abs(value)) i = PositiveInteger(-3) print i
单例模式
单例模式,需要在类的”创建部分“做一些手脚,也就自然和__new__扯上关系。
因为__new__中的 object.__new__(cls),才会有了__init__中的self的赋值。
1 class Singleton(object): 2 __instance = None 3 __first_init = True 4 5 def __new__(cls, age, name): 6 if not cls.__instance: 7 cls.__instance = object.__new__(cls) 8 return cls.__instance 9 10 def __init__(self, age, name): 11 if self.__first_init: 12 self.age = age 13 self.name = name 14 Singleton.__first_init = False 15 16 17 a = Singleton(18, "xxx") 18 b = Singleton(8, "xxx") # (1) 其实是同一个对象 19 20 print(id(a)) # (2) 此处相等,也说明了是"同一个对象" 21 print(id(b)) 22 139953926130600 23 139953926130600 24 25 print(a.age) 26 print(b.age) # (3) 因为是同一个对象,即使b的__init__没做什么,b依然可以拿来去用,因为b实际上就是a的引用 27 18 28 18 29 a.age = 19 30 print(b.age) 31 19
但,慎用单例模式!
-
魔术方法的实现
Ref: Python 魔术方法指南
/* implemnent */
一个"背包"类的设计
迭代子类 _BagIterator,有点意思。
class Bag: """ constructor: 构造函数 size contains append remove iter """ def __init__(self): self._items = list() def __len__(self): return len(self._items) def __contains__(self, item): return item in self._items def add(self, item): self._items.append(item) def remove(self, item): assert item in self._items, 'item must in the bag' return self._items.remove(item) def __iter__(self): return _BagIterator(self._items) class _BagIterator: """ 注意这里实现了迭代器类 """ def __init__(self, seq): self._bag_items = seq self._cur_item = 0 def __iter__(self): return self def __next__(self): if self._cur_item < len(self._bag_items): item = self._bag_items[self._cur_item] self._cur_item += 1 return item else: raise StopIteration
b = Bag() b.add(1) b.add(2) for i in b: # for使用__iter__构建,用__next__迭代 print(i) """ # for 语句等价于 i = b.__iter__() while True: try: item = i.__next__() print(item) except StopIteration: break """
End.

浙公网安备 33010602011771号