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 = name
    def 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 = name
        self.age = age
student_one = Student("bruce", 18)
# TypeError: 实例化必须通过关键字传参
student_two = Student(name="tom", age=22)
print(student_two.name, student_two.age)
# tom 22

【三】总结

请添加图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如果需求不完整或模糊,如何进行功能点估算?

在项目早期进行功能点估算&#xff0c;经常会遇到需求不完整或模糊的情况。这让人无法准确理解用户需求&#xff0c;这会增加项目本身的风险&#xff0c;对功能点估算也造成影响&#xff0c;可能会影响到项目的规模和工作量。因此在遇到此种情况&#xff0c;一般可以采取如下措…

linux线程重启

以下是获取线程id和重启指定线程的示例代码&#xff1a; #include <stdio.h> #include <pthread.h>// 线程函数&#xff0c;用来打印线程ID void *print_thread_id(void *arg) {printf("Thread ID: %lu\n", pthread_self());return NULL; }int main() {…

【VRTK】【Unity】【VR开发】Linear Drives

课程配套学习项目源码资源下载 https://download.csdn.net/download/weixin_41697242/88485426?spm=1001.2014.3001.5503 【概述】 前面一篇讨论了角度运动机制,本篇讨论线性运动机制。和角度运动机制类似,线性运动机制提供了更为仿真的互动机制。也分为基于物理的和不基于…

TCP三次握手过程?

TCP三次握手过程&#xff1f; 分享 回答 1 浏览 3662 一颗小胡椒 2 CISM-WSE CISP-PTS 三次握手是 TCP 连接的建立过程。在握手之前&#xff0c;主动打开连接的客户端结束 CLOSE 阶段&#xff0c;被动打开的服务器也结束 CLOSE 阶段&#xff0c;并进入 LISTEN 阶段。随后进入…

大创项目推荐 深度学习图像风格迁移

文章目录 0 前言1 VGG网络2 风格迁移3 内容损失4 风格损失5 主代码实现6 迁移模型实现7 效果展示8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习图像风格迁移 - opencv python 该项目较为新颖&#xff0c;适合作为竞赛课题…

代码训练营Day.28 | 93. 复原IP地址、78. 子集、90. 子集II

93. 复原IP地址 1. LeetCode链接 . - 力扣&#xff08;LeetCode&#xff09; 2. 题目描述 3. 解法 字符串切四刀&#xff0c;最后一刀必须是在末位。 麻烦的地方在于文本的各种限制条件、剪枝等等。 class Solution { public:vector<string> results;string result…

java基础 - 01 java集合框架概述以及Iterable接口和Collection简单介绍

最近在开发过程中&#xff0c;发现自己对java集合的了解已经忘得差不多了&#xff0c;作为开发者&#xff0c;这可不是一件好事哈&#xff0c;之前开始学习java基础的时候&#xff0c;学过一段时间的java集合&#xff0c;但是现在到了工作岗位上的时候&#xff0c;发现自己用到…

K8S部署GitLab

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

【数据结构】二叉树链式结构详解

目录 1.前言2.快速创建一颗二叉树3.二叉树的遍历3.1前序遍历3.2中序遍历3.3后序遍历3.4层序遍历 4.二叉树节点个数与高度4.1二叉树节点个数4.2二叉树叶子节点个数4.3二叉树高度4.4二叉树第k层节点个数4.5二叉树查找值为x的节点 5.二叉树的基础oj题练习6.二叉树的创建和销毁6.1通…

【⭐AI工具⭐】AI工具导航推荐

目录 零 工具导航&#x1f449;【[AI工具集导航](https://ai-bot.cn/)】&#x1f448;&#x1f449;【[iForAI](https://iforai.com/)】&#x1f448;&#x1f449;【[AInav](https://www.ainav.cn/)】&#x1f448;&#x1f449;【[Navi AI 导航](https://www.naviai.cn/)】&a…

PhpPythonC++圆类的实现(OOP)

哎......被投诉了 &#x1f62d;&#x1f62d;&#x1f62d;&#x1f62d;&#x1f62d; 其实也不是小编不更&#xff0c;这不是期末了吗&#xff08;zhaojiekou~~&#xff09;&#xff0c;而且最近学的信息收集和ctf感觉好像没找到啥能更的&#xff08;不过最经还是在考虑更一…

Flink CDC使用

Flink 环境准备 Flink 版本对应的CDC版本 两个jar包上传到flink bin目录下 flink-sql-connector-mysql-cdc mysql-connector-java 重启Flink集群

体系结构汇总复习(练习题)

1.MSI cache一致性协议问题 题解引用自&#xff1a;MSI cache一致性协议_假设在一个双cpu多处理器系统中,两个cpu用单总线连接,并且采用监听一致性协议(msi-CSDN博客 答&#xff1a; 事件A状态B状态初始状态IICPU A读SICPU A写MICPU B写IMCPU A读SS 接下来分析CPU A/B中各自c…

【Verilog】运算符

系列文章 数值&#xff08;整数&#xff0c;实数&#xff0c;字符串&#xff09;与数据类型&#xff08;wire、reg、mem、parameter&#xff09; 系列文章算术运算符关系运算符相等关系运算符逻辑运算符按位运算符归约运算符移位运算符条件运算符连接和复制运算符 算术运算符 …

React 入门 - 05(响应式与事件绑定)

本章内容 目录 一、响应式设计思想二、React 中的事件绑定 继上一节我们简单实现一个 TodoList来更加了解编写组件的一些细节。本节继续这个案例功能的完成。 一、响应式设计思想 1、在原生的 JS中&#xff0c;如果要实现点击”提交“按钮就将输入框的内容添加至页面列表中&…

【OpenVINO】 使用 OpenVINO CSharp API 部署 PaddleOCR 项目介绍

前言&#xff1a; 在之前的项目中&#xff0c;我们已经使用 OpenVINO TM CSharp API 部署 PaddleOCR 全系列模型&#xff0c;但随着PaddleOCRv4版本发布以及OpenVINO CSharp API版本迭代&#xff0c;上一版本的项目已经不再适用。因此在推出的最新项目中&#xff0c;已经完成了…

6款实用的Git可视化管理工具

前言 俗话说得好“工欲善其事&#xff0c;必先利其器”&#xff0c;合理的选择和使用可视化的管理工具可以降低技术入门和使用门槛。我们在团队开发中统一某个开发工具能够降低沟通成本&#xff0c;提高协作效率。今天给大家分享6款实用的Git可视化管理工具。 Git是什么&…

QT基础篇(1)QT概述

1.什么是QT QT是一个跨平台的C应用程序开发框架。它提供了一套丰富的图形用户界面&#xff08;GUI&#xff09;和多媒体功能&#xff0c;可以用于开发各种类型的应用程序&#xff0c;包括桌面应用程序、移动应用程序和嵌入式系统。QT具有易于使用、可定制性强、性能高等特点&a…

DelayQueue原理探究

DelayQueue并发队列是一个无界阻塞延迟队列&#xff0c;队列中的每个元素都有个过期时间&#xff0c;当从队列获取元素时&#xff0c;只有过期元素才会出队列。队列头元素是最快要过期的元素。 DelayQueue类图结构 由该图可知&#xff0c;DelayQueue内部使用PriorityQueue存放…

doris部署

doris-2.0.1.1部署安装 一、下载doris安装包二、解压到/data下&#xff0c;修改名称三、修改fe配置文件四、启动doris-fe五、验证doris-fe六、修改be配置文件七、启动doris-be八、mysql中连接be&#xff0c;在Doris中添加后端节点九、设置密码 一、下载doris安装包 wget https…