python-面向对象魔法方法、元类

1.面向对象的魔法方法

魔法方法:类中定义的双下方法都称为魔法方法
使用方法:不需要人为调用,在特定条件下自动触发运行
eg:__init__是创建对象之后自动触发给对象添加独有数据的方法
    
1.__init__:添加对象独有数据的方法,对象添加数据时自动触发
class A:
    def __init__(self, name):
        self.name = name
        print('__init__')
#类名加括号,给对象添加独有数据的方法
obj = A('max')  # obj = A('max')  

2.__str__:对象在被执行打印操作的时候自动触发,用法是在类体代码中定义__str__(self),先生成一个对象,在print(对象名),就可以自动触发打印返回值
class A:
    def __str__(self):
    '''对象在被执行打印操作的时候自动触发,并且返回什么执行结果就是什么'''
        return '哈哈哈'  # __str__子代码中不能用print,只能用return返回

a = A()
print(a)   # 哈哈哈

"""
返回值只能是字符串,非字符串数据类型会报错
"""
class A:
    def __str__(self):
        return 123

a = A()
print(a)   # 报错

"""
maximum recursion depth:最大递归深度;此代码报错原因是print(a)会自动触发__str__并且返回f'{self}说:哈哈哈',此时print(a就相当于print(f'{self}说:哈哈哈'),print里面反复递归对象a,直到达到最大递归深度报错,所以返回的字符串中不能有对象本身
"""
class A:
    def __str__(self):
        return f'{self}说:哈哈哈'

a = A()
print(a)  # 错误信息:RecursionError: maximum recursion depth exceeded while calling a Python object

"""
这个特性也可以用来分辨对象具体是谁, 返回的字符串中不能有对象本身,但可以点名字来看具体是哪个对象
"""
class A:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f'对象{self.name}'

a = A('max')
print(a)  # 对象max


3.__call__:对象加括号调用,该方法返回什么对象调用的返回值就是什么
class A:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f'对象{self.name}'

    def __call__(self, *args, **kwargs):
        print('__call__')
        return '__call__的返回值'

a = A('max') 
print(a())  # __call__  # __call__的返回值

"""
对象类似于函数名,可以在括号内加上位置参数和关键字参数,打印args和kwargs可以看到他们分别收集了括号内的位置参数和关键字参数,对象名加括号可以调用__call__,print(res)还可以接受到__call__的返回值
"""
class A:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return f'对象{self.name}'

    def __call__(self, *args, **kwargs):
        print(args, kwargs)
        return '__call__的返回值'

a = A('max')
res = a(123, '123', name='max')  # (123, '123') {'name': 'max'}
print(res)  #__call__的返回值

4.__getattr__:对象点的名字不存在时自动触发
class A:
    def __init__(self, name):
        self.name = name

    def __getattr__(self, item):  # item就是对象点的不存在的名字
        return '很抱歉你找的名字不存在'

a = A('max')
print(a.name)  # max
print(a.age)  # 很抱歉你找的名字不存在

5.__getattribute__:对象在查找名字时会自动触发,不管名字是否存在,并且只要有__getattribute__在,__getattr__会自动失效,例题中a.age不存在但是但是依然返回了'嘿嘿嘿',是因为__getattribute__作用将__getattr__覆盖
class A:
    def __init__(self, name):
        self.name = name

    def __getattr__(self, item):  
        return '很抱歉你找的名字不存在'

    def __getattribute__(self, item):
        return '嘿嘿嘿'

a = A('max')
print(a.name)  # 嘿嘿嘿
print(a.age)  # 嘿嘿嘿
"""
打印__getattribute__括号内的item后发现是一个对象名点的名字
"""
class A:
    def __init__(self, name):
        self.name = name

    def __getattr__(self, item):
        return '很抱歉你找的名字不存在'

    def __getattribute__(self, item):
        print(item)
        return '嘿嘿嘿'

a = A('max')
print(a.age)  # age 嘿嘿嘿

6.__getitem__:对象用中括号取值并且值不存在的时候触发:对象['属性']值不存在时触发。

6.__setattr__:当对象执行对象名.名字=值的时候(对象定义独有的数据或修改数据)就会自动触发
class A:
    def __init__(self, name):
    '''第一步:self此时就是a,满足对象名.名字=值,所以会执行一次__setattr__'''
        self.name = name  

    def __setattr__(self, key, value):
        print('__setattr__')


a = A('max')
a.name = 'jack'  # __setattr__   __setattr__
'''第二步,再执行一次__setattr__'''

