Python元类(metaclass)

news2025/1/25 4:39:56

Python 是一种强大的编程语言,一部分得益于其语言设计中独特的元类(Metaclass)机制。尽管元类的概念在刚开始接触时可能会让人感到困惑,但一旦理解了它们的工作原理和应用方式,我们就可以用它们做出强大且灵活的抽象。

元类的定义

在 Python 中,一切皆对象,包括类本身。类定义了对象的行为,而元类则定义了类的行为。简而言之,元类就是创建类的“类”。

符合Python对象的条件是:

  1. 能够直接赋值给一个变量
  2. 可以添加到集合对象中
  3. 能作为函数参数进行传递
  4. 可以作为函数返回值

从这里,大家就可以看出,Python中一切皆为对象,一切都符合这些条件

Python对象都会有三个特征

  • 身份,即是存储地址,可以通过id()方法查询
  • 类型,即对象所属的类型,可以用type()方法来查询
  • 值,都会有各自的数据
class Job(object):
    pass


j = Job()
print(j) #<__main__.Job object at 0x10f4f9750>
print(id(j)) #4553611344
print(j.__class__) #<class '__main__.Job'>
print(Job) #<class '__main__.Job'>
print(id(Job)) #140637422913424
print(Job.__class__) #<class 'type'>
print(type(Job)) #<class 'type'>
print(Job.__base__) #<class 'object'>
print(Job.mro()) #[<class '__main__.Job'>, <class 'object'>]
print(id(Job.__class__)) #4373786024
print(type(Job.__class__)) #<class 'type'>
print(type(int)) #<class 'type'>
print(type.__base__) #<class 'object'>

从上面的结果我们看看出来:

  • j是Job类的实例
  • j.__class__和Job是等价的,都表示Job类型
  • Job.__class__与type(Job)是等价的
  • 类型本身也是一个对象,也有id
  • Job作为对象,他的类型是type,或者说,Job是type的实例
  • Job作为类型,Job继承于object
  • 而类型type同时也是int等其他类型对象的类型,他也有自己的id,说明他也是一个对象,type的类型是type本身
  • type作为类型是object的子类

可以看出来,我们以前学习的所有的类型都是type类型的实例(对象),或者说,都是由type类型创建的类对象,我们把type类型称为元类。

type、class、object的关系如下:

我们也可以自己构建元类,改变类的创建方式。

通过继承 type 类就可以自己定义元类,元类应该定义一个 __new__ 方法。这个方法负责接收类定义的参数,并返回一个类对象。下面是一个简单的元类示例:

class MyMeta(type):
    def __new__(cls, name, bases, dct):
        x = super().__new__(cls, name, bases, dct)
        x.attr = 100
        return x

class MyClass(metaclass=MyMeta):
    pass

print(MyClass.attr)
# 输出: 100

print(type(int)) #<class 'type'>
print(type(MyClass)) #<class '__main__.MyMeta'>
print(MyClass.__base__) #<class 'object'>

上面的例子可以看到:

  • 已经将MyClass的类型改为MyMeta
  • MyClass的基类仍然是object
  • 自定义的元类中,添加了一个属性 attr,使用 MyMeta 作为元类,定义了一个类 MyClass,MyClass 确实拥有了 attr 属性。

正常的类都是由type创建出来的,但我们也可以创建自己的元类,让他继承自type,并设定某个类使用它.而这个类的子类也就同样是这个元类产生出的类.一个基本的元类如下:

class MetaClass(type):
    def __new__(cls,name,bases,namespace):
        #执行操作…
        #早于__init__方法执行,必须返回空对象,由于该方法是调用类后第一个运行的方法,此时并没有对象产生,因此该方法的第一个参数必须是类本身(MyMeta),*args, **kwargs用来接收调用元类产生对象所需的参数(类名 类的基类 名称空间)
        #其实下面那行也可以改成return type(name,bases,namespace),但这样保持了代码的一致性.
        return super().__new__(cls,name,bases,namespace)

    @classmethod
    def __prepare__(cls,name,bases,**kwargs):
        return super().__prepare__(name,bases,**kwargs)
    
    #通过__init__控制类的产生,调用自定义元类与调用内置元类type的方式相同,需要传入类名、类的父类们、类体代码的名称空间,__init__方法中第一个参数来自于__new__方法产生的空对象。
    def __init__(self,name,bases,namespace,**kwargs):
        #执行操作,注意这里虽然是self,但其实就是指我们的类,因为这个类的实例是类
        return super().__init__(name,bases,namespace,**kwargs)

    def __call__(self,*args,**kwargs):
        return super().__call__(*args,**kwargs)

只要调用类,就会依次调用类中的__new__方法和__init__方法;
__new__方法返回一个空对象,就类似一张白纸;
__init__获取__new__方法中产生的白纸在上面画不同的图案。

