加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

PyTips 0x18 - 类与元类的深度挖掘 I

发布时间:2021-03-18 11:47:20 所属栏目:大数据 来源:网络整理
导读:上一篇介绍了 Python 枚举类型的标准库,除了考虑到其实用性,还有一个重要的原因是其实现过程是一个非常好的学习、理解 Python 类与元类的例子。因此接下来两篇就以此为例,深入挖掘 Python 中类与元类背后的机制。 翻开任何一本 Python 教程,你一定可以在

可以用来生成新对象的类,包括内置的 intstr 以及自己定义的 A 等;

  • 由类生成的实例对象,包括内置类型的数字、字符串以及自己定义的类型为 __main__.Aa

  • 单纯从概念上理解这两种对象没有任何问题,但是这里要讨论的是在实践中不得不考虑的一些细节性问题:

    1. 需要一些方便的机制来实现面向对象编程中的继承、重载等特性;

    2. 需要一些固定的流程让我们可以在生成实例化对象的过程中执行一些特定的操作;

    这两个问题主要关于类的一些特殊的操作,也就是这一篇后面的主要内容。如果再回顾一下开头提到的两句话,你可能会想到,既然类本身也是对象,那它们又是怎样生成的?这就是后一篇将主要讨论的问题:用于生成类对象的类,即元类(Metaclass)。

    super,mro()

    0x00 Python 之禅中提到的最后一条,命名空间(namespace)是个绝妙的理念,类或对象在 Python 中就承担了一部分命名空间的作用。比如说某些特定的方法或属性只有特定类型的对象才有,不同类型对象的属性和方法尽管名字可能相同,但由于隶属不同的命名空间,其值可能完全不同。在实现类的继承与重载等特性时同样需要考虑命名空间的问题,以枚举类型的实现为例,我们需要保证枚举对象的属性名称不能有重复,因此我们需要继承内置的 dict 类:

    _EnumDict(dict):
     ? ?__init__(self):
     ? ? ? ?dict.__init__(self)
     ? ? ? ?self._member_names = []
    ? ?keys(self): ? ? ? ?keys = dict.keys(self)
    ? ? ? ?return list(filter(lambda k: k.isupper(),keys)) ed = _EnumDict() ed['RED'] = 1
    ed['red'] = 2
    print(ed,ed.keys())
    {'RED': 1,'red': 2} ['RED']

    在上面的例子中 _EnumDict 重载同时调用了父类 dict 的一些方法,上面的写法在语法上是没有错误的,但是如果我们要改变 _EnumDict 的父类,不再是继承自 dict,则必须手动修改所有方法中 dict.method(self) 的调用形式,这样就不是一个好的实践方案了。为了解决这一问题,Python 提供了一个内置函数 super()

    print(super.__doc__)
    super() -> same as super(__class__,<first argument>)
    super(type) -> unbound super object
    super(type,obj) -> bound super object; requires isinstance(obj,type)
    super(type,type2) -> bound super object; requires issubclass(type2,type)
    Typical use to call a cooperative superclass method:
    class C(B):
     ? ?def meth(self,arg):
     ? ? ? ?super().meth(arg)
    This works for class methods too:
    class C(B):
     ? ?@classmethod
     ? ?def cmeth(cls,arg):
     ? ? ? ?super().cmeth(arg)

    我最初只是把 super() 当做指向父类对象的指针,但实际上它可以提供更多功能:给定一个对象及其子类(这里对象要求至少是类对象,而子类可以是实例对象),从该对象父类的命名空间开始搜索对应的方法。

    以下面的代码为例:

    A(object):
     ? ?method(self):
     ? ? ? ?who(self)
     ? ? ? ?print("A.method")B(A):
     ? ?"B.method")C(B):
     ? ?"C.method")D(C):
     ? ?__init__(self):
     ? ? ? ?super().method()
     ? ? ? ?super(__class__,self).method()
    
     ? ? ? ?super(C,self).method() # calling C's parent's method
     ? ? ? ?super(B,117);"># calling B's parent's method
    
     ? ? ? ?super(B,C()).method() ?# calling B's parent's method with instance of C
    d = D()
    
    print("nInstance of D:")
    who(d)
    4542787992 <class '__main__.D'>
    C.method
    4542787992 <class '__main__.D'>
    C.method
    4542787992 <class '__main__.D'>
    B.method
    4542787992 <class '__main__.D'>
    A.method
    4542788048 <class '__main__.C'>
    A.method
    
    Instance of D:
    4542787992 <class '__main__.D'>

    当然我们也可以在外部使用 super() 方法,只是不能再用缺省参数的形式,因为在外部的命名空间中不再存在 __class__self

    super(D,d).method() # calling D's parent's method with instance d
    4542787992 <class '__main__.D'>
    C.method

    上面的例子可以用下图来描述:

    +----------+
    | A ? ? ? ?|
    +----------+
    | method() <---------------+ super(B,self)
    +----------+ ? ? ? ? ? ? ? |
     ? ? ? ? ? ? ? ? ? ? ? ? ? |
    +----------+ ? ? ? ? ? ? ? +----------+
    | B ? ? ? ?| ? ? ? ? ? ? ? | D ? ? ? ?|
    +----------+ super(C,self) +----------+
    | method() <---------------+ method() |
    +----------+ ? ? ? ? ? ? ? +----------+
     ? ? ? ? ? ? ? ? ? ? ? ? ? |
    +----------+ ? ? ? ? ? ? ? |
    | C ? ? ? ?| ? ? ? ? ? ? ? |
    +----------+ ? ? ? ? ? ? ? | super(D,self)
    | method() <---------------+
    +----------+

    可以认为 super() 方法通过向父类方向回溯给我们找到了变量搜寻的起点,但是这个回溯的顺序是如何确定的呢?上面的例子中继承关系是 object->A->B->C->D 的顺序,如果是比较复杂的继承关系呢?

    A(object):
     ? ?passmethod(self):
     ? ? ? ?print("B's method")C(A):
     ? ?"C's method")D(B,C):
     ? ?__init__(self):
     ? ? ? ?super().method()E(C,B):
     ? ?__init__(self):
     ? ? ? ?super().method()
    
    d = D()
    e = E()
    B's method
    C's method

    Python 中提供了一个类方法 mro() 可以指定搜寻的顺序,mro 是Method Resolution Order 的缩写,它是类方法而不是实例方法,可以通过重载 mro() 方法改变继承中的方法解析顺序,但这需要在元类中完成,在这里只看一下其结果:

    D.mro()
    [__main__.D,__main__.B,__main__.C,__main__.A,object]
    E.mro()
    [__main__.E,object]

    super() 方法就是沿着 mro() 给出的顺序向上寻找起点的:

    B's method
    C's method
    super(C,e).method()
    super(B,d).method()
    B's method
    C's method

    总结

    super() 方法解决了类->实例实践过程中关于命名空间的一些问题,而关于生成对象的流程,我们知道初始化实例是通过类的 __init__() 方法完成的,在此之前可能涉及到一些其它的准备工作,包括上面提到的 mro() 方法以及关键的元类->类的过程,将在后面一篇中继续介绍。

    (编辑:核心网)

    【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读