python 中如何用父类的元类(metaclass)创建所有父类的子类

实例和类

python 中的类和实例概念和c++是一样的 。在c++中实例是类在内存中可以运行的实体,说人话就是编译器会把类当成设计图。在内存中开辟空间,这个空间存放着可以运行的代码。这片空间就是实例,实例是相对于类来说的概念。而在写代码中。我们把引用/指向实例的符号叫做对象。

而python 中的类和实例的思想和这个是一样的。但是细节上有一点不同。后面我们会说。

类的new/init/call方法

class A():
    
    def __new__(cls,*args: Any,**kwargs: Any) -> 'A':
        print("A:__new__")
        print("A:__new__ arg:",cls,args,kwargs)
        ##object.__new__(cls) ==super().__new__(cls)

        return object.__new__(cls)

    def __init__(self,*args: Any,**kwargs: Any) -> None:
        print("A:__init__")
        print("A:__init__ arg:",self,args,kwargs)
        
        pass
    
    def __call__(self, *args: Any, **kwds: Any) -> Any:
        print("A:__call__")
        print("A:__call__ arg:",self,args,kwds)
        pass


# new/init/call这三个内部方法会在实例的生成时候调用。 
a=A(1231,abc=100)
b=A()
a()
A:__new__
A:__new__ arg: <class '__main__.A'> (1231,) {'abc': 100}
A:__init__
A:__init__ arg: <__main__.A object at 0x00000144DC46C1F0> (1231,) {'abc': 100}
A:__new__
A:__new__ arg: <class '__main__.A'> () {}
A:__init__
A:__init__ arg: <__main__.A object at 0x00000144DC46C220> () {}
A:__call__
A:__call__ arg: <__main__.A object at 0x00000144DC46C1F0> () {}
  • new 是python 生成对象用的。*这个方法的返回必须是生成的对象,很重要
  • init 一旦对象被生成就会自动调用init 方法来初始化它
  • call 当使用函数调用来调用实例的时候会使用到它,对应代码a()

因为我们不可能自己手动开辟内存。创建python 对象。所以必须调用object的new方法来帮我们完成这个基本操作。

A(1231,abc=100) 中传递的参数会被传递给new 和init 方法

object.__new__(cls)super().__new__(cls) 等价的。类的new 方法只需要一个类对象就能初始化。后面 args 和kwargs 是A(1231,abc=100) 传递进去的参数

元类new/init/call方法

元类(metaclass),什么元类呢?元类就是类的类。我举个例子

from typing import Any
# from typing import Self

class metaA(type):
    # 生成class  对象
    def __new__(cls,*args,**kwargs) -> 'metaA':
        print("metaA class __new__")
        print("metaA class __new__ arg:" ,cls ,*args,**kwargs)
        A=super().__new__(cls,*args,**kwargs)
        # A=type(*args,**kwargs)
        # print(type(A))
        # return type(*args)
        return A
    #初始化对象
    def __init__(self,*args,**kwargs) -> None:
        print("metaA class __init__")
        # print("metaA class __init__ arg:" ,self ,*args,**kwargs)
        super().__init__(self)
        pass
    
    def __call__(self, *args: Any, **kwds: Any) -> Any:
        print("metaA class __call__")
        # print("metaA class __call__ arg:" ,self ,*args,**kwds)
        return super().__call__(self,*args,**kwds)


class A(metaclass=metaA):
    
    def __new__(cls,*args: Any,**kwargs: Any) -> 'A':
        print("A:__new__")
        return object.__new__(cls)

    def __init__(self,*args: Any,**kwargs: Any) -> None:
        print("A:__init__")
        pass
    
    def __call__(self, *args: Any, **kwds: Any) -> Any:
        print("A:__call__")
        pass


# A=metaA() 
a=A()
b=A()
a()

metaA class __new__
metaA class __new__ arg: <class '__main__.metaA'> A () {'__module__': '__main__', '__qualname__': 'A', '__new__': <function A.__new__ at 0x0000021594655160>, '__init__': <function A.__init__ at 0x00000215946551F0>, '__call__': <function A.__call__ at 0x0000021594655280>}
metaA class __init__
metaA class __call__
A:__new__
A:__init__
metaA class __call__
A:__new__
A:__init__
A:__call__

\qquad 很简单,我们给A加了一个metaA的元类。对python 稍有了解的肯定都听说过,python 中一切皆为对象。所以我们见到的和自己定义的类都是class 对象。一开始我们说过对象就指向内存中一片可以运行的代码空间。它的分配是由class 的new来分配的。然而现在你告诉我class 也是对象。这就出现一个问题,我们的class 对象在内存中的又是由谁分配的呢?
\qquad 要是像c++这样的语言,类在内存中是没有实体的,所有的类的代码逻辑都是编译时候写死在内存中的。而python不一样,python的类是一个对象。所以这个对象(也就是我们定义的类)是可以在运行时候按照特定逻辑动态生成的。即我们肯伊按照不同的需求生成不同的类。再又不同的类生成不同的对象。
\qquad 所以回到刚才的问题。我们的class 对象由谁生成的呢?这个任务就是由元类的new方法来完成的。
\qquad 上面的代码就是按照metaA => A=>a 的方式生成对象的。

