Python高级用法之元类metaclass

在理解元类之前,我们需要再理解一下Python中的类。在Python中,一切皆为对象,我是指所有的东西——都是对象,它们要么是类的实例,要么是元类的实例。这包括整数、字符串、函数以及类。它们全部都是对象,而且它们都是从一个类或元类创建而来。类可以实例化即创建实例对象,我们可以对实例进行赋值。同样的,类本身也是对象,即类对象。元类即是批量创建类的工厂。

  • 类也是对象

在Python中,类也是一种对象。只要使用关键字class,Python解释器在执行的时候就会创建一个类对象。我们可以对类赋值,可以增加属性,可以将它作为函数参数进行传递。例子如下:

class DemoClass(object):
    pass
def foo(obj):
    print(obj)
foo(DemoClass)

##输出:

  • 动态的创建类

因为类也是对象,我们可以使用关键字class动态的创建类

def named_class(obj_name):
    if obj_name == "Demo":
        class Demo(object):
            def data(self):
                print("demo")
        return Demo  
    elif obj_name == "Test":
        class Test(object):
            def data(self):
                print("Test")
        return Test
cls_name = named_class("Demo")

print(cls_name)  # 返回的是类,不是类的实例
print(cls_name())  ##可以通过这个类创建类实例

##输出:
.Demo'>
<__main__.named_class..Demo object at 0x1043102b0>

但这还不够动态,因为仍然需要自己编写整个类的代码。

通过一个参数调用 type 时,会生成现有类的 type 信息。通过三个参数调用 type 时,会创建一个新的类对象。调用 type 时,参数是类名、基类列表以及指定类的名称空间的字典(所有字段和方法)。

type能动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类。使用type定义类格式:

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

举个例子:

class TypedClass(object):
    test_attr = "test"
    def test(self):
        print("hello")

#也可以这样定义
def test():
    print("hello")
TypedClass = type('TypedClass', (object,), {"test": test})  # 函数作为类的属性
print(TypedClass) ##类对象
print(TypedClass.__dict__)
print(TypedClass().test)

##输出

{'test': , '__module__': '__main__', '__dict__': , '__weakref__': , '__doc__': None}
>

可以看到,在Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。

  • 元类

元类是什么

通过上文的描述,我们知道了Python中的类也是对象。元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:

MyClass = MetaClass()    #元类创建
MyObject = MyClass()     #类创建实例
实际上MyClass就是通过type()来创创建出MyClass类,它是type()类的一个实例;同时MyClass本身也是类,也可以创建出自己的实例,这里就是MyObject
函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。

元类的用处:

为了当创建类时能够自动地改变类。Python中可以使用元类控制类的创建过程,类可以看作是元类创建出来的实例。我们可以通过定义元类,开控制子类的创建,例如验证子类的定义是否合法,检查子类是否实现了某些特定的方法等。

就元类本身而言,它们其实是很简单的:

1)   拦截类的创建

2)   修改类

3)   返回修改之后的类

元类主要适用于以下场合:

  • 值的域限制
  • 隐式转换自定义类的值(您可能希望向用户隐藏编写类的所有这些复杂方面)
  • 执行不同的命名约定和样式准则(比如,“每种方法都应有一个文档字符串”)
  • 向类添加新的属性

在类定义本身中定义所有这种逻辑时使用元类,主要原因就是为了避免在整个代码库中出现重复代码。

元类的作用域:

当前类中如果有__metaclass__这个属性吗,Python会在内存中通过__metaclass__创建一个类对象。如果Python没有找到__metaclass__,它会继续在(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。现在的问题就是,你可以在__metaclass__中放置些什么代码呢?

class Foo(Bar):
    pass

在该类并定义的时候,它还没有在内存中生成,知道它被调用。Python做了如下的操作:
1)Foo中有__metaclass__这个属性吗?如果是,Python会在内存中通过__metaclass__创建一个名字为Foo的类对象(我说的是类对象,请紧跟我的思路)。
2)如果Python没有找到__metaclass__,它会继续在父类中寻找__metaclass__属性,并尝试做和前面同样的操作。
3)如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。
4)如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。

怎样创建元类:

