Python学习笔记--自定义元类

news2024/11/20 0:31:59

四、自定义元类

到现在,我们已经知道元类是什么鬼东西了。

那么,从始至终我们还不知道元类到底有啥用。

只是了解了一下元类。

在了解它有啥用的时候,我们先来了解下怎么自定义元类。

因为只有了解了怎么自定义才能更好的理解它的作用。

首先我们来了解下 __metaclass__ 属性

metaclass,直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?

那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass 允许你创建类或者修改类。

换句话说,你可以把类看成是 metaclass 创建出来的“实例”。

class MyObject(object):
    __metaclass__ = something…
[]

如果是这样写的话,Python 就会用元类来创建类 MyObject。

当你写下 class MyObject(object),但是类对象 MyObject 还没有在内存中创建。P

ython 会在类的定义中寻找 __metaclass__ 属性,如果找到了,Python 就会用它来创建类 MyObject,如果没有找到,就会用内建的 type 函数来创建这个类。如果还不怎么理解,看下下面的流程图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

再举个实例:

class Foo(Bar):
    pass

它的判断流程是怎样的呢?

首先判断 Foo 中是否有 __metaclass__ 这个属性?如果有,Python 会在内存中通过 __metaclass__ 创建一个名字为 Foo 的类对象(注意,这里是类对象)。如果 Python 没有找到__metaclass__ ,它会继续在 Bar(父类)中寻找__metaclass__ 属性,并尝试做和前面同样的操作。如果 Python在任何父类中都找不到 __metaclass__ ,它就会在模块层次中去寻找 __metaclass__ ,并尝试做同样的操作。如果还是找不到 __metaclass__ ,Python 就会用内置的 type 来创建这个类对象。

其实 __metaclass__ 就是定义了 class 的行为。类似于 class 定义了 instance 的行为,metaclass 则定义了 class 的行为。可以说,class 是 metaclass 的 instance。

现在,我们基本了解了 __metaclass__ 属性,但是,也没讲过如何使用这个属性,或者说这个属性可以放些什么?

答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到 type 或者子类化 type 的东东都可以。

元类的主要目的就是为了当创建类时能够自动地改变类。

通常,你会为API 做这样的事情,你希望可以创建符合当前上下文的类。假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定__metaclass__ 。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

幸运的是,__metaclass__ 实际上可以被任意调用,它并不需要是一个正式的类。所以,我们这里就先以一个简单的函数作为例子开始。

# 元类会自动将你通常传给‘type’的参数作为自己的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr):
    '''返回一个类对象,将属性都转为大写形式'''
    #  选择所有不以'__'开头的属性
    attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
 
# 通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr)
 
__metaclass__ = upper_attr  
#  这会作用到这个模块中的所有类
 
class Foo(object):
    # 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
    bar = 'bip'
print hasattr(Foo, 'bar')
# 输出: False
print hasattr(Foo, 'BAR')
# 输出:True
 
f = Foo()
print f.BAR
# 输出:'bip'

用 class 当做元类的做法:

# 请记住,'type'实际上是一个类,就像'str'和'int'一样
# 所以,你可以从type继承
class UpperAttrMetaClass(type):
    # __new__ 是在__init__之前被调用的特殊方法
    # __new__是用来创建对象并返回之的方法
    # 而__init__只是用来将传入的参数初始化给对象
    # 你很少用到__new__,除非你希望能够控制对象的创建
    # 这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__
    # 如果你希望的话,你也可以在__init__中做些事情
    # 还有一些高级的用法会涉及到改写__call__特殊方法,但是我们这里不用
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return type(future_class_name, future_class_parents, uppercase_attr)

但是,这种方式其实不是 OOP。我们直接调用了 type,而且我们没有改写父类的 __new__ 方法。现在让我们这样去处理:


class UpperAttrMetaclass(type):
    def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr):
        attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
 
        # 复用type.__new__方法
        # 这就是基本的OOP编程,没什么魔法
        return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)