在说一下细节.

  • 元类的new的返回值必须是class对象。也就是内置函数type(name, bases, dict)的返回值。不然后面代码的A就是NoneType,参数cls 是metaA 这个class对象
  • 元类的call 的返回值必须是普通对象也就是object.__new__ 返回值
  • 你可以把元类生成类想成 A=metaA() 。这样就和类一样了。这样metaA()就是调用new方法返回A ,A()就是调用metaA的call 方法返回a对象。如果没有定义metaA的call,默认行为就是再去调用A的new来生成a对象,这样回到类的初始化流程了。
  • 上面的几条总结一些的核心论据就是。想控制生成什么类就改元类的new方法,想控制类到对象的生成就改元类的call方法,

元类new中使用type()type.__new__()的区别

我之前发现有人在元类的new中使用type来生成类。就像下面这样

from typing import Any
# from typing import Self

class metaA(type):
    # 生成class  对象
    def __new__(cls,*args,**kwargs) -> 'metaA':
        print("metaA class __new__")
        print("metaA class __new__ arg:" ,cls ,*args,**kwargs)
        # A=super().__new__(cls,*args,**kwargs)
        # A=type.__new__(cls,*args,**kwargs)
        A=type(*args,**kwargs)

        print("metaA class __new__ A:",A)
        print("metaA class __new__ type:", type(A))
        return A
    #初始化对象
    def __init__(self,*args,**kwargs) -> None:
        print("metaA class __init__")
        # print("metaA class __init__ arg:" ,self ,*args,**kwargs)
        super().__init__(self)
        pass
    
    def __call__(self, *args: Any, **kwds: Any) -> Any:
        print("metaA class __call__")
        # print("metaA class __call__ arg:" ,self ,*args,**kwds)
        A=super().__call__(self,*args,**kwds)

        return A



class A(metaclass=metaA):
    
    def __new__(cls,*args: Any,**kwargs: Any) -> 'A':
        print("A:__new__")
        return object.__new__(cls)

    def __init__(self,*args: Any,**kwargs: Any) -> None:
        print("A:__init__")
        pass
    
    def __call__(self, *args: Any, **kwds: Any) -> Any:
        print("A:__call__")
        pass


# A=metaA() 
a=A()
b=A()
a()

metaA class __new__
metaA class __new__ arg: <class '__main__.metaA'> A () {'__module__': '__main__', '__qualname__': 'A', '__new__': <function A.__new__ at 0x000001A6ED818E50>, '__init__': <function A.__init__ at 0x000001A6ED818EE0>, '__call__': <function A.__call__ at 0x000001A6ED818F70>}
metaA class __new__ A: <class '__main__.A'>
metaA class __new__ type: <class 'type'>
A:__new__
A:__init__
A:__new__
A:__init__
A:__call__

\qquad 你可以发现没有调用到元类的call方法。一开始我也不太明白,才写了这篇文档来理解这个。问题的原因就是元类new方法的返回值上。你可以看到的他是type的实例。而我们使用type.__new__()返回的是metaA的实例。也就是虽然都是叫class A 但是他是不同对象的实例。所以上面的例子其实对象初始化是由type的call 来完成的。所以才不会打印我们的信息。

\qquad 所以上面这种写法极力不推荐的,或者说是一种错误。

复杂关系中的元类

当你弄懂下面两这句话证明你已经解锁前置科技。可以往后看。

  • object 类没有父类,object 类是type 类的实例,
  • type 类的父类是object类,type 类是type类的实例、

我们创建一下代码验证



print("----------start-------------")
#元类必须集成type类
class meta(type):
    # 创建类
    def __new__(cls, name, bases, attrs):
        #打印类名称,父类。成员
        print("meta new:",cls, name, bases,)
        #传递给type函数创建类
        new_cls=super().__new__(cls, name, bases, attrs)
        #在看看他的类型
        print("type of new_cls:",type(new_cls))
        #被创建的类是不是object ,type的实例
        print("isinstance of cls:",isinstance(new_cls,cls))
        print("isinstance of type:",isinstance(new_cls,type))
        print("isinstance of oject:",isinstance(new_cls,object))

        #被创建类的父类
        print("bases:",new_cls.__bases__)
        return  new_cls

class metaa(meta):
    pass



class A(metaclass=metaa):
    def __init__(self):
        print("A init")

class B(A):
    def __init__(self):
        print("B init")

b=B()

print("------------end------------")

----------start-------------
meta new: <class '__main__.metaa'> A ()
type of new_cls: <class '__main__.metaa'>
isinstance of cls: True
isinstance of type: True
isinstance of oject: True
bases: (<class 'object'>,)
meta new: <class '__main__.metaa'> B (<class '__main__.A'>,)
type of new_cls: <class '__main__.metaa'>
isinstance of cls: True
isinstance of type: True
isinstance of oject: True
bases: (<class '__main__.A'>,)
B init
------------end------------

当python 运行代码时,解释器会扫描文件,遇到类就开始创建类对象,你可以看到整个继承链中,如果子类没有定义metaclass,所有的类都是最顶层父类的metaclass 的’new‘方法创建的,且这些被创建的类都是自定义metaclass的实例,同时自定义的metaclass是type的子类。所以这些被创建的类同时也是type的实例。而type又是object的子类。所以他们也都是object 的实例。

isinstance、 issubclass、type

  • issubclass:判断2个类是不是继承关系,等于超级__base__
  • isinstance 判断对象是不是某个类的实例
  • type 打印对象的元类 等于__class__

简单的参考

https://blog.csdn.net/qq_23996069/article/details/104594802
https://blog.csdn.net/Spade_/article/details/113485937

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值