首页 > 编程语言 >Python面向对象之元类

Python面向对象之元类

时间:2024-01-17 17:45:26浏览次数:19  
标签:__ name Python Son 面向对象 init type class 之元类

元类

【一】概要

  • 元类是类的类,它用于定义其他类。在Python中,一切皆对象,包括类。而元类就是用来创建这些类的“类”。
  • 类是对象: 在Python中,类本身也是一个对象,而元类就是用来创建这些类的对象。

【二】常见用法

  • type函数:在Python中,type函数不仅可以返回对象的类型,还可以用于创建新的类型。当 type 用于创建新的类型时,它的参数是类的名称、基类元组和类的字典。
"""
    type(object) -> the object's type
    type(name, bases, dict, **kwds) -> a new type
"""

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

class Foo(object):
    pass
print(type(Foo))  # <class 'type'>


new_class = type('new_class', (object,), {})
obj = new_class()  # <__main__.new_class object at 0x000001A30A239750>
print(obj)

  • 自定义元类:可以创建自定义的元类,通过继承 type 类并重写其方法来控制类的创建过程。这样,您可以在类被创建时执行额外的逻辑。
# 自定义元类,继承type
class MyMeta(type):
    # 派生
    def __new__(cls, name, bases, kwargs):
        '''添加代码,当子类实例化时自动实现'''
        print("将会自动执行此处内容")
        return super().__new__(cls, name, bases, kwargs)

class Son(metaclass=MyMeta):
    # 元类不可直接实例化,且继承元类必须使用(metaclass=Meta)
    pass


s = Son()
# 将会自动执行此处内容

【三】详解

【1】type函数的参数

  • type(name, bases, dict) -> a new type
    • name:类名 (我是谁)
    • bases:父类(我来自哪里?我的父亲是谁?)
    • dict:类的名称空间(我有什么?)
'''dict参数需要放字典,可以通过dict()强转生成字典'''
'''或者自行写入字典'''
NewClass = type('NewClass', (object,), dict(name='user', age=18))
# NewClass = type('NewClass', (), {'name':'user','age':18})
print(NewClass.__bases__)  # (<class 'object'>,)  # bases 如果是空元组,那么默认继承object
print(NewClass.__dict__)
# {'name': 'user', 'age': 18, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'new_class' objects>,...}


'''不填参数将默认将第一个作为对象,返回这个对象的类,如果需要生成类,就必须传后续的参数,哪怕为空'''
new_class_two = type('new_class')   # 如果不填其他参数,将默认这个字符串为对象,返回的值为str
print(new_class_two)   # <class 'str'>
print(new_class_two.__bases__)  # (<class 'object'>,)
print(new_class_two.__dict__)   # str的名称空间,巨长,就不贴了

【1.1】type函数,相当于class

new_class = type('NewClass', (object,), dict(name='user', age=18))

'''相当于'''
class NewClass(object):
    name = 'user'
    age = 18

【2】通过元类定制化类(__init__方法)

【2.1】通过元类定制化类的推导

  1. 对象是如何产生的? 调用类然后执行类里面的__init__方法了
  2. 类是如何产生的? 推导应该是,造出类的类里面的__init__方法,而这个类恰好是type元类
  3. 得出结论:如果想定制化类的代码,应该写在元类的__init__方法
  • 元类是python自带的源码,我们是无法直接修改源码的,而我们又想使用元类中的方法,应该如何?
  • 可以想到,派生!重用元类的__init__方法,并派生出自己的属性与方法
# MyMeta继承元类
class MyMeta(type):
    # 派生出自己的__init__
    def __init__(self, name, bases, dict):
        '''写条件'''
        if name != 'Son':
            raise ValueError("只有Son可以继承我")
        super().__init__(name, bases, dict)

class Son(metaclass=MyMeta):
    pass
class Daughter(metaclass=MyMeta):  # ValueError: 只有Son可以继承我
    pass

s = Son()

【补】__init__(name,bases,dict)的三个参数,当类通过元类实例化时会自动传入值

class MyMeta(type):
    def __init__(self, name, bases, dict):
        print(f"类名:{name}")
        print(f"父类:{bases}")
        print(f"名称空间:{dict}")
        super().__init__(name, bases, dict)