你可能已经注意到了有个额外的参数 upperattr_metaclass ,这并没有什么特别的。类方法的第一个参数总是表示当前的实例,就像在普通的类方法中的 self 参数一样。当然了,为了清晰起见,这里的名字我起的比较长。但是就像 self 一样,所有的参数都有它们的传统名称。因此,在真实的产品代码中一个元类应该是像这样的:

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')
        uppercase_attr  = dict((name.upper(), value) for name, value in attrs)
        return type.__new__(cls, name, bases, uppercase_attr)

如果使用 super 方法的话,我们还可以使它变得更清晰一些,这会缓解继承(是的,你可以拥有元类,从元类继承,从 type 继承)

class UpperAttrMetaclass(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)

通常我们都会使用元类去做一些晦涩的事情,依赖于自省,控制继承等等。确实,用元类来搞些“黑暗魔法”是特别有用的,因而会搞出些复杂的东西来。但就元类本身而言,它们其实是很简单的:

  • 拦截类的创建
  • 修改类
  • 返回修改之后的类

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

es使用客户端,“grunt” 不是内部或外部命令,多种解决方法

”grunt“不是内部或外部命令,也不是可运行的程序 或批处理文件。 4、问题排查 查看node的安装根目录 npm root -g 在运行grunt -version还是不行 网上找了很多,给出正确解决方案的没几个,所以自己摸索,最后确定了加环境变量的解…

python使用selenium webDriver时 报错

可能原因和解决: 1. python 解释器 ----> 设置 2. 浏览器版本 与 浏览器驱动版本不一致 ----> 安装同一版本的 (下载chromedriver | 谷歌驱动更高版本的测试版) 参考:Python使用Selenium WebDriver的入门介绍及安装教程-CSDN博客 Selenium安…

刷题学习记录(攻防世界)

wife_wife 一拿到题目就提示这题不用爆破 进入环境得到的是一个登录框 随便试了一下登录账户密码会提示错误,那就去注册账户,注册的账户还有注册管理员的选项 先注册普通用户234,注册好后登录 这样就得到flag,但是提交是错误的&a…

Linux设置禁止SSH空密码登录

为什么要禁止SSH空密码登陆? 禁止SSH空密码登录的原因是出于安全考虑。如果允许使用空密码进行SSH登录,那么任何人都可以通过尝试使用空密码来尝试登录到系统,从而获取系统的访问权限,这显然是非常不安全的。 此外,使…

FullCalendar日历插件说明文档

一般标准配置 属性描述默认值header设置日历头部信息。 如果设置为false,则不显示头部信息。包括left,center,right左中右三个位置,每个位置都可以对应以下不同的配置: title: 显示当前月份/周/日信息 prev: 用于切换到上一月/周…

蓝桥杯 map

map 代码示例 #include<iostream> #include<map> using namespace std; int main(){//创建并初始化mapmap<int,string> myMap{{1,"Apple"},{2,"Banana"},{3,"Orange"}} ;//插入元素myMap.insert(make_pair(4,"Grapes&qu…

Simulink 自动代码生成电机控制:模型预测控制(MPC)模块使用总结(2)

目录 设置预测范围对系统的影响 设置输入输出约束对系统的影响 设置权重对系统的影响 调整Close-Loop Performance 和State Estimation效果 检查MPC设计是否合理 总结 设置预测范围对系统的影响 预测范围越小&#xff0c;响应越快&#xff0c;预测性能越差&#xff0c;系统稳…

Javascript if 语句使用详解

Javascript if 语句使用详解 目录 Javascript if 语句使用详解 一、if语句 二、if……else语句 三、if……else if语句 if语句是使用最为普遍的条件选择语句&#xff0c;每一种编程语言的if语句都差不多。if语句类型共有3种&#xff1a; &#xff08;1&#xff09;if语句&…

黔院长 | 何为风邪?

中医上所说的风&#xff0c;也称风邪&#xff0c;是指受到外界侵犯&#xff08;外邪&#xff09;而感得风寒、风热、风湿等症状&#xff0c;导致人的免疫力下降。寒、湿、燥、暑、热等都属于外邪&#xff0c;多依附于风而入侵人体&#xff0c;因此风邪更多的是指一种致病因素。…

LED裸机