现在的问题就是,你可以在__metaclass__中放置些什么代码呢?
答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化type的东西都可以。type是python中所有类的元类。我们定义元类时,都需要继承type(或者type的子类)。

编写自定义元类分为两个步骤:

1)编写元类类型的子类。

2)使用元类挂钩将新元类插入到类创建流程中。

我们使 type 类实现子类化,并修改魔术方法,比如 __init____new____prepare__ 以及 __call__,以便在创建类时修改类的行为。这些方法包含基类、类名、属性及其值等方面的信息。在 Python 2 中,元类挂钩是称为 __metaclass__ 的类中的静态字段。在 Python 3 中,您可以将元类指定为类的基类列表中的一个 metaclass 参数。

最简单的例子:

class CustomMetaClass(type): ##定义新元类
    def __init__(cls, cls_name, bases, attrs):
        for name, value in attrs.items():
            print("{}: {}".format(name, value))

class SomeClass(object, metaclass=CustomMetaClass):  ##将新元类插入到类创建流程
    clas_attr = "sth"

print(SomeClass().clas_attr)

##输出:
__module__: __main__
__qualname__: SomeClass
clas_attr: sth
sth

举个循序渐进的案例:

假设我们有这样一个需求:将类的所有属性全部转为大写字符

我们可以写一个辅助函数将类属性转换为大写字符

def upper_class_attr(future_class_name, future_class_parents, future_class_attrs):
    '''返回一个类对象,选择所有不以'__'开头的属性将属性都转为大写形式'''
    old_attrs = ((name, value) for name, value in future_class_attrs.itmes() if not name.startswith('__'))
    # 将它们转为大写形式
    upper_attrs = dict((name.upper(), value) for name, value in old_attrs)
    return type(future_class_name, future_class_parents, upper_attrs)

__metaclass__ = upper_class_attr ## metacalss会作用到模块中的所有类

class Test(object):
    def test_abc(self):
        pass

class Demo(object):
    def demo_abc(self):
        pass

print(hasattr(Test, "TEST_ABC"))
print(hasattr(Demo, "demo_abc"))

##输出
False
True

元类实现如下:

class UpperMetaClass(type):
    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回之的方法
    # 而__init__只是用来将传入的参数初始化给对象
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
    def __new__(cls, future_class_name, future_class_parents, future_class_attrs):
        attrs = ((name, value) for name, value in future_class_attrs.itmes())
        upper_attrs = dict((name.upper(), value) for name, value in attrs)
        return super(UpperMetaClass, cls).__new__(future_class_name, future_class_parents, upper_attrs)

__metaclass__ = UpperMetaClass

class Test(object):
    def test_abc(self):
        pass

class Demo(object):
    def demo_abc(self):
        pass

print(hasattr(Test, "TEST_ABC"))
print(hasattr(Demo, "demo_abc"))

##输出
False
True

为什么不再此处使用__init__, 而是使用了__new__。因为__new__ 实际上是创建实例的第一步,它负责返回类的新实例。而在另一方面,__init__ 则不会返回任何内容。它只负责在创建实例后对其进行初始化。请牢记一条简单的经验法则:当需要控制新实例的创建时使用 new,而在需要控制新实例的初始化时则使用 init

在元类中实现 __init__ 并不常见,因为它不是那么强大 — 在实际调用 __init__ 之前,已经构造了类。您可以将其视为具有一个类装饰器,但不同点在于:在构建子类时会运行 __init__,而不会为子类调用类装饰器。

案例:编写元类,将所有这些 camelCase 属性更改为 snake_case

def camel_to_snake(name):
    import re
    str_s = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
    return re.sub('([a-z0-9])([A-Z])', r'\1_\2', str_s).lower()

class SnakeCaseMetaClass(type):
    def __new__(snakecase_metaclass, future_cls_name, future_cls_parents, future_cls_attrs):
        snake_attrs = {}
        for name, value in future_cls_attrs.items():
            snake_attrs[camel_to_snake(name)] = value
        return super().__new__(snakecase_metaclass, future_cls_name, future_cls_parents, snake_attrs)

class DemoClass(object, metaclass=SnakeCaseMetaClass):
    camelCaseVar = 5
>>> print(DemoClass.camelCaseVar)

