Python的元类

Python中一切皆对象!除了常见的定义一个类并创建该类的一个对象,还包括函数、变量、列表、元组、字典、集合,甚至类等。

1. 什么是对象

  对象(object)是一种数据抽象或数据结构抽象,分配的一块内存空间,拥有特定的值,支持特定类型的相关操作。Python中,对象具有三要素:标识(identity)、类型(type)、值(value)。

  • 标识(identity):用于唯一标识对象,通常对应对象在计算机内存中的地址。使用内置函数 id(object) 返回对象唯一标识。

  • 类型:标识对象类型,表示对象存储的数据的类型。

    每一个对象都有两个标准的头部信息:
    1.类型标识符,去标识对象的(数据)类型。
    2.引用计数器,记录当前对象的引用的数目。(回收机制:变量的引用计数器为0,自动清理。 ※ 较小整数型对象有缓存机制。)

  • 值(value): 表示对象存储的数据的信息。使用内置函数print(object)可以直接打印值。

2. 元类(metaclass)

2.1 类对象的创建

  在Python中类就是一组用来描述如何生成一个对象的代码段。通常可以声明一个类,创建该类的对象,如下例:

# 声明一个类
class Foo(object):
	bar = True

# 创建类的对象
foo = Foo()
print(foo)  # 输出:<__main__.Foo object at 0x8a9b84c>

  但是,Python中的类也是一种对象,所以类对象肯定也是通过某些东西创建的,参考上述类与其对象的关系,猜想如下:

Foo = M(...)   # 假设M创建了Foo类对象

  事实上,上述 M 的身份即为 元类type,即 type 创建了Foo类对象。当用class 声明 Foo类对象时,Python默认通过元类 type 创建 Foo类对象,创建Foo类对象的语法格式如下:

Foo = type('Foo', (object,), {'bar': True})

元类就是用来创建这些类(对象)的,元类就是类的类。
在这里插入图片描述

2.2 元类type

  内建函数 type 根据传入的参数不同,执行不同功能——一个参数返回对象类型,三个参数返回类对象。当作为元类返回一个类时,语法格式形如:

type(类名,基类元组, 包含属性或函数的字典)

2.2.1 使用type创建类对象

  先使用class声明 FooChild类,该类继承上述的 Foo类,并有自身的类属性、实例方法、类方法和静态方法。如下所示:

class FooChild(Foo):
	bip = False
	
	def echo_bip(self):
		print(self.bip)
	
	@staticmethod
	def test_static():
		print("static method ....")

	@classmethod
	def test_class(cls):
		print(cls.bar)

上述的FooChild类对象创建过程如下:

def echo_bip_t(self):  # 定义了一个普通的函数
	print(self.bip)

@staticmethod
def test_static_t():
print("static method ....")

@classmethod
def test_class_t(cls):
	print(cls.bar)

Foochild = type('Foochild', (Foo,), {"echo_bip":echo_bip_t, "test_static": test_static_t, "test_class": test_class_t})

  注意:type创建类对象时的第三个参数的字典中,当存在属性时,键是类对象的类属性非实例属性;存在函数时,键为类对象真正的函数名称,值为普通函数的引用。

2.2.2 type 是所有对象的元类

  type是Python在后台用来创建所有类的元类。通过__class__属性或者使用type()函数查看对象类型的方式,对整数、字符串、函数以及类进行验证,如下所示:

# 测试数字的类
age = 35
print(age.__class__)    # 或 print(type(age))


# 测试字符串的类
name = 'bob'
print(name.__class__)   # 或 print(type(name))


# 测试函数的类
def Foo():
    pass

print(Foo.__class__)    # 或 print(type(Foo))


# 测试实例对象的的类
class Bar(object):
    pass

b = Bar()
print(b.__class__)      # 或 print(type(b))


# 测试类的类
class Bar(object):
    pass


print(Bar.__class__)    # 或 print(type(Bar))

print("------分割线-------")

# 测试类型的类
print(age.__class__.__class__)
print(name.__class__.__class__)
print(Foo.__class__.__class__)
print(b.__class__.__class__)
print(Bar.__class__.__class__)

输出结果为:

<class 'int'>
<class 'str'>
<class 'function'>
<class '__main__.Bar'>
<class 'type'>
------分割线-------
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>

  可以看到,每种对象追溯最初创建起源的结果均是type,并且 type由type创建

type(type)    # 输出:type