使用元类创建新类型

使用type()创建新类型

除通过直接定义外,也可以通过type()来定义一个新类型,语法为:

type(clsname, bases, dict, **kwds) -> a new type

其中:

  • clsname:为要创建的新类
  • bases:以元组的形式,声明父类
  • dict:以字典的形式声明类的属性

def get(self) -> None:
    """定义类内部需要运行的函数"""
    print("类获取了一个信息", self.info)

MyClass = type("MyClass", (object, ), {"get": get, "info": "hello"})

c = MyClass()
if hasattr(c, "get"):  # 判断是否有get函数
    getattr(c, "get", None)()  # 获取get函数,并运行函数
else:
    print("该类内部没有get函数")

实际上,解释器在解析类的声明后,也是采用type的方式创建的类对象。

使用type创建新类型往往应用在要创建许多类型的创建中,比如我们再ORM场景中,需要将许多表映射为对象类型,这个时候就可以使用该功能,自动化的创建所有表对象类型,并且能为这些表对象类型提供公共方法。

函数做元类

使用函数也可以作为元类,实现一个将类里面的所有属性名称转换为大写:

# 元类会自动获取通常传给`type`的参数
def upper_attr(_class, _object, _attr):
    """
      返回一个类对象,将其属性置为大写
    """

    # 过滤出所有开头不为'__'的属性,置为大写
    uppercase_attr = {}
    for name, val in _attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

    # 利用'type'创建类,同时将这个类进行返回
    return type(_class, _object, uppercase_attr)


class Foo(metaclass=upper_attr):  # 创建对象时,其会经过 metaclass 来创建,再使用自身的方法进行实例化
    bar = 'bip'

print(hasattr(Foo, 'bar'))  
print(hasattr(Foo, 'BAR'))

f = Foo()
print(f.BAR)

类作为元类

类做元类:

class Meta(type):

    def __new__(cls, name, bases, attrs):
        print("执行了元类 Meta!")
        attrs['author'] = "xiaoyang-sir"
        spam_class = super().__new__(cls, name, bases, attrs)
        print(spam_class)
        return spam_class



class Spam(metaclass=Meta):

    def __init__(self, website):
        self.website = website

        
print(Spam.author)


"""
Out:
    执行了元类 Meta!
    <class '__main__.Spam'>
    xiaoyang-sir

"""

元类的使用场景

尽管元类是非常强大的工具,但它也是非常复杂的工具,所以应该在需要的时候才使用。以下是一些元类的典型应用场景:

  • 自动添加属性或方法:如果你希望所有类都具有某些属性或方法,可以使用元类自动添加。
  • 类的注册:如果你希望在创建类时做一些事情,如注册类,可以使用元类。
  • 强制 API 一致性:如果你正在构建一个框架或库,并希望用户定义的类遵循特定的规则(例如必须有某些方法或属性),则可以使用元类来强制执行这些规则。

单例

所谓单例,就是这个类型的实例对象有且只能有一个,不能使用对象声明或其他方式创建该类的多个实例,可以通过元类实现单例:

class SingleMeta(type):
    def __init__(self, *args, **kwargs):
        print('SingleMeta.__init__ ')
        super().__init__(*args, **kwargs)
        self.instance = None


    def __new__(cls, *args, **kwargs):
        print('SingleMeta.__new__')
        new_obj = super().__new__(cls, *args, **kwargs)
        return new_obj

    def __call__(self, *args, **kwargs):
        print('SingleMeta.__call__ ')
        if not self.instance:
            self.instance = self.__new__(self)
        self.__init__(self.instance, *args, **kwargs)
        return self.instance



class Foo(metaclass=SingleMeta):
    def __init__(self, *args, **kwargs):
        print('Foo.__init__ ')
        self.__name = kwargs['name']

    def __new__(cls, *args, **kwargs):
        print('Foo.__new__ ')
        return super().__new__(cls)

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, value):
        self.__name = value


foo1 = Foo(name='python')
foo2 = Foo(name='java')

print(id(foo1))
print(id(foo2))

‘’'
SingleMeta.__new__
SingleMeta.__init__ 
SingleMeta.__call__ 
Foo.__new__ 
Foo.__init__ 
SingleMeta.__call__ 
Foo.__init__ 
4391594832
4391594832
‘''

meta.__call__()方法的调用是在实例对象__new__()、__init__()之前执行。

ORM中的应用

class Field(object):
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type

    def __str__(self):
        return '<%s:%s>' % (self.__class__.__name__, self.name)


class IntegerField(Field):
    def __init__(self, name, column_type='int(11)'):
        super(IntegerField, self).__init__(name, column_type)


