魔法方法和元类总结
3882 字
19 分钟
魔法方法和元类总结
【一】常用的魔法方法
【0】什么是魔法方法
- 在类定义阶段定义,但是会根据特定条件自动触发的方法就叫魔法方法,又称为内置方法
【1】init
(1)触发场景
- 初始化对象的属性
- 在类实例化得到具体对象时自动触发
(2)示例
# 创建一个类:类名为 Person,继承 objectclass Person: def __init__(self, name, age): print(f"我在被初始化对象时触发!") self.name = name self.age = age
person = Person(name='dream', age=18)
# 我在被初始化对象时触发!【2】str
(1)触发场景
- 对象被执行打印操作的时候会自动触发
- 并且该方法必须返回一个字符串
(2)示例
# 创建一个类:类名为 Person,继承 objectclass Person: def __init__(self, name, age): print(f"我在被初始化对象时触发!") self.name = name self.age = age
def __str__(self): print(f"我在打印对象时被触发!") return f'Person(name={self.name}, age={self.age})'
person = Person(name='dream', age=18)# 我在被初始化对象时触发!print(person)# 我在打印对象时被触发!# Person(name=dream, age=18)【3】del
(1)触发场景
- 对象被删除(被动或者主动)之后自动触发
(2)示例
# 创建一个类:类名为 Person,继承 objectclass Person: def __init__(self, name, age): print(f"我在被初始化对象时触发!") self.name = name self.age = age
def __del__(self): # 对象被删除是指,在Python中,对象不再被使用时,垃圾回收器会自动调用__del__方法 print(f'我在对象被删除时触发!') print(f'{self.name} deleted')
person = Person(name='dream', age=18)# 我在被初始化对象时触发!print(person.name)# dream# 我在对象被删除时触发!# dream deleted【4】getattr
(1)触发场景
- 对象在查找不存在的属性时会自动触发
(2)示例
# 创建一个类:类名为 Person,继承 objectclass Person: def __init__(self, name, age): print(f"我在被初始化对象时触发!") self.name = name self.age = age
def __getattr__(self, attr): # attr 为获取属性时不存在的那个属性名 if attr == 'gender': # 当不存在 gender 这个属性的时候会返回 male return 'male' # 不存在其他属性时会直接报错 raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{attr}'")
person = Person(name='dream', age=18)# 我在被初始化对象时触发!print(person.gender)# maleprint(person.sex)# AttributeError: 'Person' object has no attribute 'sex'【5】setattr
(1)触发场景
- 对象在设置属性(obj.属性名=属性值)的时候会自动触发
(2)示例
# 创建一个类:类名为 Person,继承 objectclass Person: def __init__(self, name, age): print(f"我在被初始化对象时触发!") self.name = name self.age = age
def __setattr__(self, key, value): if key == 'age': # 对年龄进行限制,如果年龄不是数字类型,则抛出异常 if not isinstance(value, int): raise TypeError(f"{key} must be of type int") # 符合规定,则将对象自己的属性值进行替换 super().__setattr__(key, value)
person = Person(name='dream', age=18)# 我在被初始化对象时触发!person.age = 19print(person.age)# 19person.age = '19'# TypeError: age must be of type int【6】delattr
(1)触发场景
- 尝试删除对象的属性时自动触发。
(2)示例
# 创建一个类:类名为 Person,继承 objectclass Person: def __init__(self, name, age): print(f"我在被初始化对象时触发!") self.name = name self.age = age
def __delattr__(self, attr): # 限制当前删除的属性名为 age if attr == 'age': # 若是删除的age属性,则打印这句话 print(f'Deleting {attr}') else: # 否则调用父类中的 delattr 方法 super().__delattr__(attr)
person = Person(name='dream', age=18)# 我在被初始化对象时触发!del person.age# Deleting agedel person.sex# AttributeError: sex【7】setitem
(1)触发场景
- 当使用索引操作符(obj[key] = value)为对象的字段赋值时自动触发。
(2)示例
# 创建一个类:类名为 Person,继承 objectclass Person: def __init__(self, name, age): print(f"我在被初始化对象时触发!") self.name = name self.age = age
def __setitem__(self, key, value): print(f'正在设置属性 :>>>> {key} = {value}') # 向对象自己中初始化值 # setattr(self, key, value) # 向自己的名称空间字典中增加新的属性 self.__dict__[key] = value
person = Person(name='dream', age=18)# 我在被初始化对象时触发!
person["sex"] = "男"# 正在设置属性 :>>>> sex = 男
print(person.sex)# 男【8】getitem
(1)触发场景
- 当使用索引操作符(obj[key])获取对象的字段值时自动触发。
(2)示例
# 创建一个类:类名为 Person,继承 objectclass Person: def __init__(self, name, age): print(f"我在被初始化对象时触发!") self.name = name self.age = age
def __getitem__(self, key): # 对象本身是没有 属性值 = obj[属性名] 这个方法的,但是通过 __getitem__ 可以让对象具有该方法 return self.__dict__[key]
person = Person(name='dream', age=18)# 我在被初始化对象时触发!
print(person['name'])# dream【9】delitem
(1)触发场景
- 当使用删除操作符(del obj[key])删除对象的字段时自动触发。
(2)示例
# 创建一个类:类名为 Person,继承 objectclass Person: def __init__(self, name, age): print(f"我在被初始化对象时触发!") self.name = name self.age = age
def __delitem__(self, key): # 对象本身是没有 del obj[属性名] 这个方法的,但是通过 __delitem__ 可以让对象具有该方法 print(f'我在del删除时触发 删除属性 :>>>> {key}') del self.__dict__[key]
person = Person(name='dream', age=18)# 我在被初始化对象时触发!
del person['name']# 我在del删除时触发 删除属性 :>>>> name
print(person.name)# AttributeError: 'Person' object has no attribute 'name'【10】call
(1)触发场景
- 当对象调用函数(obj())时自动触发。
(2)示例
# 定义一个计算机模拟类class Calculator: def __init__(self, num1, num2): # 初始化需要计算的参数 self.num1 = num1 self.num2 = num2
# 加法 def add(self): return self.num1 + self.num2
def __call__(self): print(f"我在对象调用时被调用,计算结果为:{self.add()}") return self.add()
cal = Calculator(9, 6)
print(cal())# 我在对象调用时被调用,计算结果为:15# 15【11】enter__和__exit
(1)触发场景
- 使用with语句执行代码块时,先执行__enter__方法,然后执行代码块,再执行__exit__方法。
(2)示例
# 定义一个上下文处理函数class ContextManager: def __init__(self, data): self.data = data
# 当被 with 打开时触发 def __enter__(self): print(f'Enter context with data: {self.data}')
return self
# 当 with 语句块执行完毕后触发 def __exit__(self, exc_type, exc_val, exc_tb): print(f'Exit context with data: {self.data}')
with ContextManager('Hello World') as cm: print(cm.data)
# Enter context with data: Hello World# Hello World# Exit context with data: Hello World【二】元类
【1】什么是元类
- 一切源于一句话:Python中一切皆对象
- 八大基本数据类型是对象
- 类实例化得到的对象也是对象
- 其实类本身也是一种对象
class Student(object): def __init__(self, name): self.name = name
# 【1】实例化类得到类的对象student = Student(name='dream')# 【2】查看这个对象的数据类型print(f'student的数据类型:{type(student)}')# student的数据类型:<class '__main__.Student'>
# 【3】查看产生这个类的数据类型print(f'Student的数据类型:{type(Student)}')# Student的数据类型:<class 'type'>
# 【4】查看产生字典的数据类型print(f'dict 的数据类型: {type(dict)}')# dict 的数据类型: <class 'type'>- 通过查看每一种数据的数据类型,我们会发现都共同拥有一个类
- 这个类就是 type 我们也称之为元类
【2】产生类的两种方式
(1)直接关键字创建
- 语法
关键字(class) 类名(Student)继承的父类(默认是object): # 类体代码- 示例
class Student(object): school = '清华大学'
def __init__(self, name): self.name = name
def read(self): print(f'{self.name}is reading!')
# 【一】查看创建当前类的类print(type(Student))# <class 'type'>
# 【二】查看当前类的名称空间print(Student.__dict__)# {'__module__': '__main__', 'school': '清华大学', '__init__': <function Student.__init__ at 0x10480b880>, 'read': <function Student.read at 0x12a3975b0>, '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}(2)通过 type 创建
- 语法
# 初始化 type 这个类的 __init__ 方法def __init__(cls, what, bases=None, dict=None): # known special case of type.__init__ """ type(object) -> the object's type type(name, bases, dict, **kwds) -> a new type # (copied from class doc) """ pass
# 类名 = type('类名',(父类1,父类2,...),{数据属性名:数据属性值,函数属性名:函数属性值,...})- 示例
def read(self): ...
Student = type('Student', (object,), {'name': 'dream', 'read': read})
# 【一】查看创建当前类的类print(type(Student))# <class 'type'>
# 【二】查看当前类的名称空间print(Student.__dict__)# {'name': 'dream', 'read': <function read at 0x1041f3f40>, '__module__': '__main__', '__dict__': <attribute '__dict__' of 'Student' objects>, '__weakref__': <attribute '__weakref__' of 'Student' objects>, '__doc__': None}
# 细心地你可能会发现,当我们使用关键字创建类内的方法的时候,查看他的内存空间是带着类名字的# 而我们当前创建的这个类,初始化进去的只是一个普普通通的函数,也就是非绑定方法【3】为什么要使用元类
- 元类可以控制类的创建,也就意味着我们可以高度定制类的具体行为
- 比如,当我们掌控了食品的生产过程,我们就可以在里面随便动手脚
【4】元类的基本使用
(1)需求
- 要求所有类的名字必须是大写的
(2)思考
- 我们应该在哪里定制这些代码
- 类的产生过程也许我们还不熟悉
- 但是对象的产生过程,是通过类内的 init 方法实现的
- 那我们猜,在元类里面也有一个 __init__方法
(3)基本使用
class MyType(type): def __init__(cls, class_name, class_bases, class_dict): print("MyType的__init__被调用,class_name是:", class_name) print("MyType的__init__被调用,class_bases是:", class_bases) print("MyType的__init__被调用,class_dict是:", class_dict) super().__init__(class_name, class_bases, class_dict)
# 元类的使用区别于我们继承中使用父类的那种形式# 元类的使用采用 metaclass 关键字声明# 在前面学习 abc 模块的时候,我们也使用过类似的语法
# 【1】创建一个类class MyClass(object, metaclass=MyType): ...
# 【2】初始化类MyClass()
# MyType的__init__被调用,class_name是: MyClass# MyType的__init__被调用,class_bases是: (<class 'object'>,)# MyType的__init__被调用,class_dict是: {'__module__': '__main__', '__qualname__': 'MyClass'}(4)进阶使用
- 限制类名必须首字母大写
class MyType(type): def __init__(cls, class_name, class_bases, class_dict): print("MyType的__init__被调用,class_name是:", class_name) print("MyType的__init__被调用,class_bases是:", class_bases) print("MyType的__init__被调用,class_dict是:", class_dict) if not class_name.istitle(): raise TypeError(f'类名 {class_name} 必须首字母大写') super().__init__(class_name, class_bases, class_dict)
# 元类的使用区别于我们继承中使用父类的那种形式# 元类的使用采用 metaclass 关键字声明# 在前面学习 abc 模块的时候,我们也使用过类似的语法
# 【1】创建一个符合规范的类# (1)创建类class Myclass(object, metaclass=MyType): ...
# (2)初始化类Myclass()
# 符合规范会被正常调用# MyType的__init__被调用,class_name是: Myclass# MyType的__init__被调用,class_bases是: (<class 'object'>,)# MyType的__init__被调用,class_dict是: {'__module__': '__main__', '__qualname__': 'Myclass'}
# 【二】创建一个不符合规范的类# (1)创建类class myclass(object, metaclass=MyType): ...
# (2)初识化类myclass()
# 不符合规范会直接报错# raise TypeError(f'类名 {class_name} 必须首字母大写')# TypeError: 类名 myclass 必须首字母大写【5】元类的进阶使用
(1)引入
- 回想我们刚刚学过的 __call__方法
- 在类内部定义一个 __call__方法,对象加括号会自动执行产生该对象的类里面的 __call__方法,并且可以得到对应的返回值
- 类加括号调用,同理类加括号也会触发元类中的__call__方法,从而获得返回值,而这个返回值正是我们实例化类所得到的对象
class MyType(type): def __init__(cls, class_name, class_bases, class_dict): print(f'MyType 中的 init 方法 我被触发了') super().__init__(class_name, class_bases, class_dict)
def __call__(self, *args, **kwargs): print(f'MyType 中的 call 方法 我被触发了') print(f'args: {args}, kwargs: {kwargs}') # 创建对象并初始化参数 obj = super().__call__(*args, **kwargs) # print(obj) # 将产生的对象返回出去 return obj
class MyClass(metaclass=MyType): def __init__(self, name): print(f' MyClass 中的 init 方法 我被触发了') self.name = name
def __call__(self, *args, **kwargs): print(f' MyClass 中的 call 方法 我被触发了') return 'call 方法返回的值'
obj = MyClass(name='张三')print(obj)print(obj())
# MyType 中的 init 方法 我被触发了# MyType 中的 call 方法 我被触发了# args: (), kwargs: {'name': '张三'}# MyClass 中的 init 方法 我被触发了# <__main__.MyClass object at 0x1287b3dc0># MyClass 中的 call 方法 我被触发了# call 方法返回的值(2)定制对象的产生过程
class MyType(type): def __init__(cls, class_name, class_bases, class_dict): print(f'MyType 中的 init 方法 我被触发了') super().__init__(class_name, class_bases, class_dict)
def __call__(self, *args, **kwargs): print(f'MyType 中的 call 方法 我被触发了') print(f'args: {args}, kwargs: {kwargs}') # 定制对象的产生条件:只能通过关键字传参创建对象,不允许通过位置传参 if args: raise TypeError(f'{self.__name__} 产生对象只能通过关键字传参创建') # 创建对象并初始化参数 obj = super().__call__(*args, **kwargs) # print(obj) # 将产生的对象返回出去 return obj
class MyClass(metaclass=MyType): def __init__(self, name): print(f' MyClass 中的 init 方法 我被触发了') self.name = name
def __call__(self, *args, **kwargs): print(f' MyClass 中的 call 方法 我被触发了') return 'call 方法返回的值'
# 正确的obj = MyClass(name='张三')print(obj)print(obj())
# MyType 中的 init 方法 我被触发了# MyType 中的 call 方法 我被触发了# args: (), kwargs: {'name': '张三'}# MyClass 中的 init 方法 我被触发了# <__main__.MyClass object at 0x1287b3dc0># MyClass 中的 call 方法 我被触发了# call 方法返回的值
# 错误的,会被拦截并报错obj = MyClass('张三')print(obj)print(obj())
# raise TypeError(f'{self.__name__} 产生对象只能通过关键字传参创建')# TypeError: MyClass 产生对象只能通过关键字传参创建【6】总结
- 元类属于面向对象中比较高阶的用法,无特殊需求和特殊情况外不建议使用
- 如果单纯为了装逼,当我没说,嘿嘿
- 如果你想高度定制类的产生过程
- 那么编写元类里面的__init__方法
- 如果你想高度定制对象的产生过程
- 那么编写元类里面的__call__方法
【补充】__new__方法
- __new__用于产生空对象(类),相当于人的骨架
- __init__用于实例化对象(类),相当于人的血肉
(1)元类中的__new__直接调用
# 注意:并不是所有的地方都可以直接调用__new__ 该方法过于底层# 如果是在元类的__new__里面 可以直接调用class MyMeta(type): def __init__(cls, class_name, class_bases, class_dict): print(f'MyType 中的 init 方法 我被触发了') super().__init__(class_name, class_bases, class_dict)
def __new__(cls, *args, **kwargs): print(f'MyType 中的 new 方法 我被触发了') print(args) print(kwargs) obj = type.__new__(cls, *args, **kwargs) return obj
def __call__(self, *args, **kwargs): print(f'MyType 中的 call 方法 我被触发了') print(f'args: {args}, kwargs: {kwargs}') # 创建对象并初始化参数 obj = super().__call__(*args, **kwargs) # print(obj) # 将产生的对象返回出去 return obj
class MyClass(object, metaclass=MyMeta): def __init__(self, name): print(f' MyClass 中的 init 方法 我被触发了') self.name = name
def __call__(self, *args, **kwargs): print(f' MyClass 中的 call 方法 我被触发了') return 'call 方法返回的值'
# def __new__(cls, *args, **kwargs): # print(f' MyClass 中的 new 方法 我被触发了') # print(args, kwargs) # return super().__new__(cls)
p = MyClass(name='dream')
# MyType 中的 new 方法 我被触发了# ('MyClass', (<class 'object'>,), {'__module__': '__main__', '__qualname__': 'MyClass', '__init__': <function MyClass.__init__ at 0x120897640>, '__call__': <function MyClass.__call__ at 0x1208b96c0>})# {}# MyType 中的 init 方法 我被触发了# MyType 中的 call 方法 我被触发了# args: (), kwargs: {'name': 'dream'}# MyClass 中的 init 方法 我被触发了(2)元类中的__call__间接调用
# 注意:并不是所有的地方都可以直接调用__new__ 该方法过于底层# 如果是在元类的__new__里面 可以直接调用class MyMeta(type): def __init__(cls, class_name, class_bases, class_dict): print(f'MyType 中的 init 方法 我被触发了') super().__init__(class_name, class_bases, class_dict)
def __new__(cls, *args, **kwargs): print(f'MyType 中的 new 方法 我被触发了') print(args) print(kwargs) obj = type.__new__(cls, *args, **kwargs) return obj
def __call__(self, *args, **kwargs): print(f'MyType 中的 call 方法 我被触发了') print(f'args: {args}, kwargs: {kwargs}') # 创建对象并初始化参数 obj = super().__call__(*args, **kwargs) # print(obj) # 将产生的对象返回出去 return obj
class MyClass(object, metaclass=MyMeta): def __init__(self, name): print(f' MyClass 中的 init 方法 我被触发了') self.name = name
def __call__(self, *args, **kwargs): print(f' MyClass 中的 call 方法 我被触发了') return 'call 方法返回的值'
def __new__(cls, *args, **kwargs): print(f' MyClass 中的 new 方法 我被触发了') print(args, kwargs) # return super().__new__(cls) obj=object.__new__(cls) obj.__init__(*args, **kwargs) return obj
p = MyClass(name='dream')支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!
相关文章智能推荐
1
C 语言发展史
编程语言了解 C 语言从 BCPL、B 语言到现代标准 C 的发展历程、核心特点与应用领域。
2
C 语言开发环境的安装与配置
编程语言在 Windows、Linux 与 macOS 上安装 C 工具链,配置编辑器,并编译运行第一个 C 程序。
3
C 语言初识:程序运行机制与基本结构
编程语言认识 C 程序从源代码到运行的完整流程,以及基础语法和程序的基本结构。
4
C 语言基础:注释、关键字、变量与数据类型
编程语言学习 C 语言的注释、关键字、标识符、常量、变量和基本数据类型等基础知识。
5
C 语言数据类型
编程语言理解 C 语言的数据类型体系,包括基本类型、整数类型、浮点类型和逻辑类型。
随机文章随机推荐