"""
__setattr__(self, key, value)中的key和value分别指name和值max
"""
class A:
    def __init__(self, name):
        self.name = name

    def __setattr__(self, key, value):
        print('__setattr__')
        print(key, value)


class Student:
    def __init__(self, name):
        self.name = name

a = A('max')  # __setattr__  # name max


用__setattr__修改属性名:
obj1 = Student('max')
print(obj1.__dict__)  # {'name': 'max'}
obj1.__setattr__('name', 'jason')
print(obj1.__dict__)  # {'name': 'jason'}

7.__setitem__:对象['属性']=值的时候触发。

8.__enter__:当对象跟在with后面,被当做上下文管理操作开始会自动触发__enter__。
__exit__:当对象参与with上下文管理语法运行完毕后自动触发(with子代码运行完毕)
'''__enter__方法一般和__exit__连用'''
class A:
    def __init__(self, name):
        self.name = name

    def __enter__(self):
        print('enter')
        return 123

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('exit')
        return 234


a = A('max')
with a as f:
    print(f)  # enter 123 exit


由此得出,f拿到的是__enter__的返回值

2.魔法方法笔试题

1.补全下列代码使得运行不报错即可:
class Context:
        pass
    with Context() as f:
        f.do_something()
"""
分析:有with 对象,说明with参与上下文管理,必须要有__enter__方法,结束时必须要有__exit__(这两者一般会联合使用)。此外do_something()还没有定义,还需要定义一个功能do_something(),调用时才不会报错
"""
class Context:
    def do_something(self):
        pass

    def __enter__(self):
        return self
    '''此行代码执行完毕需要一个f来点do_dometging(),我们一般用的最多的就是对象点的方式,所以此处需要返回对象本身也就是self。正好上节说道f拿到的是__enter__的返回值,所以旭阳将对象本身返回给f,再用对象点名字。所以return后面要跟self'''
    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

with Context() as f:
    f.do_something()
    
2.请定义一个字典,该字典可以通过点的方式来取值
"""
我们之前学过点的方式取值,但是之前是通过对象点名字来取名称空间中的名字,而本题要求字典点的方式来取字典中的值,两者有本质区别
"""
'''1.定义一个类,该类可以派生字典里所有的方法'''
class Mydict(dict):
	
    '''2.由于添加键值对要通过点的方式来进行,那么可以联想到魔法方法中的__setattr__方法,该方法在对象名点名字=值时调用,且此时的key就指name,value指'max' '''
    def __setattr__(self, key, value):
     '''3.此时self就是obj,也就是构建的临时字典,通过按K取值的方式定义好取值方式'''
        self[key] = value
	 '''4.定义好了添加键值对,此时再考虑取值。__getattr__特点是遇到名称空间中找不到的名字会自动触发,由于对象、类名称空间中都为空,所以查找名字肯定会自动触发__getattr__,且item是对象后面点的名字'''
    def __getattr__(self, item):
    '''5.返回一个通过item取到的值'''
        return obj.get(item)



obj = Mydict()
obj.name = 'max'
print(obj.name)

3.元类简介

元类概念推导:
步骤1:如何查看数据的数据类型
s1 = 'hello world'  # str()
l1 = [11, 22, 33, 44]  # list()
d1 = {'name': 'jason', 'pwd': 123}  # dict()
t1 = (11, 22, 33, 44)  # tuple()
print(type(s1))  # <class 'str'>
print(type(l1))  # <class 'list'>
print(type(d1))  # <class 'dict'>
print(type(t1))  # <class 'tuple'>

步骤2:type不仅可以查看数据类型,也可以查看产生对象的类名
class Group:
    pass

obj = Group()
print(type(obj))  # <class '__main__.Group'>

步骤3:type(对象名)结果是类名,那么type(类名)的结果是什么呢?
class Group:
    pass

obj = Group()
print(type(obj))  # <class '__main__.Group'>
print(type(Group))  # <class 'type'>
type的父类依然是type
print(type(type))  # <class 'type'>
"""
我们用class关键字定义的类本身也是一个对象,负责产生该对象的类称之为元类(元类可以简称为类的类),内置的元类为type
"""

4.创建类的两种方式

方式1:通过关键字class创建
class Teacher:
    name = 'jason'
    age = 18
    
a = Teacher()
print(a.name, a.age)  # jason 18

方式2:通过关键字type创建,语法结构:type(类名, 类的父类, 类的名称空间)
Teacher = type('Teacher', (), {'name': 'jason', 'age': 18})
a = Teacher()
print(a.name, a.age)  # jason 18