##输出:
AttributeError: type object 'DemoClass' has no attribute 'camelCaseVar'

>>> print(DemoClass.camelCaseVar)
##输出:
5

 

metaclass主要使用场景如下:

1)抽象基类

抽象基类是只能被继承而不会被实例化的类。使用Python中自带的抽象模块abc中ABCMeta元类控制即可创建抽象基类。

from abc import ABCMeta, abstractmethod

class Vehicle(metaclass=ABCMeta):
    @abstractmethod
    def refill_tank(self, litres):
        pass

    @abstractmethod
    def move_ahead(self):
        pass

class Car(Vehicle):
    def __init__(self, company, color, wheels):
        self.company = company
        self.color = color
        self.wheels = wheels

mini_car = Car("Tesla", "Black", 4)

##输出:
TypeError: Can't instantiate abstract class Car with abstract methods move_ahead, refill_tank

抽象基类的子类必须实现抽象方法才能实例化:

from abc import ABCMeta, abstractmethod

class Vehicle(metaclass=ABCMeta):
    @abstractmethod
    def refill_tank(self, litres):
        pass

    @abstractmethod
    def move_ahead(self):
        pass

class Car(Vehicle):
    def __init__(self, company, color, wheels):
        self.company = company
        self.color = color
        self.wheels = wheels

    def refill_tank(self, litres):
        pass

    def move_ahead(self):
        pass

mini_car = Car("Tesla", "Black", 4)
print(mini_car)

##输出如下:
<__main__.Car object at 0x103c105f8>

2)用元类来验证监控类的定义

元类可以监控类的定义,警告编程人员某些没有注意到的可能出现的问题。

对于使用元类的其他类来说,Python默认会把那些类的class语句中包含的相关内容发送给元类的__new__方法。

__new__() 方法在类创建之前被调用,通常用于通过某种方式(比如通过改变类字典的内容)修改类的定义。 而 __init__() 方法是在类被创建之后被调用,当你需要完整构建类对象的时候会很有用。

例如:

class ValidatorMetaClass(type):
    def __new__(meta, name, bases, class_dict):
        '''
        meta: 元类
        name:类名称
        bases:父类
        class_dict:类字典信息__dict__
        '''
        print((meta, name, bases, class_dict))
        return super().__new__(meta, name, bases, class_dict)

class MyClass(object, metaclass=ValidatorMetaClass):
    test = 123
    def foo(self):
        pass

MyClass().foo()

##输出如下:
(, 'MyClass', (,), {'__module__': '__main__', '__qualname__': 'MyClass', 'stuff': 123, 'foo': })

从上可以看到:元类能获知经由它创建的类的名称,其所继承的父类,以及定义在class语句提中的全部类属性。

为了在定义某个类的时候,确保该类的所有参数都有效,我们可以把相关的验证逻辑添加在Meta.__new__方法中。

注意:元类中所编写的验证逻辑,针对的是该基类的子类,而不是基类本身

例子如下:

class ValidatePolygonMetaclass(type):
    def __new__(meta, name, bases, attr):
        if bases != (object,):
            if attr["sides"] < 3:
                raise ValueError("Polygon sides must >3, the input is: {}".format(attr["sides"]))
        return super().__new__(meta, name, bases, attr)

class Poylygon(object, metaclass=ValidatePolygonMetaclass):
    sides = None ##注意:元类中所编写的验证逻辑,针对的是该基类的子类,而不是基类本身
    def interior_angles(self):
        return (self.sides - 2)*180

class Triangle(Poylygon):
    sides = 2

data = Triangle.sides
print(data)


##输出如下:
Traceback (most recent call last):
  File "metaclass.py", line 207, in 
    class Triangle(Poylygon):
  File "metaclass.py", line 199, in __new__
    raise ValueError("Polygon sides must >3, the input is: {}".format(attr["sides"]))
ValueError: Polygon sides must >3, the input is: 2

 

注意:

元类中所编写的验证逻辑,针对的都是该基类中的子类,而不是基类自身。并且,一旦某个元类被指定给了某个类,那么就会被继承到所有子类中去。 因此,一个框架的构建者就能在大型的继承体系中通过给一个顶级父类指定一个元类去捕获所有下面子类的定义。

 