class StringField(Field):
    def __init__(self, name, column_type='varchar(100)'):
        super(StringField, self).__init__(name, column_type)


class ModelMetaClass(type):
    def __new__(cls, class_name, class_parents, class_attr):
        if class_name == 'Model':
            return type.__new__(cls, class_name, class_parents, class_attr)
        print('found model %s' % class_name)
        mappings = {}
        for name, value in class_attr.items():
            if isinstance(value, Field):
                mappings[name] = value
        for k in mappings.keys():
            class_attr.pop(k)
        class_attr['__mappings__'] = mappings
        class_attr['__table__'] = class_name.lower()
        return type.__new__(cls, class_name, class_parents, class_attr)


class Model(dict, metaclass=ModelMetaClass):

    def __init__(self, **kwargs):
        super(Model, self).__init__(**kwargs)

    def __getattr__(self, key):
        try:
            return str(self[key])
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = str(value)

    def save(self):
        fields = []
        args = []
        for k, v in self.__mappings__.items():
            print(k, v, v.name, v.column_type)
            fields.append(v.name)
            args.append(getattr(self, k))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(args))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))


class User(Model):
    id = IntegerField('id')
    name = StringField('name')
    age = IntegerField('age')
    address = StringField('address')


user = User(id='1', name='daocoder', age=27, address='anhui')
user.save()

User类定义继承自父类Model,且有4个属性,4个属性分别继承自IntegerField和StringField,这两个继承自Field,这个不谈。聚焦Model。

1、实例化User时,去找父类Model,发现父类拥有metaclass属性值为ModelMetaClass,即它是由一个自定义的元类来创建的类,向上寻找ModelMetaClass,这个类是继承自type。需要先创建它的实例对象。调用其静态方法new,这里面4个参数(cls, class_name, class_parents, class_attr),分别为ModelMetaClass的类对象、Model类名、父类(dict, )元组、自身内置属性。类名为Model时,直接创建type.__new__(cls, class_name, class_parents, class_attr)并返回。再调用Model类的init方法,调用了父类dict的init的方法。父类Model作为类对象创建完成。

2、开始User类对象的创建,Model已有,然后开始创建User,还是向上找到了ModelMetaClass,这时的4个参数分别是(cls, class_name, class_parents, class_attr),分别为ModelMetaClass的类对象、User类名、父类(Model, )元组、自身内置属性包含id,name,age,address等。然后判断类名不是Model,继续向下,将User属性遍历,其实例自Field的属性封装为User类对象的mappings属性,类名User为User类对象的table属性。

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

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

相关文章

无涯教程-JavaScript - COUPDAYS函数

描述 COUPDAYS函数返回包含结算日期的息票期限内的天数。 语法 COUPDAYS (settlement, maturity, frequency, [basis])争论 Argument描述Required/OptionalSettlement 证券的结算日期。 证券结算日期是指在发行日期之后将证券交易给买方的日期。 RequiredMaturity 证券的到…

Nginx重写功能

