魔术方法
在 Python 中,所有以“__”双下划线包起来的方法,都统称为“Magic Method”,中文称:"魔术方法"
这里只补充内置(类)方法中的魔术方法
字符串相关
改变对象的字符串显示的魔术方法
- 用于控制台输出显示的
__str__
- 用于程序调试显示的
__repr__
- 自定制格式化字符串
__format__
内置函数str
或者print
会调用 obj.__str__()
内置函数repr
会调用 obj.__repr__()
如果__str__
没有被定义,那么就会使用__repr__
来代替输出(这个很容易导致递归需要留意)
注意:这俩方法的返回值必须是字符串,否则抛出异常
# _*_coding:utf-8_*_
format_dict = {
"nat": "{obj.name}-{obj.addr}-{obj.type}", # 学校名-学校地址-学校类型
"tna": "{obj.type}:{obj.name}:{obj.addr}", # 学校类型:学校名:学校地址
"tan": "{obj.type}/{obj.addr}/{obj.name}", # 学校类型/学校地址/学校名
}
class School:
def __init__(self, name, addr, type):
self.name = name
self.addr = addr
self.type = type
def __repr__(self):
return "School(%s,%s)" % (self.name, self.addr)
def __str__(self):
return "(%s,%s)" % (self.name, self.addr)
def __format__(self, format_spec):
# if format_spec
if not format_spec or format_spec not in format_dict:
format_spec = "nat"
fmt = format_dict[format_spec]
return fmt.format(obj=self)
s1 = School("oldboy1", "北京", "私立")
print("from repr: ", repr(s1))
print("from str: ", str(s1))
print(s1)
print(format(s1, "nat"))
print(format(s1, "tna"))
print(format(s1, "tan"))
print(format(s1, "asfdasdffd"))
%s
和%r
class B:
def __str__(self):
return "str : class B"
def __repr__(self):
return "repr : class B"
b = B()
print("%s" % b)
print("%r" % b)
在之前章节中提到的:
在基础字符串类型中,魔术方法__repr__
返回的是原始字符串类型。
而基础的容器类型中__str__
对容器内部成员一般是用的__repr__
方法。
例如:
>>> '\\'.__str__()
'\\'
>>> '\\'.__repr__()
"'\\\\'"
>>> ['\\'].__str__()
"['\\\\']"
>>>
重写__repr__()
方法使容器外访问和容器内偏差
from colorama import init, Fore
init(autoreset=False)
class Color_str(str):
color = "WHITE"
def set_color(self, color):
self.color = color
return self
def __repr__(self):
"""注意不要递归"""
return f"{getattr(Fore, self.color)}{self}{Fore.RESET}"
arr = [Color_str("Y").set_color("GREEN"), Color_str("N").set_color("RED")]
print(arr[0], arr[1])
print(arr)
print(arr[0], arr[1])
是 msg 本体正常颜色的 Y 和 Nprint(arr)
打印出来的是绿色的 Y 和红色的 N:[Y, N]
索引相关
如果不实现,使用下标会出现 TypeError: '...' object is not subscriptable
__getitem__
__setitem__
__delitem__
在读取、修改、删除对象成员按索引[0]
的时候可以分别定义以上方法。
事实上内置模块operator
里就包括这些同名的方法映射了索引的操作。
而在需要切片[0:1]
时则可以用这些方法结合内置函数slice(start, stop, step)
来自定义。
class Foo:
def __init__(self, name):
self.name = name
def __getitem__(self, item):
print(self.__dict__[item])
def __setitem__(self, key, value):
self.__dict__[key] = value
def __delitem__(self, key):
print("del obj[key]时,我执行")
self.__dict__.pop(key)
def __delattr__(self, item):
print("del obj.key时,我执行")
self.__dict__.pop(item)
f1 = Foo("sb")
f1["age"] = 18
f1["age1"] = 19
del f1.age1
del f1["age"]
f1["name"] = "alex"
print(f1.__dict__)
如果一个类没实现__iter__
只实现了__getitem__
, 也是可以被iter()
执行创建初始化的迭代器的。
除此之外还有__missing__
,
通过 dict[key]
或者 dict.__getitem__
访问不存在的 key 时就会返回__missing__()
当然,dict
类并没有实现它,但是collections.defaultdict
类实现了它
其他类型想要转为 dict 类至少需要实现 keys 方法和__getitem__
实例化相关
除了__init__
还有一个__new__
(构造函数)。
实例化时触发,重写时必须返回super().__new__(cls)
class Singleton:
"""
实现一个极简的单例模式。
注意,每次即使已经实例化,`__init__`也会被调用。
而且它不是线程安全的。
"""
def __new__(cls, *args, **kw):
if not hasattr(cls, "_instance"):
cls._instance = object.__new__(cls, *args, **kw)
return cls._instance
one = Singleton()
two = Singleton()
print(one is two) # True
调用相关
__call__
对象后面加括号,触发执行__call__
。
class Foo:
def __init__(self):
pass
def __call__(self, *args, **kwargs):
print("__call__")
obj = Foo() # 执行 __init__和__new__
obj() # 执行 __call__
内置函数callable
可以用来辨别是否有实现它。
它也可以用来写装饰器:
import threading
from typing import Final, TypeVar, Generic
T = TypeVar("T")
class Singleton(Generic[T]):
"""
类装饰器实现单例
双重检查加锁保证了线程安全
Final在IDE层面防止A-B-A问题
"""
_cls: Final[type[T]]
_instance_lock: Final = threading.Lock()
_unique_instance: Final[T]
def __init__(self, cls: type[T]):
self._cls = cls
def __call__(self, *args, **kwargs) -> T:
if not hasattr(self, "_unique_instance"):
with self._instance_lock:
if not hasattr(self, "_unique_instance"):
self._unique_instance = self._cls(*args, **kwargs) # type: ignore
return self._unique_instance
@Singleton
class MyClass:
def __init__(self, name: str = ""):
self.name = name
a = MyClass("a")
b = MyClass("b")
print(a is b) # True
print(b.name) # 'a'
对象属性相关
在访问对象的属性的时候 依照这样一个优先级 经过 ③ 轮遍历:
- ①mro 中各个类的
__getattribute__
方法 - 本类的数据描述符属性
- 本实例属性(
obj.__dict__
) - 本类的非数据描述符属性
- ②mro 各个类的属性和描述符属性(
super().__dict__
) - ③mro 各个类的
__getattr__
方法 - 抛出
AttributeError
异常
__getattr__
如果找不到对象的属性时会调用这个方法
这个方法应该返回属性值或者抛出AttributeError
异常
注意,如果通过以上优先级机制能找到对象属性的话,不会调用__getattr__
方法
另外两个
__setattr__
设置对象的属性__delattr__
删除对象的属性
__getattribute__
当访问某个对象的属性时,会无条件的调用这个方法。这个方法只适用于新式类。
该方法应该返回属性值或者抛出AttributeError
异常。
当同时定义__getattribute__
和__getattr__
时,__getattr__
方法不会再被调用,除非引发AttributeError
异常
注意:
当在__getattribute__
代码块中,再次执行属性的获取操作时,
会再次触发__getattribute__
方法的调用,代码将会陷入无限递归
运算符相关
内置模块operator
里的函数与运算符映射。重写以下方法其实是影响的 operator 中函数的调用。不限于运算符,还有下标,下标赋值,长度等大部分魔术方法可定义的。
逻辑运算
布尔类型转换(如直接if
)调用__bool__
,需要返回布尔值
class A:
def __init__(self):
self.a = 1
self.b = 2
def __bool__(self):
return bool(self.a)
a = A()
if a:
print(not bool(a))
如果不定义__bool__
, 会默认以bool(__len__())
为结果,如果未定义__len__
始终为 True
比较运算
__eq__
遇到比较运算符时触发,需要返回布尔值
class A:
def __init__(self):
self.a = 1
self.b = 2
def __eq__(self, obj):
return self.a == obj.a and self.b == obj.b
a = A()
b = A()
print(a == b)
如果一个类重写了__eq__
方法,它的__hash__
就会变成 None 而继承不到。
其他运算操作符与之相似:__lt__()
、__le__()
、__ne__()
、__gt__()
和__ge__()
等
其他内置函数有关
内置函数len()
调用__len__
class A:
def __init__(self):
self.a = 1
self.b = 2
def __len__(self):
return len(self.__dict__)
a = A()
print(len(a))
内置函数hash()
调用__hash__
class A:
def __init__(self):
self.a = 1
self.b = 2
def __hash__(self):
return hash(str(self.a) + str(self.b))
a = A()
print(hash(a))