在学习python3过程中发现,metaclass的用处比较广泛, 更多可以查看 PEP 3115. 在学习过程中需要做一些序列化操作(比如csv转换), 查阅资料发现, 利用元类可以很容易自动记录一个类中属性和方法定义的顺序,然后进行序列化的操作.
首先,先介绍一下metaclass的新语法规则:
1. 有一个prepare函数:
def prepare_class(name, *bases, metaclass=None, **kwargs): if metaclass is None: metaclass = compute_default_metaclass(bases) prepare = getattr(metaclass, '__prepare__', None) if prepare is not None: return prepare(name, bases, **kwargs) else: return dict()
参数解析:
'name' -- 将要创建的类的名称;
'bases' -- 基类列表;
由于它通常是在metaclass实例创建前被调用,所以__prepare__() 一般都被实现为一个类函数. 最后返回的dict()是一个定制的字典对象.http://write.blog.csdn.net/postedit/54095727
2. __new__(cls, name, bases, clsdict)
先了解一下在python里__new__(*args, **kwargs)的知识点. __new__至少有一个cls参数, 代表要实例化的类, 有python编译器自动提供. 此外, 必须要有返回值, 返回实例化出来的实例. 为了更好理解并防止以后学习的混淆,在此简单的分析一下__new__ 和 __init__的区别.
__init__ 有一个self参数, 代表__new__返回的实例, 不需要返回值. 总之, __init__ 是在 __new__正确返回当前类实例的情况下, 才调用__init__() 的.下面用一个最简的例子来探索一下其中的原理.
In [1]: class A(object):
...: def __init__(self):
...: print('init...')
...: def __new__(cls, *args, **kwargs):
...: print(cls)
...: return object.__new__(cls, *args, **kwargs)
In [2]: A()
<class '__main__.A'>
init...
Out[4]: <__main__.A at 0x7fd08daf6f28>
这是正确调用的;
In [3]: class C(object):
...: pass
...:
In [4]: class A(C):
...: def __init__(self):
...: print('init...')
...: def __new__(cls, *args, **kwargs):
...: print(cls)
...: return object.__new__(C, *args, **kwargs)
...:
In [20]: A()
<class '__main__.A'>
Out[20]: <__main__.C at 0x7fd08d28f5f8>
发现__init__并没有被调用, 这是因为__new__()提供的是C而不是代表当前类实例的cls, 所以__new__没有返回当前类实例, 而是返回C的实例.
说了这么多,接下来回到正题.继续讨论metaclass里__new__ 的用法.我援引PEP 3115中的例子来说明这个问题. 用metaclass创建包含所有类成员的列表, 按照声明的顺序排列.
# 定制字典
# The custom dictionaryclass member_table(dict):
def __init__(self):
self.member_names = []
def __setitem__(self, key, value):
# if the key is not already defined, add to the
# list of keys.
if key not in self:
self.member_names.append(key)
# Call superclass
dict.__setitem__(self, key, value)
# The metaclass
class OrderedClass(type):
# The prepare function
@classmethod
def __prepare__(metacls, name, bases): # No keywords in this case
return member_table()
# The metaclass invocation
def __new__(cls, name, bases, classdict):
# Note that we replace the classdict with a regular
# dict before passing it to the superclass, so that we
# don't continue to record member names after the class
# has been created.
result = type.__new__(cls, name, bases, dict(classdict))
result.member_names = classdict.member_names
return result
class MyClass(metaclass=OrderedClass):
# method1 goes in array element 0
def method1(self):
pass
# method2 goes in array element 1
def method2(self):
pass
按照这个模式, 我们很容易可以写出符合我们要求的python类, 参考python3-cookbook.
from collections import OrderedDict # A set of descriptors for various types class Typed: _expected_type = type(None) def __init__(self, name=None): self._name = name def __set__(self, instance, value): if not isinstance(value, self._expected_type): raise TypeError('Expected ' + str(self._expected_type)) instance.__dict__[self._name] = value class Integer(Typed): _expected_type = int class Float(Typed): _expected_type = float class String(Typed): _expected_type = str # Metaclass that uses an OrderedDict for class body class OrderedMeta(type): def __new__(cls, clsname, bases, clsdict): d = dict(clsdict) order = [] for name, value in clsdict.items(): if isinstance(value, Typed): value._name = name order.append(name) d['_order'] = order return type.__new__(cls, clsname, bases, d) @classmethod def __prepare__(cls, clsname, bases): return OrderedDict()使用:
class Structure(metaclass=OrderedMeta): def as_csv(self): return ','.join(str(getattr(self,name)) for name in self._order) # Example use class Stock(Structure): name = String() shares = Integer() price = Float() def __init__(self, name, shares, price): self.name = name self.shares = shares self.price = price>>> s = Stock('xz', 100, 20.3)
>>> s.name
'xz'
>>> s.as_csv()
'xz,100,20.3'
>>>
这就得到一个能进行csv数据转换的代码了.