1.前言
最近在刷codewars
练习Python的时候碰到一道题:
The builder of things
For this kata you will be using some meta-programming magic to create a newThing
object. This object will allow you to define things in a descriptive sentence like format.
This challenge attempts to build on itself in an increasingly complex manner.
后面的代码示例就不贴了,有心者可以搜到题目,当然也有答案。
但对于我这种在这之前只看过metaclass
这个单词或者说没有了解过的非高级开发者而言,抄答案完成题目并非我所求,知其所以然才是追求所在。
2.metaclass
一番搜索找到了一篇很不错的博客——浅析python的metaclass,通过这篇博客的分享,虽然我还没有完全掌握metaclass
,但至少对它是什么、能做什么、怎么做这三点有了一定的了解。当然在阅读这篇博客的同时,也将它所参考的博客和科普文章一并做了些阅读,看完后发现这篇博客已经归纳总结的十分不错:
二 metaclass的作用是什么?(感性认识)
metaclass能有什么用处,先来个感性的认识:
- 你可以自由的、动态的修改/增加/删除 类的或者实例中的方法或者属性
- 批量的对某些方法使用decorator,而不需要每次都在方法的上面加入@decorator_func
- 当引入第三方库的时候,如果该库某些类需要patch的时候可以用metaclass
- 可以用于序列化(参见yaml这个库的实现,我没怎么仔细看)
- 提供接口注册,接口格式检查等
- 自动委托(auto delegate)
- more...
至于metaclass
是什么?wiki
的定义如下
In object-oriented programming, a metaclass is a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances.
翻译一下就是:
metaclass的实例化结果是类,而class实例化的结果是instance
而在Python中, 如果你要用类来实现metaclass
的话,该类需要继承于type
当然,愿意的话,还可以搞出metametaclass
、metametametaclass
...
但type
类依然是辈分最高的那个类,不过这也仅限于python 2.2
之后,在这个版本之前type
类也还没出生
type -> metaclass -> class -> instance
metaclass
的基本原理:
metaclass的原理其实是这样的:当定义好类之后,创建类的时候其实是调用了type的
__new__
方法为这个类分配内存空间,创建好了之后再调用type的__init__
方法初始化(做一些赋值等)。所以metaclass的所有magic其实就在于这个__new__
方法里面了。
说说这个方法:__new__(cls, name, bases, attrs)
-cls
: 将要创建的类,类似与self,但是self指向的是instance,而这里cls指向的是class
-name
: 类的名字,也就是我们通常用类名.__name__
获取的。
-bases
: 基类
-attrs
: 属性的dict。dict的内容可以是变量(类属性),也可以是函数(类方法)。
所以在创建类的过程,我们可以在这个函数里面修改name
,bases
,attrs
的值来自由的达到我们的功能。这里常用的配合方法是getattr
和setattr
(just an advice)
博客浅析python的metaclass 在后面的补充部分说到的在__init__
和__new__
中进行修改,我的理解是两者都可以,但在__new__
中修改会比在__init__
更有效,只是平常大部分人都只用到了__init__
,而且用__init__
也可以达到目标。
The magic methods
.__new__()
and.__init__()
are special, but in conceptually the same way they are for any other class. The.__init__()
method lets you configure the created object, the.__new__()
method lets you customize its allocation. The latter, of course, is not widely used, but exists for every Python 2.2 new-style class (usually inherited but not overridden).
其余更多信息,可以自行阅读原博客甚至是wiki
原文。
3.在py2与py3上的实现差异
博客浅析python的metaclass 中除了概念、知识点归纳陈述,针对上面所列出的metaclass
的用途还给出了代码实例,然而我在尝试运行时发现报错。
以2. 批量的对某些方法使用decorator,而不需要每次都在方法的上面加入@decorator_func
的示例为例,在修改了print
的差异后,使用py3和py2得到不一样的结果:
如图1所示,在使用py3执行时,使用
metaclass
添加的login_required
并未生效
这是为什么呢?
终于在Metaclass 的wiki
定义中找到了答案:
Now the class Car can be rewritten to use this metaclass. In Python 3 this done by providing a "keyword argument" metaclass to the class definition:
class Car(object, metaclass=AttributeInitType):
@property
def description(self):
""" Return a description of this car. """
return " ".join(str(value) for value in self.__dict__.values())
而示例中的代码写法是
class Operation(object):
__metaclass__ = LoginDecorator
def delete(self, x):
print 'deleted %s' % str(x)
这就是py2与py3中实现metaclass
的差异所在了
修改后代码如下:
# metalcalss_2.py
from types import FunctionType
def login_required(func):
print('login check logic here')
return func
class LoginDecorator(type):
def __new__(cls, name, bases, dct):
# for name, value in dct.iteritems(): # py2
for name, value in dct.items(): # py3
if name not in ('__metaclass__', '__init__', '__module__') and\
type(value) == FunctionType:
value = login_required(value)
dct[name] = value
return type.__new__(cls, name, bases, dct)
class Operation(object, metaclass=LoginDecorator): # py3
# class Operation(object): # py2
# __metaclass__ = LoginDecorator
def delete(self, x):
print('deleted %s' % str(x))
def main():
op = Operation()
op.delete('test')
if __name__ == '__main__':
main()
这就得到预期的运行结果了:
不过我的SublimeLinter-pyflasks
高亮提示我的代码不符合规范,应该是它的问题:
4.补充一些个人阅读理解
1)在执行3. 当引入第三方库的时候,如果该库某些类需要patch的时候可以用metaclass
的示例代码时,发现:利用metaclass
对一个类进行Monkeypatch操作后,被patch的原始类也得到了新增的method,用示例代码来说,就是使用metaclass
对类A
打补丁增加了patcha_method
方法得到PatchA
之后,发现类A
也拥有了patcha_method
这个成员方法
先改下代码,原示例中是print(pa)
和print(dir(PatchA))
,改为print(dir(PatchA))
和print(dir(A))
做对比
结果:
从上图可见,类
A
和类PatchA
都具有a
和patcha_method
两个成员方法。
2)在阅读完Metaclass Programming In Python中下面这一段时有些困惑,于是重读了几遍算是明白了,不过后面的用途案例还是看着有些迷糊,不如前面的中文博客那么直白:
There is one feature of
type
descendents to be careful about; it catches everyone who first plays with metaclasses. The first argument to methods is conventionally calledcls
rather thanself
, because the methods operate on the produced class, not the metaclass. Actually, there is nothing special about this, all methods attach to their instances, and the instance of a metaclass is a class. A non-special name makes this more obvious:#### Attaching class methods to produced classes (python)
>>> class Printable(type): ... def whoami(cls): print "I am a", cls.__name__ ... >>> Foo = Printable('Foo',(),{}) >>> Foo.whoami() I am a Foo >>> Printable.whoami() Traceback (most recent call last): TypeError: unbound method whoami() [...]
i) 虽然上面引用中加粗的那句话我不知道该怎么翻译才算恰当(继承type
的类被认为是最初的metaclass
?),但后面的陈述大致理解了,然后对比示例代码有了自己的理解:在metaclass
中定义methods
要用cls
而不是self
,这些methods
可以被produced class
调用,不能被metaclass
调用。
一方面印证了metaclass
的定义:metaclass
是定义class
的class
,两者对比时,metaclass
是虚的,class
是实(produced)的
另一方面的知识点,cls
定义类的成员方法,self
定义类的实例方法。
All this surpisingly non-remarkable machinery comes with some syntax sugar that both makes working with metaclasses easier, and confuses new users. There are several elements to the extra syntax. The resolution order of these new variations is tricky though. Classes can inherit metaclasses from their ancestors--notice that this is not the same thing as having metaclasses as ancestors (another common confusion). For old-style classes, defining a global
_metaclass_
variable can force a custom metaclass to be used. But most of the time, and the safest approach, is to set a_metaclass_
class attribute for a class that wants to be created via a custom metaclass. You must set the variable in the class definition itself since the metaclass is not used is the attribute is set later (after the class object has already been created). E.g.:#### Setting metaclass with class attribute (python)
>>> class Bar: ... __metaclass__ = Printable ... def foomethod(self): print 'foo' ... >>> Bar.whoami() I am a Bar >>> Bar().foomethod() foo
ii)加粗部分我的理解是:一个class
可以从它的父类那里继承metaclass
属性,这并不等同于拥有一个metaclass
父类,就是可以隔代传。
我觉得如果不用ancestors
这个具有祖先
意义的单词,或许就不会造成新的困惑了。
而示例代码中则显示在metaclass
中定义的method
,class
即可以作为成员方法来调用,可以通过实例方法来调用。
5.参考
1)浅析python的metaclass — 何似王
2)Python Metaclasses in Review Board — Mike Conley's Blog
3)Metaclass — wiki
4)Metaclass Programming In Python