【11.0】魔法方法和元类总结
【一】常用的魔法方法
【0】什么是魔法方法
- 在类定义阶段定义,但是会根据特定条件自动触发的方法就叫魔法方法,又称为内置方法
【1】__init__
(1)触发场景
-
初始化对象的属性
-
在类实例化得到具体对象时自动触发
(2)示例
# 创建一个类:类名为 Person,继承 object
class 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,继承 object
class 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,继承 object
class 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,继承 object
class 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)
# male
print(person.sex)
# AttributeError: 'Person' object has no attribute 'sex'
【5】__setattr__
(1)触发场景
- 对象在设置属性(obj.属性名=属性值)的时候会自动触发
(2)示例
# 创建一个类:类名为 Person,继承 object
class 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 = 19
print(person.age)
# 19
person.age = '19'
# TypeError: age must be of type int
【6】__delattr__
(1)触发场景
- 尝试删除对象的属性时自动触发。
(2)示例
# 创建一个类:类名为 Person,继承 object
class 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 age
del person.sex
# AttributeError: sex
【7】__setitem__
(1)触发场景
- 当使用索引操作符(obj[key] = value)为对象的字段赋值时自动触发。
(2)示例
# 创建一个类:类名为 Person,继承 object
class 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,继承 object
class 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,继承 object
class 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')
本文来自博客园,作者:Chimengmeng,转载请注明原文链接:https://www.cnblogs.com/dream-ze/p/18040937