3)在库和框架中,通过元类创建API

案例1:

我们通过创建一个类似Django中的ORM来熟悉一下元类的使用,通常元类用来创建API是非常好的选择,使用元类的编写很复杂但使用者可以非常简洁的调用API。

##ORM框架ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。
##要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。
##需求
# class User(Model):
#     # 定义类的属性到列的映射:
#     id = IntegerField('id')
#     name = StringField('username')
#     email = StringField('email')
#     password = StringField('password')
#
# # 创建一个实例:
# u = User(id=12345, name='Michael', email='[email protected]', password='my-pwd')
# # 保存到数据库:
# u.save()

class Field(object):
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type

    def __str__(self):
        return '<{}: {}>'.format(self.__class__.__name__, self.name)

class IntegerField(Field):
    def __init__(self, name):
        print("curent field: {}".format("IntegerField"))
        super(IntegerField, self).__init__(name, 'bigint')

class StringField(Field):
    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')

class ModelMetaclass(type):
    '''
    它做了以下几件事:
    创建一个新的字典mapping
    将每一个类的属性,通过.items()遍历其键值对。如果值是Field类,则打印键值,并将这一对键值绑定到mapping字典上。
    将刚刚传入值为Field类的属性删除。
    创建一个专门的__mappings__属性,保存字典mapping。
    创建一个专门的__table__属性,保存传入的类的名称。
    '''
    def __new__(cls, name, bases, attrs):
        if name == "Model":
            return super().__new__(cls, name, bases, attrs)
        print("model name is: {}".format(name))
        mapping = {}
        for k, v in attrs.items():
            if isinstance(v, Field):
                print("Found map : {} ==> {}".format(k, v))
                mapping[k] = v
        for k in mapping.keys():
            attrs.pop(k)

        attrs['__mappings__'] = mapping
        attrs['__table__'] = name
        return super().__new__(cls, name, bases, attrs)

class Model(dict, metaclass=ModelMetaclass):
    def __init__(self, **kwargs):
        super(Model, self).__init__(**kwargs)

    def __getattr__(self, key):
        try:
            print("get attr {} with value {}".format(key, self[key]))
            return self[key]
        except KeyError:
            raise AttributeError("Mode has no attribute {}".format(key))

    def __setattr__(self, key, value):
        print("set attr {} --> {}".format(key, value))
        self[key] = value

    def save(self):
        fields = []
        args = []
        for k, v in self.__mappings__.items():
            fields.append(v.name)
            args.append(getattr(self, k, None))
        sql = 'insert into {} ({}) vaules ({})'.format(self.__table__, ','.join(fields), ','.join([str(i) for i in args]))
        print('SQL: {}'.format(sql))
        print('ARGS: {}'.format(args))

class User(Model):
    '''
    因为IntergerField(‘id’)是Field的子类的实例,自动触发元类的__new__,所以将IntergerField(‘id’)存入__mappings__并删除这个键值对。
    '''
    id = IntegerField('id') ##id = IntegerField(‘id’)就会自动解析为:Model.__setattr__(self, ‘id’, IntegerField(‘id’))
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

# print(User(test='123'))
u = User(id=123, name='bruce', email='[email protected]', password='123ui')
u.save()

当用户定义一个class User(Model)时,Python解释器首先在当前类User的定义中查找metaclass,如果没有找到,就继续在父类Model中查找metaclass,找到了,就使用Model中定义的metaclassModelMetaclass来创建User类,也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。

ModelMetaclass中,一共做了几件事情:

  1. 排除掉对Model类的修改;

  2. 在当前类(比如User)中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个__mappings__的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误(实例的属性会遮盖类的同名属性);

  3. 把表名保存到__table__中,这里简化为表名默认为类名。

Model类中,就可以定义各种操作数据库的方法,比如save()delete()find()update等等。

我们实现了save()方法,把一个实例保存到数据库中。因为有表名,属性到字段的映射和属性值的集合,就可以构造出INSERT语句。

输出如下:

curent field: IntegerField
model name is: User
Found map : id ==> 
Found map : name ==> 
Found map : email ==> 
Found map : password ==> 
get attr id with value 123
get attr name with value bruce
get attr email with value [email protected]
get attr password with value 123ui
SQL: insert into User (id,username,email,password) vaules (123,bruce,[email protected],123ui)
ARGS: [123, 'bruce', '[email protected]', '123ui']

 