2.3 自定义元类

  除了通过默认的 type 来创建类对象,还可以通过自定义元类创建类对象(此元类一定要显示继承自 type)。声明一个类时,可通过传入__metaclass__属性的值来指定创建该类的元类。

如果声明类时没有传入__metaclass__属性指定元类,Python会在父类中寻找__metaclass__属性指定的元类,如果没有将继续在父类的父类中寻找······依此类推,直到找到__metaclass__属性指定的元类或最终只能通过内建的 type 创建这个类为止。

2.3.1 通过自定义元类创建类

在 Python3 中如下例:

class MyType(type):
    def __new__(cls, *args, **kwargs):
        print("Python传递给__new__的参数:\ncls: ", cls, "\nargs: ", args, "\nkwargs:", kwargs)
        # 创建类
        new_cls = type(*args, **kwargs)
        
		# __new__是用来创建对象并返回的方法
        # 或者 new_cls = super().__new__(cls, *args, **kwargs)   
        
        print(new_cls)
        return new_cls


# 声明Yoo类,指定为MyType创建
class Yoo(object, metaclass=MyType):
    bip = True

运行后,结果如下:

Python传递给__new__的参数:
cls:  <class '__main__.MyType'> 
args:  ('Yoo', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'Yoo', 'bip': True}) 
kwargs: {}
<class '__main__.Yoo'>

类似 type 创建类对象的方式,以上过程相当于Yoo = MyType('Yoo', (object,), {'bip':True})

__metaclass__属性除了指定元类创建类,也可以指定一个函数去创建类。如下例:

def func(class_name, class_parents, class_attr):
   print("class_name为:", class_name, "\nclass_parents为:", class_parents, "\nclass_attr为:",  class_attr)
   # 调用type来创建一个类
   new_cls = type(class_name, class_parents, class_attr)
   print(new_cls)
   return new_cls
   
   # 声明Yoo类,指定为func创建
class Yoo(object, metaclass=func):
    bip = True

运行结果为:

class_name为: Yoo 
class_parents为: (<class 'object'>,) 
class_attr为: {'__module__': '__main__', '__qualname__': 'Yoo', 'bip': True}
<class '__main__.Yoo'>

2.3.2 自定义元类的意义

  虽然 type 能创建类对象,但有时候对已声明的类根据需求做某些改变时,直接修改该类不是最合适的做法,而是采用自定义元类的方式实现。自定义元类的主要目的就是为了在创建类时能灵活操纵类对象建立过程。

🔺示例一:将已声明的Moo类的所有属性全部改为大写。

class UpperAttrMetaClass(type):
    def __new__(cls, class_name, class_parents, class_attr):
        # 遍历属性字典,把不是__开头的属性名字变为大写
        new_attr = {}
        for name, value in class_attr.items():
            if not name.startswith("__"):
                new_attr[name.upper()] = value

        # 方法1:通过'type'来做类对象的创建
        return type(class_name, class_parents, new_attr)

        # 方法2:复用type.__new__方法
        # return type.__new__(cls, class_name, class_parents, new_attr)

# python3的用法
class Moo(object, metaclass=UpperAttrMetaClass):
    bar = 'bip'

print(hasattr(Moo, 'bar'))   # 输出: False
print(hasattr(Moo, 'BAR'))   # 输出:True

f = Moo()
print(f.BAR)   # 输出:'bip'

  可以看到,通过元类中的__new__方法控制类对象的创建,Moo类的所有小写属性全部被改为大写。

🔺示例二:禁止创建一个有“test_”开头的成员的类对象。

class M(type):
    def __new__(cls, name, bases, dict):
        print(name, bases, dict)
        for key in dict.keys():
            if key.startswith("test_"):
                raise ValueError()
        return type.__new__(cls, name, bases, dict)


class A(metaclass=M):
    def test_case(self):
        pass

运行结果为:

A () {'__module__': '__main__', '__qualname__': 'A', 'test_case': <function A.test_case at 0x000002328E65FEE0>}
Traceback (most recent call last):
  File "...", line 10, in <module>
    class A(metaclass=M):
  File "...", line 6, in __new__
    raise ValueError()
ValueError

🔺示例三:为类对象添加类变量 random_id。

import random
class M(type):

    def __new__(cls, name, bases, dict):
        print(name, bases, dict)
        return type.__new__(cls, name, bases, dict)

    def __init__(self, name, bases, dict):
        print(name, bases, dict)
        self.random_id = random.randint(0, 100)
        return type.__init__(self, name, bases, dict)