class Son(metaclass=MyMeta):
    name = 'user'
    pass
# 类名:Son
# 父类:()
# 名称空间:{'__module__': '__main__', '__qualname__': 'Son', 'name': 'user'}

【2.1.1】定制类必须首字母大写
# MyMeta继承元类
class MyMeta(type):
    # 派生出自己的__init__
    def __init__(self, name, bases, dict):
        '''写条件'''
        if not name.istitle():
            raise ValueError("首字母必须大写!")
        super().__init__(name, bases, dict)


class Son(metaclass=MyMeta):
    pass


class son(metaclass=MyMeta):   # ValueError: 首字母必须大写!
    pass

【2.2】__init__的执行顺序:先执行元类的__init__,再执行自己的

class MyMeta(type):
    def __init__(self, name, bases, dict):
        print("MyMeta的__init__")
        super().__init__(name, bases, dict)


class Son(metaclass=MyMeta):
    def __init__(self):
        print("Son的__init__")


s = Son()
# MyMeta的__init__   # 优先执行元类中的方法
# Son的__init__

'''元类与正常继承的__init__对比'''
class Foo(object):
    def __init__(self):
        print("Foo的__init__")

class Son(Foo):
    def __init__(self):
        print("Son的__init__")
        super().__init__()

s = Son()
# Son的__init__   # 普通的继承时,将先执行自己的,再执行父类的
# Foo的__init__

【3】通过元类定制类的对象的产生(__call__方法)

【3.1】通过元类定制化类的对象推导

  1. 如何定制类的对象?通过实例化后,类中的__init__方法
  2. 类实例化时是通过类名+ ()进行实例化对象
  3. 类名+ ()会触发类中的__call__方法(如果类中定义了),如果没有就去父类或基类中查找__call__方法
  4. 前面提到,其实类也是元类实例化出来的对象,所以类名+ ()也会触发元类中的__call__方法
  5. 得出结论,如果需要定制实例化对象,那么应该写在元类的__call__方法
  • 同样的,因为是源码,无法修改,所以使用派生
class MyMeta(type):
    def __call__(self, *args, **kwargs):
        print("MyMeta.__call__")
        return super().__call__(*args, **kwargs)


class Son(metaclass=MyMeta):
    def __init__(self, name, age):
        print("Son.__init__")


s = Son('user', 18)
# MyMeta.__call__
# Son.__init__

【补】同样的,在对象实例化时,会自动将参数传给__call__(*args,**kwargs)

class MyMeta(type):
    def __call__(self, *args, **kwargs):
        print(args)  # ('user', 18)
        print(kwargs)  # {}
        print("MyMeta.__call__")
        return super().__call__(*args, **kwargs)


class Son(metaclass=MyMeta):
    def __init__(self, name, age):
        print("Son.__init__")


s = Son('user', 18)

【3.2】定制类的对象必须通过关键字传参

class MyMeta(type):
    def __call__(self, *args, **kwargs):
        if args:  # 如果args中有值,为True时,说明不符合条件
            raise Exception("必须通过关键字传参!")
        return super().__call__(*args, **kwargs)


class Son(metaclass=MyMeta):
    def __init__(self, name, age):
        print("Son.__init__")


# s = Son('user', 18)  # Exception: 必须通过关键字传参!
s = Son(name='user', age=18)

【4】小练习

【4.1】自动为类添加方法(自动打招呼)

class MyMeta(type):
    def __init__(cls, name, bases, cls_dict):
        if not name.istitle():
            raise Exception("类名必须首字母大写!")
        super().__init__(name, bases, cls_dict)

    def __new__(cls, name, bases, cls_dict):
        # 将功能添加到类的名称空间中
        # 传入lambda匿名函数
        cls_dict['say'] = lambda self, saying=name: print(f"Hello, {saying}! I'm Meta!")
        return super().__new__(cls, name, bases, cls_dict)


class F1(metaclass=MyMeta):
    pass


f = F1()
f.say()
print(F1.__dict__)
# {'__module__': '__main__', 'say': <function MyMeta.__new__.<locals>.<lambda> at 0x000001CD563C1A20>,...}