4)使用元类注册子类

一般来说,无论何时需要维护存储类特征的某种数据结构,都可以使用元类。

案例1:

我们以某个服务器上的多个文件处理程序为例。想法就是能够根据文件格式快速找到正确的处理程序类。我们将创建处理程序字典,让 CustomMetaclass 注册在代码中遇到的不同处理程序

handers = {}  ##handers字典用,文件格式后缀:处理该文件格式的类

class CustomMetaClass(type):
    def __new__(meta, new_cls_name, bases, attrs):
        print(new_cls_name)
        cls = super().__new__(meta, new_cls_name, bases, attrs) ##构造类对象
        for ext in attrs["formats"]: ##将类对象属性formats中的值作为key,类对象作为value注册到handers字典。
            handers[ext] = cls
        return cls

class Hander(metaclass=CustomMetaClass):
    formats = []

class ImageHander(Hander):
    formats = ["png", "jpeg"]

class AudioHandler(Hander):
    formats = ["mp4", "avi"]

print(handers)
print(handers["png"].formats)

##输出:
Hander
ImageHander
AudioHandler
{'png': , 'jpeg': , 'mp4': , 'avi': }
['png', 'jpeg']

现在根据文件格式,我们能轻易找到需要使用哪个类处理文件。

案例2:

Object序列化为json,json反序列化为Object

序列化可以理解为:把python的对象object编码转换为json格式的字符串(用于存储或传输),反序列化可以理解为:把json格式字符串解码为python数据对象(程序接收处理)

方法1: 最简单实现

import json
class Serializable(object):
    def __init__(self, *args):
        self.args = args

    def serialize(self):
        return json.dumps({'args': self.args})

class Point2D(Serializable):
    def __init__(self, x, y):
        super().__init__(x, y)
        self.x = x
        self.y = y

    def __repr__(self):
        return 'Point2D({}, {})'.format(self.x, self.y)

class Deserializable(Serializable):

    @classmethod
    def deserialize(cls, json_data):
        params = json.loads(json_data)
        return cls(*params['args'])

class BetterPoint2D(Deserializable):
    def __init__(self, x, y):
        super().__init__(x, y)
        self.x = x
        self.y = y

point = BetterPoint2D(5,3)
print("Before object: {}".format(point))
data = point.serialize()
print("Serialized: {}".format(data))
after = BetterPoint2D.deserialize(data)
print("After: {}".format(after))

这种实现方法的问题是,每次反序列化时必须提前知道序列化所使用的类。当类较多时,反序列化需要显示的获取每次序列化使用的类,一旦类名发生变化,或者增加了新类,需要通知到反序列化方,代码可维护性很低。

方法2: 使用字典保存每一个需要被反序列化的类

class BetterSerializable(object):
    def __init__(self, *args):
        self.args = args

    def serialize(self):
        return json.dumps({'class': self.__class__.__name__, "args": self.args})

registery = {}
def register_class(target_class):
    registery[target_class.__name__] = target_class

def deserialize(json_data):
    params = json.loads(json_data)
    name = params["class"]
    target_class = registery[name]
    return target_class(*params["args"])

class EvenBetterPoint2D(BetterSerializable):
    def __init__(self, x, y):
        super().__init__(x, y)
        self.x = x
        self.y = y

register_class(EvenBetterPoint2D) ##每一个类都需要显示注册一次

point = EvenBetterPoint2D(5,3)
print("Before object: {}".format(point))
data = point.serialize()
print("Serialized: {}".format(data))
after = deserialize(data)
print("After: {}".format(after))

全局字典保存了每一个类,但是每次创建新类时都需要显示注册一次,代码还是不够简洁。一旦新类忘记注册,将会引起序列化失败。有没有什么办法能在我们创建类时自动注册?

方法3: 使用元类注册子类

元类是创建类的模版,我们只要在元类中保证每次通过元类创建的类都自动注册到字典中,即可完美解决此类问题。元类中的__new__方法构建新类之后,将类信息注册到registery字典即可。