Nginx重写功能 一、Nginx常见模块二、访问路由location2.1location常用正则表达式2.2、location的分类2.3、location常用的匹配规则2.4、location优先级排列说明2.5、location示例2.6、location优先级总结2.7、实例2.7.1、location/{}与location/{}2.7.2、location/index.html{…

joplin更新后找不到文章

Joplin的数据默认是存储在C:\Users\Username.config\joplin-desktop下的。我修改为了D:\joplinnotes 这样就导致在升级覆盖安装的时候&#xff0c;笔记丢失路径。如果记不起之前笔记保存在哪里&#xff0c;也可以搜索类似文件来回忆之前自己保存笔记的位置 cache\ plugins\ re…

黑马JVM总结(三)

&#xff08;1&#xff09;栈内存溢出 方法的递归调用&#xff0c;没有设置正确的结束条件&#xff0c;栈会有用完的一天&#xff0c;导致栈内存溢出 可以修改栈的大小&#xff1a; 再次运行&#xff1a;减少了次数 案例二&#xff1a; 两个类的循环应用问题&#xff0c;导致Js…

注解生效激活(idea)

File---------settings-----------Build,Execution,Deployment-----------Compiler------- Annotation Processors

数据分析和可视化平台:Splunk Enterprise for mac v9.1.1激活版 兼容m1

Splunk Enterprise 是一个数据分析和可视化平台&#xff0c;可帮助企业理解其数据。虽然没有适用于 Mac OS 的 Splunk Enterprise 官方版本&#xff0c;但他们确实为 Mac OS 提供了一个名为“Splunk Light”的应用程序&#xff0c;它提供了基本的数据索引、搜索和仪表板。或者&…

「网页开发|前端开发|Vue」07 前后端分离:如何在Vue中请求外部数据

本文主要介绍两种在Vue中访问外部API获取数据的方式&#xff0c;通过让Vue通过项目外部的接口来获取数据&#xff0c;而不是直接由项目本身进行数据库交互&#xff0c;可以实现前端代码和后端代码的分离&#xff0c;让两个部分的代码编写更独立高效。 文章目录 本系列前文传送…

SpringMVC的常用注解,参数传递以及页面跳转的使用

目录 slf4j 常用注解 RequestMapping RequestParam RequestBody PathVariable 参数传递 首先在pom.xml配置文件中导入SLF4J的依赖 基础类型String 复杂类型 RequestParam PathVariable RequestBody 增删改查 返回值 void返回值 String返回值 modelString …

“高效记录收支明细,按时间轻松查找借款信息“

我们有时候要去查找借款信息&#xff0c;只记得住借款记录的日期&#xff0c;想通过日期来进行筛选出借款信息&#xff0c;要如何进行操作&#xff1f;今天就让小编来教教大家要如何操作。 第一步&#xff0c;我们要打开【晨曦记账本】&#xff0c;并登录账本。 第二步&#x…

弃用http改用https的缘故,与密钥的使用,证书意义

为何弃用http协议 在十几年前&#xff0c;我们的传输协议是http协议&#xff0c;为何到了如今改成了https协议呢&#xff1f;为了安全的考虑。 在http协议中&#xff0c;我们的内容是透明的&#xff0c;不被保护的&#xff0c;在黑客等恶意分子的面前&#xff0c;信息极其任意…

百度输入法全面升级,打造首个基于大模型的输入法原生应用

基于文心一言&#xff0c;百度输入法宣布全面升级&#xff0c;打造行业首个“基于大模型的输入法原生应用”&#xff0c;从“输入工具”全面转型为“AI创作工具”。 近日&#xff0c;百度文心一言正式向公众开放。基于文心一言&#xff0c;百度输入法宣布全面升级&#xff0c;打…

Spring Data Rest远程命令执行漏洞复现(CVE-2017-8046)

一、漏洞说明 Spring Data Rest服务器在处理PATCH请求时存在一个远程代码执行漏洞。攻击者通过构造好的JSON数据来执行任意Java代码 二、影响版本 Spring Data REST versions < 2.5.12, 2.6.7, 3.0 RC3 Spring Boot version < 2.0.0M4 Spring Data release trains < K…

Python爬虫 教程:IP池的使用

前言 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 一、简介 爬虫中为什么需要使用代理 一些网站会有相应的反爬虫措施&#xff0c;例如很多网站会检测某一段时间某个IP的访问次数&#xff0c;如果访问频率…

tcp满开始和拥塞避免

tcp的拥塞控制有四种算法&#xff0c;后面的快重传和快恢复是后面新增的&#xff0c; 刚开始会初始化慢开始门限值&#xff0c;并将拥塞窗口值为1往网络中发送&#xff0c;若收到确认包则将拥塞窗口翻倍&#xff0c;执行慢开始算法&#xff0c;当拥塞窗口值达到慢开始门限后&am…

关于测试的思考-自动化测试以及流量回放

二、自动化平台建设 线上问题 流量回放 一些流量回放比较好的实践

【F2 ...】

F2 -ThrombinGlaKRKRlightSP

人工智能轨道交通行业周刊-第59期(2023.9.4-9.10)

本期关键词&#xff1a;无锡智慧地铁、无人车站、钢轨打磨、混元大模型、开源大模型 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro轨道世界…

【JavaEE】线程安全

文章目录 1. 前言2. 线程安全的概念3. 造成线程不安全的原因4. 如何解决出现的线程不安全问题4.1 如何使用 synchronized 加锁&#xff1f;4.2 解决上面自增问题导致的线程安全问题 5. synchronized 的特性5.1 互斥性5.2 可重入性 6. 死锁6.1 什么情况下会造成死锁6.1.1 两个线…

[学习笔记]Node2Vec图神经网络论文精读

参考资料&#xff1a;https://www.bilibili.com/video/BV1BS4y1E7tf/?p12&spm_id_frompageDriver Node2vec简述 DeepWalk的缺点 用完全随机游走&#xff0c;训练节点嵌入向量&#xff0c;仅能反应相邻节点的社群相似信息&#xff0c;无法反映节点的功能角色相似信息。 …

从 LinkedHashMap 源码到手撕 LRU 缓存

大家好&#xff0c;我是 方圆。最近在刷 LeetCode 上LRU缓存的题目&#xff0c;发现答案中有 LinkedHashMap 和自己定义双向链表的两种解法&#xff0c;但是我对 LinkedHashMap 相关源码并不清楚&#xff0c;所以准备学习和记录一下。如果大家想要找刷题路线的话&#xff0c;可…