python 元类

元类type介绍

我们知道在python中一切皆对象,即使是类,也是对象,那么类这个对象是的类是谁呢?那就是元类

通过 type()obj.__class__ 可查看对象的元类:

# 元类
class A:
    pass


if __name__ == "__main__":
    a = A()
    # 1.查看自定义对象的元类
    print(type(a))  # <class '__main__.A'>
    # 2.查看类A的元类
    print(type(A))  # <class 'type'>
    # 3.python一些内置类型的元类
    print(type(str))  # <class 'type'>
    print(type(int))  # <class 'type'>
    print(type(float))  # <class 'type'>
    # 4.甚至type类的元类还是自己
    print(type(type))  # <class 'type'>

从上面结果可以看出:

  1. 普通对象的元类可直接查看是创建对象的类
  2. 自己创建的一些类的元类是type
  3. python内置的一些基本类型的元类是type
  4. type自己的元类也是type

所以可以说:所有的类终归都是由 type 实例化得来的,它就是最元始的,同时它自己也是一个类,所以也可以称之为元类(Metaclass)。

实例、类、元类之间的关系如图所示:

在这里插入图片描述

使用元类创建类

既然前面说了所有的类终究都是由元类创建的,那么下面看看,除了我们经常使用class关键字来快速创建类,如何使用元类来创建类。

通常有以下两种方法

直接使用type

type源码如下

class type(object):
    """
    type(object_or_name, bases, dict)
    type(object) -> the object's type
    type(name, bases, dict) -> a new type
    """
    def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__
        """
        type(object_or_name, bases, dict)
        type(object) -> the object's type
        type(name, bases, dict) -> a new type
        # (copied from class doc)
        """
        pass

创建type对象一般有3个参数:

  • name:新类的名称;
  • bases:以元组的形式,声明父类;
  • dict:以字典的形式声明类的属性;

示例如下:

# 元类

if __name__ == "__main__":
    # 通过type创建类A
    A = type("A", (), {})
    # 创建类A的实例a
    a = A()

    print(type(a))  # <class '__main__.A'>
    print(type(A))  # <class 'type'>
    print(A.__bases__)  # (<class 'object'>,)

从结果可以看出我们使用type成功创建一个类A。

虽然没有给 bases 赋值,但是 Python 中所有的类都是以 object 为基类,所以查看类 Foo1 的父类,会得到这样一个结果。PS:__bases__查看父类。

通过type创建带属性的类:
注意属性是类属性

if __name__ == "__main__":
    User = type("User", (), {"name":"user"})
    my_obj = User()
    print(my_obj)
    # my_obj实例有属性
    print(my_obj.name)

通过type创建带方法的类

def say(self):
    return "i am user"
    # return self.name

if __name__ == "__main__":
    User = type("User", (), {"name":"user", "say":say})
    my_obj = User()
    print(my_obj)
    print(my_obj.say())

通过type创建带父类的类

def say(self):
    return "i am user"
    # return self.name

class BaseClass:
    def answer(self):
        return "i am baseclass"

if __name__ == "__main__":
    User = type("User", (BaseClass, ), {"name":"user", "say":say})
    my_obj = User()
    print(my_obj)
    print(my_obj.answer())

继承type

如果一个类继承了type类,那么这个类也是元类,使用metaclass可以指定某个类的元类是哪个。来这种方式使用的较多。

下面是一个简单的示例:

class MetaClass(type):
    pass


class User(metaclass=MetaClass):
    pass


if __name__ == "__main__":
    my_obj = User()
    print(my_obj)

MetaClass 继承了 type ,就可以称为元类,而 User 类与之前定义的类的不同之处就在于,类名后括号内添加了参数 metaclass 来指定元类(即指定使用哪个元类来创建当前类,默认是type),这样就利用了元类 MetaClass 创建了类 User

类和对象的创建过程

上面知道了如何去定义一个元类,下面看看,元类是如何创建一个类的。

看一个下面的例子:

class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        print("new of MetaClass")
        return super(MetaClass, cls).__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        print("init of MetaClass")
        super(MetaClass, self).__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        print("call of MetaClass")
        super(MetaClass, self).__call__( *args, **kwargs)

class User(metaclass=MetaClass):
    pass

直接运行文件:打印如下:

new of MetaClass
init of MetaClass

可以看到与对象的实例化过程很像。

