python 面向对象专题(十三):metaclass魔术方法

python 面向对象专题(十三):metaclass魔术方法,第1张

概述0魔术方法魔术方法是python的一个特点:他们允许程序员重写变量 *** 作符号和对象的行为。调用者需要这样来重写:classFunky:def__call__(self):print("Lookatme,Iworklikeafunction!")f=Funky()f()返回值就是print的那句话了。像function一样工作。m 0 魔术方法

魔术方法是python的一个特点:他们允许程序员重写变量 *** 作符号和对象的行为。调用者需要这样来重写:

class Funky:    def __call__(self):        print("Look at me, I work like a function!")f = Funky()f()

返回值就是print的那句话了。像function一样工作。

Metaclass依赖一些魔术方法,所以多了解一些是非常有用的。

1 slots(定位,跟踪)

当你在class中定义一个魔术方法的时候,function除了__dict__中的条目之外,在整个类结构中,作为一个描述着这个class的指针一样结束。这个结构对于每一个魔术方法有一个字段。出于一些原因这些字段被称为type slots。

现在,这里有另一个特征,通过__slots__属性执行,一个拥有__slots__的class创造的实例不包含__dict__(这将使用更少的内存)。副作用是实例不能出现未在__slots__中指定的字段:如果你尝试设置一个不存在于__slots__中的字段,那么将会获得一个报错。

本文提及的单独的slots都是type slots不是__slots__。(类里的魔术方法)

class Foobar:    """     A class that only allows these attributes: "a", "b" or "c"    """    __slots__ = "a", "b", "c" foo = Foobar()foo.a = 1# foo.x = 2
2 对象属性查找

这里很容易出错,因为和python2的就样式相比有很多细小的不同。

假设我们有一个类和一个实例,并且实例是类的实例,获取(评估:原文用evaluate)实例的footbar大概相当于下面这样:

为Class.__getattribute__ (tp_getattro)调用type slot。默认会执行下面:    Class.__dict__是否有一个foobar元素是一个数据描述符?        如果有,返回Class.__dict__['foobar'].__get__(instance, Class)    instance.__dict__是否有一个foobar元素?        如果有,返回instance.__dict__['foobar']    Class.__dict__是否有一个foobar元素但并不是数据描述符?        如果有,返回Class.__dict__['foobar'].__get__(instance, klass)    Class.__dict__是否有一个foobar元素?        如果有,返回Class.__dict__['foobar']如果属性还没找到,如果有Class.__getattr__,就会调用Class.__getattr__('foobar')

如果你还不清楚,请看下图:

 

 为了避免点号'.'带来的混淆,图里用了冒号':'。

类属性查找

当你查找(评估:原文用evaluate)一些类似于class的foobar,由于class需要能够支持classmathod和staticmethod装饰器,所以和查找实例的foobar有一点不同。

假设类是Metaclass的实例,查找(评估:原文用evaluate)class的foobar相当于下面这样:

为Metaclass.__getattribute__ (tp_getattro)调用type slot。默认会执行下面:    Metaclass.__dict__是否有一个foobar元素是一个数据描述符?        如果有,返回Metaclass.__dict__['foobar'].__get__(Class, Metaclass)    Class.__dict__是否有一个foobar元素是一个描述符(任何种类)?        如果有,返回Class.__dict__['foobar'].__get__(None, Class)    Class.__dict__是否有一个foobar元素?        如果有,返回Class.__dict__['foobar']    Metaclass.__dict__是否有一个foobar元素不是一个数据描述符?        如果有,返回Metaclass.__dict__['foobar'].__get__(Class, Metaclass)    Metaclass.__dict__是否有一个foobar元素?        如果有,返回Metaclass.__dict__['foobar']如果属性还没找到,并且有Metaclass.__getattr__,就会调用Metaclass.__getattr__('foobar')

魔术方法查看

对于魔术方法来说,查找已经完成了,直接在大结构上用slots。

对象的类是否有关于魔术方法的slot(大概就像c语言中object->ob_type->tp_<魔术方法>)?如果有,就使用,如果是NulL,那么选项不被支持。在C中:object->ob_type是对象的类ob_type->tp_<魔术方法>是type slot

这看起来很简单,然而type slots在你function的外包装上到处都是,所以描述符就按照预期工作:

class Magic:    @property    def __repr__(self):        def inner():            return "It works!"        return inner print(repr(Magic()))

这是否意味着这些地方并没有遵守规则,并且用不同的方式找到了slot?很遗憾是的,继续。。。

__new__方法

__new__方法是class和Metaclass之间最容易混淆的方法之一。他有一些非常特别的约定。

当__init__只是一个初始化装置(当__init__被调用的时候,实例已经被创建了)的时候,__new__方法是一个创造者(因为他返回新的实例)。

假设有下面的class:

class Foobar:    def __new__(cls):        return super().__new__(cls)

