[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()
ORM 示范

 

 

 

装饰器类


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
Singleton实现

但,慎用单例模式!

对程序架构而言,单例意味着没有隐藏,插入到程序的任何组件都可以随时修改它,这客观上违背了面向对象的最小公开法则,程序健壮性安全性骤降。
对程序扩展性而言,单例意味着很难被继承重写!  当你在一个单例中尝试覆盖它的某些功能时,编译器会报错,这时候你哭去吧。或者,你会奇怪的发现,咦?怎么还是基类的功能!
对程序逻辑而言,单例,顾名思义,仅有其一,但你的程序在发展着,你确定只有一个实例即可?某天突然发现,业务变了,需要扩展,那些数不清的用单例调用的代码怎么处理?尤其是已经发布到顾客手中的代码?
对效率而言,每次访问它的时候,都会查看是否被创建(多增加了一次判断),这对于一些需要高性能的程序是非常不合适的。
对多线程而言,在多线程环境下,实现单例是需要技巧的。否则,单例不单!
对对象生命周期而言,单例只有自己持有真正的引用,,如何销毁何时销毁都是问题,可能还会造成指针悬挂。
对开发者而言,一个类不能new! 这还会带来更多的疑问和混淆。

 

  • 魔术方法的实现

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.

posted @ 2019-08-19 18:01  郝壹贰叁  阅读(353)  评论(0)    收藏  举报