Python 面向对象之元类

Python 面向对象之元类

【一】一切皆对象

【1】元类

  • 元类(metaclass是Python中用于创建类的类
  • 在Python中,类是对象,而元类就是类的类
  • 它们控制类的创建过程,允许你定制类的行为
  • Python中内置的默认元类是type
  • 我们用class关键字定义的所有类以及内置的类都是有元类type实例化产生

【2】class机制

  • class是Python的关键字,目的用来创建类
  • 它在底层实现类的定义本质上有四个步骤
    1. 获取类名
    2. 获取基类
    3. 获取类的名称空间
    4. 调用元类type实例化产生所定义的新类
# 使用class关键字创建
class PeaShooter(object):owner = "戴夫"def __init__(self, name):self.name = namedef introduce(self):print(f"I`m {self.name}")print(PeaShooter.__dict__)
# {'__module__': '__main__', 'owner': '戴夫', '__init__': <function PeaShooter.__init__ at 0x000002534E353AC0>, 'introduce': <function PeaShooter.introduce at 0x000002534E639EA0>, '__dict__': <attribute '__dict__' of 'PeaShooter' objects>, '__weakref__': <attribute '__weakref__' of 'PeaShooter' objects>, '__doc__': None}
# 使用type函数创建
# 类名
class_name = "PeaShooter"
# 基类
class_bases = (object,)
# 类的名称空间
class_dict = {}
class_body = """
owner = "戴夫"
def __init__(self, name):self.name = name
def introduce(self):print(f"T`m {self.name}")    
"""
# 类的属性和方法存储在class_dict里面. 第二个参数{}控制代码的执行环境
exec(class_body, {}, class_dict)
# 使用type函数创建
PeaSHooter = type(class_name, class_bases, class_dict)print(PeaSHooter.__dict__)
# {'owner': '戴夫', '__init__': <function __init__ at 0x0000017E39653E20>, 'introduce': <function introduce at 0x0000017E39783AC0>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'PeaShooter' objects>, '__weakref__': <attribute '__weakref__' of 'PeaShooter' objects>, '__doc__': None}

【3】如何自定义元类

  • 元类的作用是控制类的创建
    • 也就意味着我们可以定制自己的类的具体行为
  • class机制默认的元类是type(metaclass=type
    • 也就是说我们修改metaclass参数就可以自定义元类
  • 元类是一切类的基石,所以我们自定义的元类就必须继承type
    • 目的在于使用元类的大部分功能,仅定制我们需要的那部分功能
# 自定义元类
class MyMeta(type):pass# 普通的类,元类默认是type
class PeaShooter(object, metaclass=type):pass# 普通自定义的类,元类是Shooter
class PeaShooter(object, metaclass=Mymeta):pass

【二】自定义元类控制自定义类

【1】实例化对象的本质

  • 我们知道类的实例化会触发魔法方法__new____init__
    • __new__:申请空间,创建并返回一个空对象
    • __init__:接收new创建的空对象,并初始化这个空对象
  • 我们知道实例的调用会触发魔法方法__call__
    • __call__:当对象被调用时触发

【2】控制自定义类的类名、继承关系、名称空间

(1)讲解

# 类名
class_name = "PeaShooter"
# 类的基类
class_bases = (object, )
# 类的名称空间
class_dict = {}# 使用元类创建类
PeaShooter1 = type(class_name, class_bases, class_dict)# 使用自定义元类创建类
class MyMeta(type):pass
PeaShooter2 = MyMeta(class_name, class_bases, class_dict)
  • type是Python的内置元类,而使用type创建类就是通过元类type进行类的实例化过程,即元类的实例化会自动触发魔法方法__init____new__
  • 使用元类type或者自定义元类MyMeta需要传入参数类名class_name、基类class_bases、名称空间class_dict
  • 所以可以通过自定义的元类中的魔法方法__init____new__来控制类名、基类、名称空间这些参数
  • 让我们来看看代码的执行执行结果
class MyMeta(type):def __init__(cls, what, bases, dict):print("自定义元类的魔法方法init")print(f"参数cls:{cls}")print(f"参数what:{what}")print(f"参数bases:{bases}")print(f"参数dict:{dict}")super().__init__(what, bases, dict)def __new__(cls, *args, **kwargs):print("自定义元类的魔法方法new")print(f"参数cls:{cls}")print(f"参数args:{args}")print(f"参数kwargs:{kwargs}")return super().__new__(cls, *args, **kwargs)# 等价于 PeaShooter = MyMeta("PeaShooter", (object, ), {})
class PeaShooter(object, metaclass=MyMeta):pass
# 自定义元类的魔法方法new
# 参数cls:<class '__main__.MyMeta'>
# 参数args:('PeaShooter', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'PeaShooter'})
# 参数kwargs:{}
# 自定义元类的魔法方法init
# 参数cls:<class '__main__.PeaShooter'>
# 参数what:PeaShooter
# 参数bases:(<class 'object'>,)
# 参数dict:{'__module__': '__main__', '__qualname__': 'PeaShooter'}
(2)应用
  • 要求:自定义元类来控制创建新类

    • 类名必须含有射手(Shooter)

    • 类名首字母大写

    • 如果没有基类默认为object

    • 必须要有注释文档

  • 小坑:不可以直接使用istitle()判断首字母大写,因为这个函数会要求其他字母都是小写

class MyMeta(type):def __init__(cls, class_name:str, class_bases, class_dict):if "Shooter" not in class_name:raise NameError("必须含有Shooter")elif not class_name[0].istitle():print(class_name, type(class_name))raise NameError("必须首字母大写")elif not cls.__doc__:raise ValueError("类必须有注释文档")if not class_bases:class_bases = (object, )super().__init__(class_name, class_bases, class_dict)def __new__(cls, *args, **kwargs):return super().__new__(cls, *args, **kwargs)
class iceShoot(metaclass=MyMeta):pass
# NameError: 必须含有Shooter
class iceShooter(metaclass=MyMeta):pass
# NameError: 必须首字母大写
class IceShooter(metaclass=MyMeta):pass
# ValueError: 类必须有注释文档
class IceShooter(metaclass=MyMeta):"""这是寒冰射手"""pass
print(IceShooter.__mro__)
# (<class '__main__.IceShooter'>, <class 'object'>)

【3】控制自定义类实例化的括号内容

(1)讲解
class MyMeta(type):def __call__(self, *args, **kwargs):print("这是自定义元类的魔法方法call")print(f"参数self:{self}")print(f"参数args:{args}")print(f"参数kwargs:{kwargs}")return super().__call__(*args, **kwargs)class A(metaclass=MyMeta):def __init__(self, *args, **kwargs):print("这是根据自定义元类创建新类的魔法方法init")print(f"参数self:{self}")print(f"参数args:{args}")print(f"参数kwargs:{kwargs}")def __call__(self, *args, **kwargs):print("这是根据自定义元类创建新类的魔法方法call")print(f"参数self:{self}")print(f"参数args:{args}")print(f"参数kwargs:{kwargs}")
a = A("bruce", age=18)
a("tom", age=16)
# 这是自定义元类的魔法方法call
# 参数self:<class '__main__.A'>
# 参数args:('bruce',)
# 参数kwargs:{'age': 18}
# 这是根据自定义元类创建新类的魔法方法init
# 参数self:<__main__.A object at 0x00000200C1787DF0>
# 参数args:('bruce',)
# 参数kwargs:{'age': 18}
# 这是根据自定义元类创建新类的魔法方法call
# 参数self:<__main__.A object at 0x00000200C1787DF0>
# 参数args:('tom',)
# 参数kwargs:{'age': 16}
  • 我们来看看这个例子:
  • a = A("bruce", age=18)
    • 自定义的类(A)是根据自定义的元类(MyMeta)创建出来的
    • 所以这里会执行自定义元类(MyMeta)的魔法方法__call__
    • 然后将参数传递给自定义类的魔法方法___init__
    • 这两部内容是一样的,并且必须要有返回值,即将生成的类返回给实例a
    • 综上所述:我们可以通过自定义元类(MyMeta)的魔法方法__call__来控制自定义类的括号内容
  • a("tom", age=16)
    • 实例a使用括号调用了自定义类中的__call__方法,这是显而易见的,不需要解释
(2)应用
  • 要求:自定义元类来控制创建新类
    • 实例化的参数必须通过关键字参数传参
    • 不能通过位置关键字传参
class MyMeta(type):def __call__(cls, *args, **kwargs):if len(args):raise TypeError("实例化必须通过关键字传参")return super().__call__(*args, **kwargs)class Student(metaclass=MyMeta):def __init__(self, name, age):self.name = nameself.age = age
student_one = Student("bruce", 18)
# TypeError: 实例化必须通过关键字传参
student_two = Student(name="tom", age=22)
print(student_two.name, student_two.age)
# tom 22

【三】总结

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/334522.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

团结引擎 | 发布微信小游戏的那些坑

问题1 问题&#xff1a;Failed to download file Build/安装包.framework.js.unityweb. Loading web pages via a file:// URL without a web server is not supported by this browser. Please use a local development web server to host content, or use the Build and Ru…

MiniTab的正态性检验结果的分析

正态性检验概述 可使用 正态性检验 确定数据是否不服从正态分布。 执行菜单&#xff1a;要执行正态性检验&#xff0c;请选择统计 > 基本统计 > 正态性检验。 正态性检验 的假设 对于正态性检验&#xff0c;进行如下假设。 H0&#xff1a;数据服从正态分布。H1&…

什么是博若莱新酒节?

在红酒圈儿里混&#xff0c;一定不能不知道博若莱新酒节&#xff0c;这是法国举世闻名的以酒为主题的重要节日之一。现已成为世界范围内庆祝当年葡萄收获和酿制的节日&#xff0c;被称为一年一度的酒迷盛会。 云仓酒庄的品牌雷盛红酒LEESON分享博若莱位于法国勃艮第南部&#x…

静态关键字:static

static的作用 static是静态的意思&#xff0c;可以修饰成员变量和成员方法。 static修饰成员变量表示该成员变量只在内存中只存储一份&#xff0c;可以被共享访问、修改。 成员变量 分为2类 静态成员变量&#xff08;有static修饰&#xff0c;属于类&#xff0c;内存中加载…

智汇云舟受邀出席《城市轨道交通公共安全防范安全评价标准》专家评审会

1月3日&#xff0c;由中国城市公共交通协会归口的《城市轨道交通公共安全防范安全评价标准》&#xff08;以下简称“《标准》”&#xff09;送审稿审查会顺利召开。该标准由同方威视技术股份有限公司、上海新海信通信息技术有限公司和中安保实业集团有限公司主编&#xff0c;北…

Spring事务控制见解6

7.Spring事务控制 7.1.事务介绍 7.1.1.什么是事务&#xff1f; 当你需要一次执行多条SQL语句时&#xff0c;可以使用事务。通俗一点说&#xff0c;如果这几条SQL语句全部执行成功&#xff0c;则才对数据库进行一次更新&#xff0c;如果有一条SQL语句执行失败&#xff0c;则这…

如何脱离keil在vscode上实现STM32单片机编程

【VScode Embedded IDE】Keil工程导入VScode&#xff0c;与Keil协同开发MCU_vscode编辑keil工程-CSDN博客 从零开始的51单片机——VsCodeEIDE环境搭建_vscodeeidesdcc-CSDN博客 结合一下这两个大佬的博客就是可以实现STM32的编程了 主要要点&#xff1a; &#xff08;1&#…

【Python机器学习】用于回归的决策树

用于回归的决策树与用于分类的决策树类似&#xff0c;在DecisionTreeRegressor中实现。DecisionTreeRegressor不能外推&#xff0c;也不能在训练数据范围之外的数据进行预测。 利用计算机内存历史及格的数据进行实验&#xff0c;数据展示&#xff1a; import pandas as pd im…

破解linux系统密码

破解密码&#xff1a; 虚拟机重启的时候按e键 按↓键&#xff0c;找到以linux16开头的行&#xff0c;在最后加上rd.break 按ctrlx进入救援模式 重新挂载/sysroot为可读写模式&#xff0c;并切换根目录为/sysroot mount -o remount,rw /sysroot chroot /sysroot 设置密码 passwd…

【方法】如何修改Word文档密码?

我们在生活和工作中经常会使用到Word&#xff0c;有时候需要保护Word文档&#xff0c;还会设置密码&#xff0c;那如果后续想要修改密码&#xff0c;要怎么操作呢&#xff1f; 下面来看看Word文档常用的3种密码是如何修改的。 一、打开密码 想要修改“打开密码”&#xff0c…

《亚太教育》是什么级别的期刊?是正规期刊吗?能评职称吗?

《亚太教育》主要发表教育理论研究、教育教学实践、学校管理、学科教育、科研管理等学术论文以及其他与教育教学相关的学术论文和研究成果,现征集教育管理以及各学科优秀论文。欢迎.各位教师、教育工作者及高校学生踊跃投稿。 收录情况&#xff1a;知网万方维普收录 投稿方式&a…

使用postman发送请求,提示Content type ‘multipart/form-data not supported

1、原因&#xff1a;后端通过RequestBody 的方式接收参数&#xff0c;不能通过form-data方式传参&#xff0c;每一个请求必须是通过实体对象进行传参&#xff0c;需要改为使用json传参