"""
也可以继承父类中的名字(如果只有一个父类后面需加逗号,同元组)
"""
class Person:
    def work(self):
        print('人不能不上班')


Teacher = type('Teacher', (Person,), {'name': 'jason', 'age': 18})
a = Teacher()
print(a.work())  # 人不能不上班  None

5.元类参与类的产生行为

"""
在某一步设定一些条件,来干涉类产生的行为,对象是由类加括号产生的,类是由元类加括号产生的。一个类没有声明自己的元类,默认他的元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个类指定元类
	对象是由类名加括号产生的  	__init__
	类是由元类加括号产生的		__init__
"""
class MyMetaClass(type):
    def __init__(cls, what, bases=None, dict=None):
        print('what', what)  # what Student
        print('bases', bases)  # bases ()
        print('dict', dict)  # dict {'__module__': '__main__', '__qualname__': 'Student', 'name': 'max'}
        super().__init__(what, bases, dict)

'''通过metaclass=MyMetaClass来继承元类'''
class Student(metaclass=MyMetaClass):
    name = 'max'
"""
通过依次打印what, bases, dict得知,what是继承了MyMetaClass的类,dict是类Student的名称空间。元类产生类的行为其实也就是:Student=MyMetaClass('Student',object,{})
"""

class MyMetaClass(type):
    def __init__(cls, what, bases=None, dict=None):
        # print('what', what)  # what Student  继承MyMetaClass的类名,可以在这里控制类名的大小写
        # print('bases', bases)  # bases ()  MyMetaClass的父类
        # print('dict', dict)  # dict {'__module__': '__main__', '__qualname__': 'Student', 'name': 'max'}  MyMetaClass的子类的类名称空间
        if not what.istitle():
            raise TypeError('首字母必须大写')

        super().__init__(what, bases, dict)

class student(metaclass=MyMetaClass):
    name = 'max'  # TypeError: 首字母必须大写

class Student(metaclass=MyMetaClass):
    name = 'max'  # 可以正常创建

6.元类参与对象的产生行为

1.对象加括号会执行产生该对象类里面的__call__
推导得出:类加括号会执行产生该类的类里面的__call__

2.给对象添加一些独有数据,首先是传到了元类里面的__call__,位置参数传给了args,关键字参数传给了kwargs,其次再交给__init__
"""
class MyMetaClass(type):
    def __call__(self, *args, **kwargs):
        print('__call__')
        print(args, kwargs)
        super().__call__(*args, **kwargs)

class Student(metaclass=MyMetaClass):
    def __init__(self, name, age, gender):
        print('__init__')
        self.name = name
        self.age = age
        self.gender = gender

obj = Student('max', 25, 'male')
'''
执行结果:
__call__
('max', 25, 'male') {}
__init__
'''
要求:定义一个类,声称对象只能上传关键字参数
class MyMetaClass(type):
    def __call__(self, *args, **kwargs):
        if args:
            raise TypeError('必须要传关键词参数哦')
        '''super语句前面一定要加上return,不加会报错'''
        return super().__call__(*args, **kwargs)

class Student(metaclass=MyMetaClass):
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender

# obj = Student('max', 25, 'male')  # TypeError: 必须要传关键词参数哦
obj = Student(name='max', age=25, gender='male')
print(obj.__dict__)  # {'name': 'max', 'age': 25, 'gender': 'male'}
"""
分析:为什么要用类Student继承元类MyMetaClass呢?
因为对象产生的过程中首先把参数传入MyMetaClass的__call__,只有用Student继承元类MyMetaClass才能介入对象的生成过程。