class A(metaclass=M):
    pass

print(A.random_id)

  元类如同普通类一般,如同时存在__new__方法和__init__方法时,也遵循“首先执行前者创建类对象,然后执行后者对类对象进行初始化”的规则,在这个过程中参数 self 指代类对象。运行结果为:

A () {'__module__': '__main__', '__qualname__': 'A'}
A () {'__module__': '__main__', '__qualname__': 'A'}
11

上述过程等价于如下代码:

import random
class M(type):

    def __new__(cls, name, bases, dict):
        print(name, bases, dict)
        return type.__new__(cls, name, bases, dict)

    def __init__(self, name, bases, dict):
        print(name, bases, dict)
        return type.__init__(self, name, bases, dict)

class A(metaclass=M):
    random_id = random.randint(0, 100)

print(A.random_id)

3. 拓展

3.1 元类与父类多继承

3.1.1 元类的多继承

  既然类可以实现多继承,如下所示:

class 子类(父类1,父类2,...):
	类的属性
	类的方法

那么是否也可以实现元类的多继承?可惜的是,Python不支持为某个类同时指定多于一个的元类类似class 某类(metaclass=[元类1, 元类2]),尝试通过多重继承的方式实现相同的功能,如下所示:

class Meta1(type):
    def __new__(cls, *args, **kwargs):
        return type.__new__(Meta1, *args, **kwargs)

class Meta2(type):
    def __new__(cls, *args, **kwargs):
        return type.__new__(Meta2, *args, **kwargs)

class ClassWithMeta1(metaclass=Meta1):
    pass


class ClassWithMeta2(metaclass=Meta2):
    pass


class ClassWithMeta1AndMeta2(ClassWithMeta1, ClassWithMeta2):
    pass

""" output
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
"""

  结果报错,报错提示为:ClassWithMeta1AndMeta2所有父类(ClassWithMeta1, ClassWithMeta2)的对应元类(Meta1, Meta2)不能找到某个元类为其他元类的子类。在这个例子中元类Meta1和 Meta2都不是各自的子类,因此会报元类冲突的错误。

解决办法一:多个父类中涉及的所有元类之间拥有继承关系

改写元类Meta2,让元类Meta2成为Meta1的子类。如下所示:

class Meta1(type):
    def __new__(cls, *args, **kwargs):
    	clsname, bases, namespace = args

    	print(clsname, "is created by", Meta1.__name__)
        return type.__new__(Meta1, *args, **kwargs)

# 变化部分:Meta2继承Meta1
class Meta2(Meta1):
    def __new__(cls, *args, **kwargs):
    	clsname, bases, namespace = args

    	print(clsname, "is created by", Meta2.__name__)
        return type.__new__(Meta2, *args, **kwargs)

class ClassWithMeta1(metaclass=Meta1):
    pass


class ClassWithMeta2(metaclass=Meta2):
    pass


class ClassWithMeta1AndMeta2(ClassWithMeta1, ClassWithMeta2):
    pass

print(type(ClassWithMeta1AndMeta2))

""" output
ClassWithMeta1 is created by Meta1
ClassWithMeta2 is created by Meta2
ClassWithMeta1AndMeta2 is created by Meta2
<class '__main__.Meta2'>
"""

  如上所示,元类Meta2继承自元类Meta1,Meta2和Meta1分别是ClassWithMeta1类和ClassWithMeta2类的元类。当定义继承自ClassWithMeta1和ClassWithMeta2这两个类的ClassWithMeta1AndMeta2类时,编译器可以找到一个元类(Meta2),该元类是ClassWithMeta1AndMeta2所有父类包含的不同元类的子类,因此不会发生元类冲突。
  在定义ClassWithMeta1AndMeta2时判断出它的元类为Meta2,因此触发Meta2(‘ClassWithMeta1AndMeta2’, (ClassWithMeta1, ClassWithMeta2), {}),进入Meta2的__new__方法,最终由return type.__new__(Meta2, *args, **kwargs)创建出ClassWithMeta1AndMeta2类。

解决办法二:自定义一个元类,继承父类中涉及到的所有元类

  对于元类Meta1和Meta2出于种种原因无法更改的情况,这时可以自定义元类,继承这些互相独立的元类,从而避免元类冲突。如下所示:

class Meta1(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta1.__name__)
        return type.__new__(Meta1, *args, **kwargs)


