(学习笔记)初识metaclass暨py2和py3中的不同写法

1.前言

最近在刷codewars练习Python的时候碰到一道题:

The builder of things
For this kata you will be using some meta-programming magic to create a new Thing 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能有什么用处,先来个感性的认识:

  1. 你可以自由的、动态的修改/增加/删除 类的或者实例中的方法或者属性
  2. 批量的对某些方法使用decorator,而不需要每次都在方法的上面加入@decorator_func
  3. 当引入第三方库的时候,如果该库某些类需要patch的时候可以用metaclass
  4. 可以用于序列化(参见yaml这个库的实现,我没怎么仔细看)
  5. 提供接口注册,接口格式检查等
  6. 自动委托(auto delegate)
  7. 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
当然,愿意的话,还可以搞出metametaclassmetametametaclass...
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的内容可以是变量(类属性),也可以是函数(类方法)。
所以在创建类的过程,我们可以在这个函数里面修改namebasesattrs的值来自由的达到我们的功能。这里常用的配合方法是getattrsetattr(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.png

如图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()

这就得到预期的运行结果了:


2.png

不过我的SublimeLinter-pyflasks高亮提示我的代码不符合规范,应该是它的问题:

3.png

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))做对比

metaclass_3_部分修改代码.png

结果:
A与PatchA对比.png

从上图可见,类A和类PatchA都具有apatcha_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 called clsrather than self, 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是定义classclass,两者对比时,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中定义的methodclass即可以作为成员方法来调用,可以通过实例方法来调用。

5.参考

1)浅析python的metaclass — 何似王
2)Python Metaclasses in Review Board — Mike Conley's Blog
3)Metaclass — wiki
4)Metaclass Programming In Python

你可能感兴趣的:((学习笔记)初识metaclass暨py2和py3中的不同写法)