当使用class关键字定义一个类时,其实是先去寻找这个类的元类,找到元类后,先调用元类的__new__方法实例化一个类对象,然后调用__init__去初始化这个类对象。

另外:
创建类时,会先检查当前类是否有元类,没有再找父类是否有,没有再去模块中找,再没有再使用type创建类。

然后我使用User类来创建一个对象:

class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        print("new of MetaClass")
        return super(MetaClass, cls).__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        print("init of MetaClass")
        super(MetaClass, self).__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        print("call of MetaClass")
        super(MetaClass, self).__call__(self, *args, **kwargs)


class User(metaclass=MetaClass):
    pass


if __name__ == "__main__":
    print("--- 开始创建User对象")
    my_obj = User()

运行打印结果如下:

new of MetaClass
init of MetaClass
--- 开始创建User对象
call of MetaClass

在使用元类的__new__方法和__init__去创建完类后,实例化这个类时,会调用元类的__call__方法,这个方法会返回实例化后的对象。

其实在实例化对象时,这个元类的__call__方法会自己调类的__new__方法和__init__去创建并初始化对象,然后返回。

看下面的示例:

class MetaClass(type):
    def __new__(cls, *args, **kwargs):
        print("new of MetaClass")
        return super().__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        print("init of MetaClass")
        super().__init__(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        print("call of MetaClass")
        return super().__call__(*args, **kwargs)


class User(metaclass=MetaClass):
    def __new__(cls, *args, **kwargs):
        print("new in User")
        return super().__new__(cls)

    def __init__(self, name, age):
        print("init in User")
        self.name = name
        self.age = age



if __name__ == "__main__":
    print("--- 开始创建User对象")
    my_obj = User("a", 10)
    print(my_obj)

打印结果如下:

new of MetaClass
init of MetaClass
--- 开始创建User对象
call of MetaClass
new in User
init in User
<__main__.User object at 0x7f9b58056d30>

可以很清楚的看到从创建类到类的实例化的过程,可总结如下:

  1. 创建类:调用原元类的__new__方法和__init__方法创建了一个类;
  2. 创建对象:调用了原类的__call__方法;
  3. 创建对象:__call__方法调类的__new__方法和__init__去创建并初始化对象,然后返回。

元类有什么用

一般我们不会自己定义元类,但是会再一些框架只能怪经常见到。

其主要作用就是在定义一些类时有一些限制或特殊要求。

比如我想限制一个测试类必须以Test开头:

class TestType(type):
    def __new__(cls, name, bases, attrs):
        if not name.startswith("Test"):
            raise AttributeError("类名请以Test开头")
        return super().__new__(cls,  name, bases, attrs)


class TestUser(metaclass=TestType):
    pass


if __name__ == "__main__":
    print("--- 开始创建对象")
    test_obj = TestUser()

如果我定义了类不以Test开头就会报错

class TestType(type):
    def __new__(cls, name, bases, attrs):
        if not name.startswith("Test"):
            raise AttributeError("类名请以Test开头")
        return super().__new__(cls,  name, bases, attrs)


class User(metaclass=TestType):
    pass


if __name__ == "__main__":
    print("--- 开始创建对象")
    test_obj = User()
	# AttributeError: 类名请以Test开头

使用元类实现一个单例模式

所谓的单例模式,就是指一个类不论我实例化多少次,都指返回同一个对象。主要用在一些公用资源、文件对象等。比如我们的数据库连接池。

示例如下:

class Meta(type):
    __instance = None

    def __call__(cls, *args, **kwargs):
        if not cls.__instance:
            cls.__instance = type.__call__(cls, *args, **kwargs)
        return cls.__instance


class DbPool(metaclass=Meta):
    pass


if __name__ == "__main__":
    x = DbPool()
    y = DbPool()
    print(x)
    print(y)
    print(x==y)
    
    # <__main__.DbPool object at 0x7fc210182e20>
    # <__main__.DbPool object at 0x7fc210182e20>
    # True

上面主要是让我们的DbPool类使用我们自定义的元类Meta。然后重写了Meta例的__call__方法。为什么呢?回一下前面讲的,在实例化对象时,会调用元类的__call__方法返回实例化后的对象,所以为了返回同一个对象,可以在__call__方法里返回同一个即可。

参考:
https://www.cnblogs.com/XiaoYang-sir/p/16524775.html
https://blog.csdn.net/weixin_43988680/article/details/123903473

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ethan-running

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值