class Meta2(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta2.__name__)
        return type.__new__(Meta2, *args, **kwargs)

class CombinedMeta(Meta1, Meta2):
    pass


class ClassWithMeta1AndMeta2(metaclass=CombinedMeta):
    pass


print(type(ClassWithMeta1AndMeta2))

""" output
ClassWithMeta1AndMeta2 is created by Meta1
<class '__main__.Meta1'>
"""

  如上所示,Meta2和Meta1均继承type,且被自定义类CombinedMeta多继承,ClassWithMeta1AndMeta2的元类指定为CombinedMeta。
  虽然上述代码能够顺利运行,但是观察倒数两句输出结果 ClassWithMeta1AndMeta2 is created by Meta1<class '__main__.Meta1'>和ClassWithMeta1AndMeta2类在定义时指定的元类class ClassWithMeta1AndMeta2(metaclass=CombinedMeta)矛盾,这并不是我们想要的结果!

执行分析
  在定义ClassWithMeta1AndMeta2时,该类的元类为CombinedMeta,因此会调用CombinedMeta(‘ClassWithMeta1AndMeta2’, (), {})生成ClassWithMeta1AndMeta2类。但CombinedMeta中没有__new__方法,因此直接调用父类的__new__方法。又由于CombinedMeta类有多个父类,涉及到mro顺序,执行如下代码进行查看:

print(CombinedMeta.__mro__)

""" output
(<class '__main__.CombinedMeta'>, <class '__main__.Meta1'>, <class '__main__.Meta2'>, <class 'type'>, <class 'object'>)
"""

  可知,会调用父类Meta1的__new__方法。打印出ClassWithMeta1AndMeta2 is created by Meta1语句,再调用return type.__new__(Meta1, *args, **kwargs),执行结束!

分析结果:多继承中涉及到mro顺序,父类方法的调用应该使用 super!修改如下:

class Meta1(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta1.__name__)
        return super().__new__(cls, *args, **kwargs)


class Meta2(type):
    def __new__(cls, *args, **kwargs):
        clsname, bases, namespace = args

        print(clsname, "is created by", Meta2.__name__)
        return super().__new__(cls, *args, **kwargs)


class ClassWithMeta1(metaclass=Meta1):
    pass


class ClassWithMeta2(metaclass=Meta2):
    pass


class CombinedMeta(Meta1, Meta2):
    pass


class ClassWithMeta1AndMeta2(metaclass=CombinedMeta):
    pass


print(type(ClassWithMeta1AndMeta2))

""" output
ClassWithMeta1 is created by Meta1
ClassWithMeta2 is created by Meta2
ClassWithMeta1AndMeta2 is created by Meta1
ClassWithMeta1AndMeta2 is created by Meta2
<class '__main__.CombinedMeta'>
"""

  上述代码,根据mro顺序调用到Meta1的__new__方法时,打印出ClassWithMeta1AndMeta2 is created by Meta1语句,再调用return super().__new__(cls, *args, **kwargs)。根据mro顺序,调用Meta2的__new__方法,打印ClassWithMeta1AndMeta2 is created by Meta2语句,再调用return super().__new__(cls, *args, **kwargs)。Meta2中的super.__new__实际等同于type.__new__(CombinedMeta, *args, **kwargs)。
  于是类ClassWithMeta1AndMeta2通过指定继承了Meta1和Met2的元类CombinedMeta被这个两个元类控制,实现了元类的多继承实现。

启发:在实现每个独立的元类的__new__方法时,注意最后最好使用super().__new__来返回。

3.1.2 元类与父类多继承

  如下例定义一个Human类,同时实现父类和元类多继承,实现方式如下所示:

class Meta1(type):
    def __new__(mcs, *args, **kwargs):
        return super(Meta1, mcs).__new__(mcs, *args, **kwargs)

class Meta2(type):
    def __new__(mcs, *args, **kwargs):
        return super(Meta2, mcs).__new__(mcs, *args, **kwargs)

class Body(metaclass=Meta1):
    pass

class Head(metaclass=Meta2):
    pass

class Common:
    pass

# 变化部分:新增元类继承所有涉及的元类
class CombineMeta(Meta1, Meta2):
    pass

# 设置元类
class Human(Common, Body, Head, metaclass=CombineMeta):
    pass

print(type(Human))
print(Human.__bases__)