现在你重新调用之前的部分,你将期待__new__将会在Metaclass上查找,但是很遗憾,对于这种情况他并不是很有用,所以他查找的很安静。

__prepare__方法

这个方法被第要用在class本体被执行之前并且他必须返回一个类似字典的对象,这个对象被用来作为class本体的所有代码的本地命名空间。(在类中namespace参数可以取到__prepare__的返回值)在python3的时候加入。

如果你的__prepare__返回一个对象x:

class Class(Metaclass=Meta):    a = 1    b = 2    c = 3

将对x做如下改变:

x['a'] = 1x['b'] = 2x['c'] = 3

这个x对象需要看起来像个字典。注意这个x对象最终将成为Metaclass.__new__的参数,如果他不是一个dict的实例,你需要在调用super().__new__之前转换它。

我们用__prepare__返回一个对象,这个对象只能执行__getitem__和__setitem__:

class Dictlike:    def __init__(self):        self.data = {}    def __getitem__(self, name):        print('__getitem__(%r)' % name)        return self.data[name]    def __setitem__(self, name, value):        print('__setitem__(%r, %r)' % (name, value))        self.data[name] = valueclass CustomnamespaceMeta(type):    def __prepare__(name, bases):        return Dictlike()

然而,__new__将会抱怨:

class Foobar(Metaclass=CustomnamespaceMeta):    a = 1    b = 2__getitem__('__name__')__setitem__('__module__', '__main__')__setitem__('__qualname__', 'Foobar')__setitem__('a', 1)__setitem__('b', 2)Traceback (most recent call last):  file "test.py", line 99, in <module>    class Foobar(Metaclass=CustomnamespaceMeta):TypeError: type.__new__() argument 3 must be dict, not Dictlike

我们必须把它转化成真正的字典(或者他的一个子类):

class FixedCustomnamespaceMeta(CustomnamespaceMeta):    def __new__(mcs, name, bases, namespace):        return super().__new__(mcs, name, bases, namespace.data)

接着,一切跟我期待的一样:

class Foobar(Metaclass=FixedCustomnamespaceMeta):    a = 1    b = 2__getitem__('__name__')__setitem__('__module__', '__main__')__setitem__('__qualname__', 'Foobar')__setitem__('a', 1)__setitem__('b', 2)

下面这段代码我添了点东西,上面理解了你可以不看:

class Dictlike:    def __init__(self):        self.data = {}    def __getitem__(self, name):        print('__getitem__(%r)' % name)        return self.data[name]    def __setitem__(self, name, value):        print('__setitem__(%r, %r)' % (name, value))        self.data[name] = valueclass CustomnamespaceMeta(type):    def __prepare__(name, bases):        d = Dictlike()        print(d)        print(d.__dict__)        return d class FixedCustomnamespaceMeta(CustomnamespaceMeta):    def __new__(mcs, name, bases, namespace):        print(mcs)        print(name)        print(namespace)        print(namespace.__dict__)         return super().__new__(mcs, name, bases, namespace.data)class Foobar(Metaclass=FixedCustomnamespaceMeta):    a = 1    b = 2

返回值

<__main__.Dictlike object at 0x04F53790>{'data': {}}__getitem__('__name__')__setitem__('__module__', '__main__')__setitem__('__qualname__', 'Foobar')__setitem__('a', 1)__setitem__('b', 2)<class '__main__.FixedCustomnamespaceMeta'>Foobar<__main__.Dictlike object at 0x04F53790>{'data': {'__module__': '__main__', '__qualname__': 'Foobar', 'a': 1, 'b': 2}}

返回值中可以看出namespace和__prepare__的返回值是一个东西。

把他们放在一起

先介绍一下实例是如何构建的:

如何读这个泳道图:

水平的两块泳道代表你定义function的地方。

实心的线意味着function被调用了。

从Metaclass.__call__到Class.__new__的线意味着Metaclass.__call__将调用Class.__new__。

虚线意味着有一些东西要返回。

Class.__new__返回了一个Class的实例。

Metaclass.__call__返回了一切Class.__new__返回的东西(如果他返回了一个class实例,他也要在上面调用class.__init__)。

写数字红圆圈记录了调用顺序。

创造一个class也非常的相似:

简单的写下:

Metaclass.__prepare__只是返回命名空间对象(一个类似字典的对象,像之前解释的那样)。

Metaclass.__new__返回Class对象

Metaclass.__call__返回一切Metaclass__new__ 返回的(返回一个Metaclass的实例,他同样在实例上调用了Metaclass.__init__)。

无论是Metaclass还是class,如果__new__没有返回实例,那么就不会触发__init__

所以,你会发现Metaclass允许你定制对象生命周期中几乎所有的部分。

 

总结

以上是内存溢出为你收集整理的python 面向对象专题(十三):metaclass魔术方法全部内容,希望文章能够帮你解决python 面向对象专题(十三):metaclass魔术方法所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1186709.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-03
下一篇 2022-06-03

发表评论

登录后才能评论

评论列表(0条)

保存