关于STM32的GPIO的初始化&#xff1a; 初始化&#xff1a; 时钟&#xff1b;输出/输入功能、上拉、速度等等&#xff1b;设置复用功能等等 回顾GPIO和IO&#xff1a; GPIO&#xff1a; 通用IO口。嵌入式系统中常常有数量众多&#xff0c;但是结构却比较简单的外部设备/电路&a…

Python系列:如何提高python程序代码的健壮性

前言 在编程的时候,我们难免会遇到一些不可靠的情况,比如网络请求失败,数据库连接超时等等。这些不确定性会让我们的程序容易出现各种错误和异常。那么如何来增加程序的容错性和健壮性呢? 可能大多数人会想到使用try except来进行异常捕捉进行失败重试(Retry)。虽然try-esc…

lvgl 画好一个圆弧arc 要了解的相关知识

目录 一、概述 1. css盒子模型示意图2. 圆弧的理解对象3. lvgl 版本 二、功能细节 1. 圆弧的角度 1.1 圆弧的0度在哪里&#xff1f;1.2 设置圆弧角度的1.3 设置圆弧的背景角度 2. 圆弧的半径3. 圆弧的圆角 3.1 设置圆弧的圆角 效果如下&#xff1a; 4. 圆弧的宽度 4.1圆弧主体…

基于SSM的宠物医院管理系统

基于SSM的宠物医院管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringMyBatisSpringMVC工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 后台 摘要 随着人们对宠物健康关注的增加&#xff0c;宠物医疗服务的需求也…

云端力量:探索亚马逊云服务器,提升您的业务无限可能

文章目录 前言亚马逊云服务器简介亚马逊云服务器的优势使用步骤Amazon EC2 实例类型常见问题 总结 前言 随着大数据、人工智能和物联网等技术的飞速发展&#xff0c;企业对于高效、灵活和可扩展的计算需求日益增强。在这样的背景下&#xff0c;亚马逊云服务器 以其卓越的性能、…

TSINGSEE青犀视频平台EasyCVR如何搭建自然保护区视频监控系统

随着经济的发展与城市化进程&#xff0c;很多国家自然保护区不断被破坏&#xff0c;为了对国家自然保护区进行全面管理&#xff0c;加大保护区的防护管理力度&#xff0c;人工管理工作量十分繁重&#xff0c;并会存在一些漏洞。因此在国家自然保护区建立一套完整的视频监控系统…

(免费)双相情感障碍筛查MDQ 在线测试双向情感障碍

MDQ用于筛查双相障碍&#xff0c;主要包含13个关于双相障碍症状的是非问题&#xff0c;当前测试采用的量表为2010年杨海晨博士翻译版。该量表为目前世界范围内最常用的双相障碍筛查量表&#xff0c;目前在精神科门诊最为常用的量表之一。 双向情感障碍筛查量表&#xff0c;也叫…

提取纯色马赛克

​​​​​​ 图像预处理 将彩色图像加载到内存。 转换图像为灰度图像&#xff0c;以简化处理。 对灰度图像应用二值化处理&#xff0c;将图像中的纯色区域分为前景和背景。这可以使用阈值处理来完成。 轮廓检测 使用轮廓检测算法&#xff0c;例如OpenCV的 findContours 函数&…

①Redis String 字符串类型【命令汇总】

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ Redis String ①Redis String 字符串类型【命令…

Linux 无名管道实现文件复制

无名管道 通过一个管道&#xff08;假象&#xff09;进行传输数据&#xff0c;但是这个管道的传输方式是单工&#xff08;半双工&#xff09;的&#xff0c;就是这个管道允许进行发送和接受数据&#xff0c;不过不能同时进行。 创建无名管道 这里用到一个pipe&#xff08;&…

微软Ignite 2023大盘点:GPT-4 Turbo、DALL-E 3等

11月16日&#xff0c;微软在美国西雅图举办“Microsoft Ignite 2023”全球开发者大会。ChatGPT等生成式AI成为了本次大会的重要主题。 下面「AIGC开放社区」将根据大会发布的内容&#xff0c;盘点重要内容。 GPT-4 Turbo、 DALL E 3等全新模型 16K上下文的新GPT-3.5 Turbo模…