python 面向对象之元类

type方法的应用

1.查看数据类型

s1 = 'hello world'  # str()
l1 = [11, 22, 33, 44]  # list()
d1 = {'name': 'jason', 'pwd': 123}  # dict()
t1 = (11, 22, 33, 44)  # tuple()
print(type(s1))  # <class 'str'>
print(type(l1))  # <class 'list'>
print(type(d1))  # <class 'dict'>
print(type(t1))  # <class 'tuple'>

2.查看对象是哪个类产生的

class Student:
    pass
obj = Student()
print(type(obj))  # <class '__main__.Student'>

3.类其实也是一种对象,类是由谁产生的呢

class Student:
    pass
obj = Student()
print(type(obj))  # <class '__main__.Student'>
print(type(Student))  # <class 'type'>
class A:pass
class B:pass
print(type(A), type(B))  # <class 'type'> <class 'type'>

我们发现我们的类都是由type产生的。

而type就是一切类的生产者(包括它自己)被称为元类

print(type(type))  # <class 'type'>

创建类的底层机制

我们平常都是用class关键字来创建类的:

class 类名(父类):
	类体代码

而我们打开产生类的type它的源码,可以看见type除了传入对象来判断产生它的类,还有另一种用法

![image-20221108155811062](C:\Users\ASUS\Pictures\md文件图片\python 面向对象之元类\image-20221108155811062.png)

即传入类名,父类,类体名称空间产生一个新类。

class_body_code = """
name = 'leethon'
"""
class_dict = {}
# exec会执行代码并将一些名称传入class_dict名称空间
exec(class_body_code, {}, class_dict)
cls = type('Student', (object,), class_dict)  # 传入类名,传入父类,传入名称空间
obj = cls()
print(obj.__class__)  # <class '__main__.Student'>
print(obj.name)  # leethon

这个过程很麻烦,而且和class关键字所实现的还有差距,所以这里只做了解。

即我们的类实际上都是由元类产生的,底层就是用了一个type方法。

通过元类控制类的产生

因为类是由元类产生的,所以可以理解为类实际上就是元类进行实例化产生的对象,

联系对比:

  • 对象–通过类名()的方式产生–触发了类体中__init__得到了独有的属性
  • 类—通过元类产生–触发了元类中的__init__得到了每个类所独有的类属性

所以我们可以通过派生元类中的__init__方法达到控制类产生的目的。

那么派生自然要用到继承和super关键字,将元类type作为父类得到的类也是元类,因为它有产生类的方法。

class MyMetaClass(type):
    def __init__(cls, what, bases=None, dict=None):
        if not what.istitle():  # 如果类名不大写开头
            raise TypeError(f'类定义必须要大写,你看看你写的{what}')  # 报错终止程序
        # 如果符合开头大写的命名风格就继续执行
        super().__init__(what, bases, dict)  # 继承父类中的方法

class A(metaclass=MyMetaClass):  # 修改默认元类变为我们的元类,也修改了产生本类的方式
    pass
class bbb(metaclass=MyMetaClass):  # 在这里报错,TypeError: 类定义必须要大写,你看看你写的bbb
    pass

如果我们选择修改产生类的元类(通过修改metaclass关键字参数的方式),那么这个元类可以是我们基于type派生的,我们通过派生type的双下init方法就可以控制类的产生过程了。

通过元类控制类产生对象

看标题有些相似,但要注意区分,上一小节指控制类的产生过程,这一节要说明对象的产生过程。

  • 我们在上一篇博客中提到,类的魔法方法__call__是在对象被加括号调用时自动触发的。

  • 那么将类看成对象,产生它的元类中的__call__也就会在类名加括号调用时自动触发。

也就是我们平常习以为常的obj = 类名()的方式产生对象的过程会自动触发元类中的__call__

理解了上面这一点后,我们就可以尝试从元类的__call__做手脚,来控制对象的产生了。

我们可以直接通过派生的方式修改,也可以直接重写的一个call

一般来说类的元类都是type,它的__call__,主要做了以下几件事:

class MyMetaClass(type):
    def __call__(cls, *args, **kwargs):
        # 1.就是产生一个空对象
        obj = cls.__new__(cls)
        # 2.调用类的init传入对象和参数
        cls.__init__(obj, *args, **kwargs)
        # 3.返回创建好的对象
        return obj
# 以上的__call__只是对type中的简单模仿,建议还是直接用super派生

class A(metaclass=MyMetaClass):  # 修改默认元类变为我们的元类,也修改了产生本类的方式
    pass


obj = A()  # 触发了MyMetaClass的__call__
print(obj)  # <__main__.A object at 0x0000025DBB436610>
让产生对象时只能传入关键字参数
# 实现方式1
class MyMetaClass(type):
    def __call__(cls, *args, **kwargs):
        if args:
            raise TypeError('你只能传入关键字参数')  # 在这一步控制了只能传入关键字参数
        return super().__call__(*args, **kwargs)  # 自动传入调用的对象,方法是父类的方法
		# 并将值返回出去,与原本call的结构一致

class A(metaclass=MyMetaClass):  # 修改默认元类变为我们的元类,也修改了产生本类的方式
    def __init__(self, name):
        self.name = name


obj = A('leethon')
print(obj.name)

# 实现方式2(不建议)
class MyMetaClass(type):
    def __call__(cls, *args, **kwargs):
        # 1.就是产生一个空对象
        obj = cls.__new__(cls)
        # 2.调用类的init传入对象和参数
        if args:
            raise TypeError('你只能传入关键字参数')  # 在这一步控制了只能传入关键字参数
        cls.__init__(obj, *args, **kwargs)
        # 3.返回创建好的对象
        return obj

原文地址:http://www.cnblogs.com/Leethon-lizhilog/p/16870479.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性