标签:__,name,Python,Son,面向对象,init,type,class,之元类
From: https://www.cnblogs.com/Lea4ning/p/17970602

相关文章

  • python之异常
    ###异常异常:程序在运行的时候,如果python解释器遇到一个错误,会停止程序的执行,并且提示一些错误的信息,这就是异常我们在程序开发的时候,很难将所有的特殊情况都处理,通过异常捕获可以针对突发事件做集中处理,从而保证程序的健壮性和稳定性在程序开发中,如果对某些代码的执行不能......
  • 解决Python虚拟环境安装模块失败的问题
    Python虚拟环境的出现为我们创建和管理项目提供了很大的方便。通过虚拟环境,我们可以隔离不同项目的依赖包,避免版本冲突和混乱。然而,有时候在虚拟环境中安装模块时会遇到各种问题,例如找不到模块、安装超时等。下面将介绍几种常见的情况和相应的解决方法,以帮助您顺利安装模块。1.网络......
  • Python、Anaconda、PyCharm和终端的关系及其作用
    Python是一种高级编程语言,广泛应用于数据分析、科学计算、Web开发等领域。为了便于开发和运行Python程序,我们通常会使用一些工具和环境。其中,Anaconda是一个Python发行版,提供了大量的科学计算和数据处理库;PyCharm是一款强大的Python集成开发环境(IDE);终端(或命令行)则是执行Python代码......
  • 解决PyCharm显示"No Python Interpreter configured for the project"的问题
    PyCharm提供了许多功能和工具,以帮助开发人员编写、调试和运行Python程序。但是,在启动新项目或打开现有项目时,有时会出现"NoPythonInterpreterconfiguredfortheproject"的错误提示。这意味着PyCharm无法找到配置的Python解释器,导致无法正常运行代码。下面将介绍可能导致此问......
  • 在Python中为什么同样的Cypher语句运行结果不会完整显示
    Neo4j是一款流行的图数据库,它使用Cypher查询语言来操作和查询图数据。在Python中,我们可以使用Neo4j的官方驱动程序或第三方库(如py2neo)来与数据库进行交互。然而,当我们执行某些复杂的Cypher查询时,有时会发现结果被截断或不完整显示。下面将介绍可能导致此问题的原因,并提供相应的解决......
  • 使用Bootstrap方法在Python中绘制带有置信带的ROC曲线
    Bootstrap方法是一种统计学方法,在样本数据有限的情况下,通过随机重采样的方式来估计样本统计量的分布。通过应用Bootstrap方法,我们可以通过对训练数据进行多次重采样,并在每次重采样后重新拟合模型,得到多个ROC曲线。然后,我们可以使用这些ROC曲线的结果来计算置信带,以评估模型的稳定性......
  • cyclone list to python tuple!
    背景python有list和tuplecyclone只有list(被称为array)pythonreturn多个值pythontuplecyclonelistpythontocyclonepythonlistto_cyclonelistto_pythontuple!单个元素的tuple末尾有,确实是tuplepythontupleto_cyclonelist证明cyclone的list(被称为array)其......
  • python pyqt6 颜色弹窗 QColorDialog
     defsetColor(self):#避免窗口置顶后,Dialog被主窗口覆盖,所以需要传递self#设定默认颜色使用getColor的第一个参数(使用setCurrentColor不生效)#"选择颜色"为Dialog弹窗的标题#设定QColorDialog.ColorDialogOption.ShowAlphaChanne......
  • python 切片slice和实现一个切片类
    alist=[2,5,32,34,11,44,65,113]print(alist[::])##取所有alist[2,5,32,34,11,44,65,113]print(alist[::-1])##alist倒序[113,65,44,11,34,32,5,2]print(alist[::2])##取alist偶数位数值[2,32,11,65]print(alist[1::2])##取alist奇数位数值[5,34,44,......
  • 几行Python代码,轻松搞定Excel表格数据去重
    转载说明:如果您喜欢这篇文章并打算转载它,请私信作者取得授权。感谢您喜爱本文,请文明转载,谢谢。众所周知,Python在处理Excel数据文档时非常强大。最近也尝试了一下使用Python处理Excel数据,几行代码就能实现一个非常有用的功能,非常棒!这次实验的是,使用Python给Excel数据去重。创建......