如果args布尔值为True说明上传了关键字参数,这种情况下主动报错,报错情况下不会执行,只有args布尔值为Flase时才会执行super语句
"""

7.魔法方法之双下new

"""
产生一个对象的步骤:
1.产生一个空对象(骨架)
2.调用__init__给空对象添加该对象独有的数据(血肉)
3.返回创建好的对象
4.继承自object的新式类才有__new__
5.__new__至少要有一个参数cls,代表当前类,此参数在实例化时由Python解释器自动识别
6.__new__必须要有返回值,返回实例化出来的实例,这点在自己实现__new__时要特别注意,可以return父类(通过super(当前类名, cls))__new__出来的实例,或者直接是object的__new__出来的实例
注:__init__有一个参数self,这个就是__new__返回的实例,__init__在__new__的基础上可以完成一些其它初始化的动作,__init__不需要返回值
7.如果__new__创建的是当前类的实例,会自动调用__init__函数,通过return语句里面调用的__new__函数的第一个参数是 cls 来保证是当前类实例,如果是其他类的类名,那么实际创建返回的就是其他类的实例,其实就不会调用当前类的__init__函数,也不会调用其他类的__init__函数。
更多查看:https://www.cnblogs.com/zhaoyuanshi/p/16130508.html
"""
上述步骤可以用代码模拟出来:
class MyMetaClass(type):
    def __call__(self, *args, **kwargs):
        # 1.产生一个空对象(骨架)
        obj = self.__new__(self)
        # 2.调用__init__给对象添加独有的数据(血肉)
        self.__init__(obj,*args, **kwargs)
        # 3.返回创建好的对象
        return obj


class Student(metaclass=MyMetaClass):
    def __init__(self, name):
        self.name = name

obj = Student('jason')
print(obj.name)
"""
__new__可以产生空对象
"""
"""
__init__和__new__的区别:
1.__init__和__new__都是类创建实例时调用,__init__是初始化实例时调用,__new__是创建实例时调用,所以__init__在__new__之后调用:
class Person(object):
    def __init__(self, name, age):
        print('__init__called')
        self.name = name
        self.age = age

    def __new__(cls, name, age):
        print('__new__called')
        return super().__new__(cls)


per1 = Person('max', 26)
执行结果:
__new__called
__init__called

2.__new__ 通常用于控制生成一个新实例的过程。它是类级别的方法。
__init__ 通常用于初始化一个新实例,控制这个初始化的过程,比如添加一些属性, 做一些额外的操作
3.__new__是一个静态方法,而__init__是一个实例方法
4.__new__会返回一个cls的实例,__init__什么都不返回
"""

8.设计模式简介

1.设计模式:前人通过大量的验证创建出来解决一些问题的固定高效方法
2.IT行业目前有23种设计模式。分类:创建型、结构型、行为型
3.单例模式
	类加括号无论执行多少次永远只会产生一个对象
 	目的:
        当类中有很多非常强大的方法 我们在程序中很多地方都需要使用
        如果不做单例 会产生很多无用的对象浪费存储空间
        我们想着使用单例模式 整个程序就用一个对象

9.单例模式实现

方式1:类中存储单例
class C1:
    __isinstance = None  # 1.定义一个全局变量
    def __init__(self, name, age):
        self.name = name
        self.age = age
	 
    '''通过这种方法产生一个固定的对象,并且只能调用此种方式产生'''
    @classmethod
    def singleton(cls):  # 2.通过classmethond修饰的函数可以直接将类传进去
        if not cls.__isinstance:  # 3.判断,通过类.名字的方式拿到类中的数据
            cls.__isinstance = cls('max', 123)  # 4.如果cls.instance为None,则把它修改为一个对象,cls('max', 123)是一个对象
        return cls.__isinstance  # 5.返回刚刚生成的对象,拿到的返回值都是对象C1('max', 123)

obj1 = C1.singleton()
obj2 = C1.singleton()
print(id(obj1), id(obj2))
# 1952330470792 1952330470792
"""
此种方法也可以产生正常的对象,不调用singleton方法就可以正常产生
"""

方法2:通过模块来实现单例模式
md.py文件中代码:
class C1:
    def __init__(self, name):
        self.name = name

obj = C1('jason')

另一个py文件:
import md
print(id(md.obj))  # 2278381247176

print(id(md.obj))  # 2278381247176

方式3:元类控制单例类的产生
class MyType(type):
    def __init__(cls, what, bases=None, dict=None):  # 1.定义一个类
        cls.__instance = cls.__new__(cls)  # 2.用双下__new__产生一个新对象
        cls.__init__(cls.__instance, 'jason', 18)  # 3.添加对象独有的数据
        super().__init__(what, bases, dict)  # 4.继承元类其它的方法

    def __call__(cls, *args, **kwargs):  # 6.类的对象加括号调用类的__call__,子类加括号调用父类__call__
        if args or kwargs:  # 7.如果子类中传了值,就正常给对象添加独有数据,并且返回该对象
            obj = cls.__new__(cls)
            cls.__init__(obj, *args, **kwargs)
            return obj
        return cls.__instance  # 8.如果子类对象没有添加数据,则返回之前创建的对象

class Mysql(metaclass=MyType):  # 5.定义类Mysql,父类是MyType
    def __init__(self, name, age):
        self.name = name
        self.age = age

obj1 = Mysql()  # 9.所以Mysql产生的对象如果不加参数
obj2 = Mysql()
print(id(obj1), id(obj2))  # 2675820190128 2675820190128  
posted @   ERROR404Notfound  阅读(47)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
Title
点击右上角即可分享
微信分享提示