本文主要介绍Python的MetaClass的用法。
MetaClass也是一个class,这个class继承自type
,它用于生产新的class对象。定义class的时候,就会执行到MetaClass的__new__()
代码。
从这里出发,我们需要一个新的class的时候,就可以使用MetaClass定义它的行为。本文介绍MetaClass的三种用法:
- 定义新的class的时候,执行一些检查操作
- 定义新的class的时候,将新class进行注册动作
- 定义新的class的时候,自动修改class的属性
MetaClass可以读取和修改class的属性。
1 2 3 4 5 6 7 8 9 10 11 |
class Meta(type): def __new__(meta, name, bases, class_dict): print((meta, name, bases, class_dict)) return type.__new__(meta, name, bases, class_dict) class MyClass(object, metaclass=Meta): stuff = 123 def foo(self): pass # (<class '__main__.Meta'>, 'MyClass', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'MyClass', 'stuff': 123, 'foo': <function MyClass.foo at 0x10af271e0>})Python |
Python2和Python3的写法有所不同,上面是Python3的写法,Python2的写法如下。
1 2 3 4 5 6 7 8 9 10 |
class Meta(type): def __new__(meta, name, bases, class_dict): print((meta, name, bases, class_dict)) return type.__new__(meta, name, bases, class_dict) class MyClass(object): __metaclass__ = Meta stuff = 123 def foo(self): pass |
用MetaClass验证
定义一个新的class相当于调用type
创建一个新的class
对象,所以覆盖__new__()
方法可以实现一些验证的动作。
下面的代码定义了一个多边形的MetaClass,在定义新的class的时候,验证边数是否小于3,如果小于3,这不科学。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# -*- coding: utf-8 -*- class ValidatePolygon(type): def __new__(meta, name, bases, class_dict): # Don’t validate the abstract Polygon class if bases != (object,): if class_dict['sides'] < 3: raise ValueError(‘Polygons need 3+ sides’) return type.__new__(meta, name, bases, class_dict) class Polygon(object, metaclass=ValidatePolygon): sides = None # Specified by subclasses @classmethod def interior_angles(cls): return (cls.sides - 2) * 180 class Triangle(Polygon): sides = 3 |
使用MetaClass对class进行注册
下面的代码,在每次定义一个新的class的时候,就将class注册到一个字典,维护对应的行为。而不必每次都手动进行维护。
1 2 3 4 5 6 7 8 9 |
class Vector3D(RegisteredSerializable): 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: ’, v3) data = v3.serialize() print(‘Serialized:’, data) print(‘After: ’, deserialize(data)) |
修改class的属性
假设我们想要实现一个Python对象到数据库字段的对应(也就是ORM),我们可以使用前面博客提到的Descriptor,如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class Field(object): def __init__(self, name): self.name = name self.internal_name = ‘_’ + self.name def __get__(self, instance, instance_type): if instance is None: return self return getattr(instance, self.internal_name, ”) def __set__(self, instance, value): setattr(instance, self.internal_name, value) class Customer(object): # Class attributes first_name = Field(‘first_name’) last_name = Field(‘last_name’) prefix = Field(‘prefix’) suffix = Field(‘suffix’) |
但是这里存在的问题是,first_name
已经写了一次了,还要在创建Field
的时候再写一次,不是有些多余吗?Field
应该自动根据引用它的变量名自动命名。这可以通过MetaClass来做到。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Meta(type): def __new__(meta, name, bases, class_dict): for key, value in class_dict.items(): if isinstance(value, Field): value.name = key value.internal_name = ‘_’ + key cls = type.__new__(meta, name, bases, class_dict) return cls # Field不需要在接受参数创建实例了 class Field(object): def __init__(self): # These will be assigned by the metaclass. self.name = None self.internal_name = None # 这里由MetaClass代劳 class BetterCustomer(meta=Meta): first_name = Field() last_name = Field() prefix = Field() suffix = Field() |
综上,MetaClass是非常实用的,可以帮我们减少很多需要重复复制粘贴的代码。
参考:Effective Python 59 SPECIFIC WAYS TO WRITE BETTER PYTHON by Brett Slatkin, Item 33,34,35