registery = {}
def register_class(target_class):  ##将类对象保存在字典中,类名:类对象
    registery[target_class.__name__] = target_class

def deserialize(json_data):  ##根据json_data中的的类名name,返回类对象的实例对象
    params = json.loads(json_data)
    name = params["class"]
    target_class = registery[name]
    return target_class(*params["args"])

class BetterSerializable(object):
    def __init__(self, *args):
        self.args = args

    def serialize(self):
        return json.dumps({'class': self.__class__.__name__, "args": self.args})

class RegisterMetaclass(type):
    def __new__(meta, cls_name, bases, attr):
        cls = super().__new__(meta, cls_name, bases, attr)
        register_class(cls)
        return cls

class RegisterSerializable(BetterSerializable, metaclass=RegisterMetaclass):
    pass

class Vector3D(RegisterSerializable):
    def __init__(self, x, y, z):
        super().__init__(x, y, z)
        self.x, self.y, self.z = x, y, z

v3 = Vector3D(10, -7, 3)
print("Before object: {}".format(v3))
data = v3.serialize()
print("Serialized: {}".format(data))
after = deserialize(data)
print("After: {}".format(after))

##输出如下:
Before object: <__main__.Vector3D object at 0x104d103c8>
Serialized: {"class": "Vector3D", "args": [10, -7, 3]}
After: <__main__.Vector3D object at 0x104d10710>

5)使用元类来注解/修该子类的属性

元类还有一个更广泛的使用场景,即在类刚定义好但是还没实例化时,提前修改/注解子类的属性。这种写法通常和描述符一起配合使用。

案例:要定义新的类,表示数据库中的一行数据。同时希望在该类的相关属性和数据表的列名之间建立对应关系。

先定一个描述符,将类属性和数据表的列名关联起来:

class Field(object):
    def __init__(self, name):
        self.name = name
        self.internal_name = '_' + self.name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return getattr(instance, self.internal_name, '')

    def __set__(self, instance, value):
        return setattr(instance, self.internal_name, value)

再定义Customer类,表示数据库中的一行数据

class Customer(object):
    ##类属性
    first_name = Field("first_name")
    last_name = Field("last_name")
    prefix = Field("prefix")
    suffix = Field("suffix")

给数据行赋值

foo = Customer()
print("Before: ", repr(foo.first_name), foo.__dict__)
foo.first_name = "bruce"
print("After: ", repr(foo.first_name), foo.__dict__)

##输出如下:
Before:  '' {}
After:  'bruce' {'_first_name': 'bruce'}

以上用法,在Customer类中,我们需要将列名"first_name"赋值给Field类,然后再将Field实例赋值给Customer.first_name。重复写入列名first_name,显得重复。

为了消除这种重复,我们可以使用元类改写。使用元类,相当于在class中设置了挂钩,一旦创建新类会立即调用元类,为FieldDescriptor描述符类自动设置属性FieldDescriptor.name和FieldDescriptor.internal_name。这样我们就不必在需要在实例化FieldDescriptor再次传入列的名称。

元类实现代码如下:

class FieldMetaClass(type):
    def __new__(meta, cls_name, bases, attrs):
        for key, value in attrs.items():
            if isinstance(value, FieldDescriptor):
                value.name = key
                value.internal_name = "_" + key
        cls = super().__new__(meta, cls_name, bases, attrs)
        return cls

class FieldDescriptor(object):
    def __init__(self):
        self.name = None 
        self.internal_name = None

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return getattr(instance, self.internal_name, '')

    def __set__(self, instance, value):
        return setattr(instance, self.internal_name, value)

class DatabaseRow(metaclass=FieldMetaClass):
    pass

class BetterCustomer(DatabaseRow):
    first_name = FieldDescriptor()
    last_name = FieldDescriptor()
    prefix = FieldDescriptor()
    suffix = FieldDescriptor()

foo = BetterCustomer()
print("Before: ", repr(foo.first_name), foo.__dict__)
foo.first_name = "bruce"
print("After: ", repr(foo.first_name), foo.__dict__)

 

参考文献:

http://blog.jobbole.com/21351/

https://www.ibm.com/developerworks/cn/analytics/library/ba-metaprogramming-python/index.html

你可能感兴趣的:(Python)