"""
<class '__main__.CombineMeta'>
(<class '__main__.Common'>, <class '__main__.Body'>, <class '__main__.Head'>)
"""

  运行结果可知,Human多继承Common、Body、Head并被元类CombineMeta所创建(其中Common是普通类,Body和Head是指定自定义元类创建的类,CombineMeta是自定义元类)。
  要想取得此结果,Human所有父类的自定义元类(Meta1, Meta2)与指定元类(CombineMeta),其中一个元类需继承剩余的所有元类。上述代码中,元类CombineMeta正好多继承了父类Body和Head分别对应的元类Meta1和元类Meta2。

3.2 类实例化时先执行__new__后执行__init__的原理

  回顾一下当类中定义了__call__方法时, 该类的实例对象可调用,即对象()。如下例:

class Person(object):

    def __call__(self):
        print("Method __call__() is called")

d = Person()
d()

# 运行结果为:Method __call__() is called

  同理,类对象作为元类的对象,调用类生成实例对象即类()时,实际上执行的是元类中定义的__call__方法。如下例:

class Goodmeta(type):
    def __call__(self, *args, **kwargs):
        return 'ok'
 
class Simpler(metaclass=Goodmeta):
 
    def __init__(self, name, age):
        print('obj__init__')
        self.name = name
        self.age = age
 
    def __new__(cls, *args, **kwargs):
        print('obj__new__')
        return object.__new__(cls)
 
    def __call__(self):
        return f'My name is {self.name}, age is {self.age}'
 
 
if __name__ == '__main__':
    s = Simpler('sidian', 18)
    print(s)

# 运行结果为:ok

  观察到,虽然调用Simpler类时如预期一样执行了元类Goodmeta的__call__定义的内容,但与平常现象相悖,创建Simpler类的实例对象时,Simpler的__init__与__new__都没有执行!
  其实,在类在实例化的过程中会自动先调用__new__后调用__init__方法的规则是元类的__call__方法中定义的。即在元类中通过调用元类中的self对象[被创建的类对象],调用该对象的__new__以及__init__,并返回seif[元类创造的类]的实例。如下例:

class Goodmeta(type):
 
    def __call__(self, *args, **kwargs):
        # self是什么是Goodmeta创建的对象
        # 调用Goodmeta创建的类对象的__new__方法创建对象
        obj = self.__new__(self)
        # 调用Goodmeta创建的类对象的__init__方法进行初始化
        self.__init__(obj, *args, **kwargs)   #或 obj.__init__(*args, **kwargs)
        return obj
 
class Simpler(metaclass=Goodmeta):
 
    def __init__(self, name, age):
        print('obj__init__')
        self.name = name
        self.age = age
 
    def __new__(cls, *args, **kwargs):
        print('obj__new__')
        return object.__new__(cls)
 
    def __call__(self):
        return f'My name is {self.name}, age is {self.age}'
 
 
if __name__ == '__main__':
    s = Simpler('sidian', 18)
    print(s)

运行结果为:

obj__new__
obj__init__
<__main__.Simpler object at 0x7faab82ac130>

3.3 object、type、class之间的关系

  由上述可知,type 是所有对象的元类。另外,object 是所有类的基类,验证如下:

class Student:
	pass
	
print(int.__bases__)   # 输出:(object,)
print(str.__bases__)   # 输出:(object,)
print(Student.__bases__)   # 输出:(object,)

print(object.__bases__)   # 输出:()

  而 type 本身也是类,本质上也是一种对象,查看 type 的继承关系,如下所示:

print(type.__bases__)   # 输出:(object,)

  得到,type 继承自object ,查看 object 的由谁创建,如下所示:

print(type(object))   # 输出:type

  得到,objecttype 创建。

理解 typeobjectclass 的关系:typeobject是互补关系,即 type 创建了 object 时又继承了自己创建的 objectclass 声明一个类,类实际是 type 创建的。

3.4 newinit、__call__所传的第一个参数的奥秘

  当元类中使用 super 的方式调用父类方法时,super().__new__必须传第一个参数(cls),对于__init__方法和__call__方法则不用。
(留个坑)

参考

1.【python】metaclass理解加入门,看完就知道什么是元类了。
2. 彻底搞定python元类metaclass
3.【比刷剧还爽】清华大佬耗时128小时讲完的Python高级教程!全套200集!学不会退出IT界!
4. 元类-04-理解元类
5. Python 元类详解 newinitcall[收官之作]
6. Python之解决元类冲突
7. Python3元类的多重继承

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值