在中高级Python开发面试中,面试官为了考察面试者是否了解面向对象编程,会通过Python魔术方法相关概念来变向提问
引言
定义和基本概念
魔术方法(Magic Methods) 是 Python 中一种特殊的函数,它们用于定义类的特定行为和属性。这些方法通常由 Python 解释器自动调用,而不是显式地在代码中调用。
它们允许你自定义类与内置操作符、函数和语法结构的交互方式,使得自定义对象能够像内置类型一样自然地使用。
命名约定
魔术方法的命名有特定的格式要求,它们都以双下划线__开头和结尾。这种命名约定使得这些方法在类中具有特殊的用途,不会与普通的方法名发生冲突。例如:
学习原因
- 提升代码可读性和可维护性
- 实现类的特殊行为和高级功能
常见魔术方法
初始化、删除、调用
-
__init__(self, ...):对象初始化方法(构造函数) -
__new__(cls, ...):创建对象实例的方法,通常与__init__搭配使用 -
__del__(self):对象销毁方法(析构函数) -
__call__(self, ...):对象被调用时,比如xxx()这样调用形式,就会调用__call__方法的内容
字符串表示
-
__str__(self):定义对象的字符串表示,适用于str()和print() -
__repr__(self):定义对象的官方字符串表示,适用于repr(),通常可通过eval()函数重建对象 -
__format__(self, format_spec):定义对象的格式化表示,适用于format()函数和格式化字符串 -
__bytes__(self):定义对象的字节串表示,适用于bytes()
数值运算
-
__neg__(self):一元负号(取负),即-self -
__pos__(self):一元正号,即+self -
__abs__(self):绝对值,即abs(self) -
__invert__(self):按位取反,即~self -
__add__(self, other):加法,即self + other -
__sub__(self, other):减法,即self - other -
__mul__(self, other):乘法,即self * other -
__matmul__(self, other):矩阵乘法,即self @ other -
__truediv__(self, other):真除法,即self / other -
__floordiv__(self, other):取整除法,即self // other -
__mod__(self, other):取模,即self % other -
__divmod__(self, other):同时计算取整除法和取模,返回(self // other, self % other) -
__pow__(self, other, modulo=None):幂运算,即self ** other -
__lshift__(self, other):左移位运算,即self << other -
__rshift__(self, other):右移位运算,即self >> other -
__and__(self, other):按位与,即self & other -
__xor__(self, other):按位异或,即self ^ other -
__or__(self, other):按位或,即self | other -
__radd__(self, other):反向加法,即other + self -
__rsub__(self, other):反向减法,即other - self -
__rmul__(self, other):反向乘法,即other * self -
__rmatmul__(self, other):反向矩阵乘法,即other @ self -
__rtruediv__(self, other):反向真除法,即other / self -
__rfloordiv__(self, other):反向取整除法,即other // self -
__rmod__(self, other):反向取模,即other % self -
__rdivmod__(self, other):反向同时计算取整除法和取模,返回(other // self, other % self) -
__rpow__(self, other, modulo=None):反向幂运算,即other ** self -
__rlshift__(self, other):反向左移位运算,即other << self -
__rrshift__(self, other):反向右移位运算,即other >> self -
__rand__(self, other):反向按位与,即other & self -
__rxor__(self, other):反向按位异或,即other ^ self -
__ror__(self, other):反向按位或,即other | self -
__iadd__(self, other):增量加法赋值,即self += other -
__isub__(self, other):增量减法赋值,即self -= other -
__imul__(self, other):增量乘法赋值,即self *= other -
__imatmul__(self, other):增量矩阵乘法赋值,即self @= other -
__itruediv__(self, other):增量真除法赋值,即self /= other -
__ifloordiv__(self, other):增量取整除法赋值,即self //= other -
__imod__(self, other):增量取模赋值,即self %= other -
__ipow__(self, other, modulo=None):增量幂运算赋值,即self **= other -
__ilshift__(self, other):增量左移位赋值,即self <<= other -
__irshift__(self, other):增量右移位赋值,即self >>= other -
__iand__(self, other):增量按位与赋值,即self &= other -
__ixor__(self, other):增量按位异或赋值,即self ^= other -
__ior__(self, other):增量按位或赋值,即self |= other
类型转换
-
__int__(self):定义对象到整数的转换,适用于int() -
__float__(self):定义对象到浮点数的转换,适用于float() -
__complex__(self):定义对象到复数的转换,适用于complex() -
__bool__(self):定义对象的布尔值转换,适用于bool() -
__index__(self):定义对象作为索引的转换,适用于切片操作和bin()、hex()、oct()函数
集合操作
-
__len__(self):定义对象的长度,适用于len() -
__getitem__(self, key):定义按键访问,适用于obj[key] -
__setitem__(self, key, value):定义按键赋值,适用于obj[key] = value -
__delitem__(self, key):定义按键删除,适用于del obj[key] -
__contains__(self, item):定义成员测试,适用于item in obj
迭代协议
-
__iter__(self):定义返回迭代器,适用于iter() -
__next__(self):定义返回下一个元素,适用于next()
上下文管理
-
__enter__(self):定义进入上下文管理时的行为,适用于with语句 -
__exit__(self, exc_type, exc_value, traceback):定义退出上下文管理时的行为,适用于with语句
属性访问
-
__getattr__(self, name):定义访问不存在的属性时的行为 -
__getattribute__(self, name):定义访问任何属性时的行为 -
__setattr__(self, name, value):定义设置属性时的行为 -
__delattr__(self, name):定义删除属性时的行为
描述符协议
-
__get__(self, instance, owner):定义描述符的获取行为 -
__set__(self, instance, value):定义描述符的设置行为 -
__delete__(self, instance):定义描述符的删除行为
比较操作
-
__lt__(self, other):小于,适用于< -
__le__(self, other):小于等于,适用于<= -
__eq__(self, other):等于,适用于== -
__ne__(self, other):不等于,适用于!= -
__gt__(self, other):大于,适用于> -
__ge__(self, other):大于等于,适用于>=
常见应用示例
字符串表示
__str__ 方法用于定义对象的字符串表示,通常用于用户友好的输出,而 __repr__ 方法用于定义对象的官方字符串表示,通常可以用来重建对象。
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"({self.x}, {self.y})"
def __repr__(self):
return f"Point({self.x}, {self.y})"
point = Point(3, 4)
print(str(point)) # 输出:(3, 4)
print(repr(point)) # 输出:Point(3, 4)
排序和比较
通过实现一系列比较运算的魔术方法,可以定义自定义类的排序和比较行为。
class Student:
def __init__(self, name, score):
self.name = name
self.score = score
def __lt__(self, other):
return self.score < other.score
def __eq__(self, other):
return self.score == other.score
def __repr__(self):
return f"Student({self.name}, {self.score})"
students = [
Student("Alice", 85),
Student("Bob", 75),
Student("Charlie", 90)
]
# 根据分数排序
sorted_students = sorted(students)
print(sorted_students)
# 输出:[Student(Bob, 75), Student(Alice, 85), Student(Charlie, 90)]
在这个例子中,通过实现 __lt__ 方法,定义了对象的小于比较。因此可以使用 sorted() 函数对学生列表进行排序。
实现数值类
通过重载算术运算符的魔术方法,可以实现自定义数值类,如复数或矩阵。
class ComplexNumber:
def __init__(self, real, imag):
self.real = real
self.imag = imag
def __add__(self, other):
return ComplexNumber(
self.real + other.real,
self.imag + other.imag
)
def __mul__(self, other):
return ComplexNumber(
self.real * other.real - self.imag * other.imag,
self.real * other.imag + self.imag * other.real
)
def __repr__(self):
return f"{self.real} + {self.imag}i"
# 创建两个复数
c1 = ComplexNumber(2, 3)
c2 = ComplexNumber(4, 5)
# 复数加法
print(c1 + c2) # 输出:6 + 8i
# 复数乘法
print(c1 * c2) # 输出:-7 + 22i
在这个例子中,通过重载 __add__ 和 __mul__ 方法,实现了复数的加法和乘法
集合与序列协议
通过实现集合与序列协议相关的魔术方法,可以定义自定义容器类,使其支持索引和迭代。
比如我们要实现一个既能排序又能去重的SortedSet类:
class SortedSet:
def __init__(self, items=None):
self._items = sorted(set(items)) if items else []
def __repr__(self):
return f"SortedSet({self._items})"
def __len__(self):
return len(self._items)
def __contains__(self, item):
return item in self._items
def __iter__(self):
return iter(self._items)
def __getitem__(self, index):
return self._items[index]
def add(self, item):
if item not in self._items:
self._items.append(item)
self._items.sort()
def remove(self, item):
if item in self._items:
self._items.remove(item)
# 测试 SortedSet 类
s = SortedSet([3, 1, 2, 3, 4])
print(s) # 输出:SortedSet([1, 2, 3, 4])
print(len(s)) # 输出:4
print(2 in s) # 输出:True
print(s[0]) # 输出:1
s.add(5)
print(s) # 输出:SortedSet([1, 2, 3, 4, 5])
s.remove(2)
print(s) # 输出:SortedSet([1, 3, 4, 5])
属性访问控制
通过实现属性访问相关的魔术方法,可以实现动态属性和代理模式。
class DynamicAttribute:
def __getattr__(self, name):
if name == "age":
return 25
elif name == "name":
return "Alice"
else:
raise AttributeError(
f"'DynamicAttribute' object has no attribute '{name}'"
)
obj = DynamicAttribute()
print(obj.age) # 输出:25
print(obj.name) # 输出:Alice
在这个例子中,通过实现 __getattr__ 方法,动态地为对象添加属性。如果属性不存在,则会调用这个方法来获取属性值,这在Python一些的ORM框架中很常见。
构造查询语句
python中的运算符|、&其实可以对标SQL中的or、and,所以我们可以通过魔术方法来实现一些类似SQL语句拼接
# 注:代码块暂不考虑太多异常情况,比如not操作
class Q:
def __init__(self, *expressions, operator="and", **kwargs):
self.operator = operator
self._expressions = expressions
self._kwargs = kwargs
def __or__(self, other):
return Q(self, other, operator="or")
def __str__(self):
return self.translate()
def translate(self):
kv_sql = " and ".join([f"{k}={v}" for k, v in self._kwargs.items()])
expr_sql = f" {self.operator} ".join([expr.translate() for expr in self._expressions])
return expr_sql + (f" {self.operator} " + kv_sql if expr_sql and kv_sql else kv_sql)
# 示例用法
q1 = Q(name='Alice', height=168)
q2 = Q(age=25, gender=0)
q3 = Q(q1, age=25)
# 实现 OR 运算符
print(q1 | q2)
# 输出:name=Alice and height=168 or age=25 and gender=0
print(q3)
# 输出:name=Alice and height=168 and age=25
上面例子中kwargs参数代表的是参与and操作的拼接键值对参数,而|则表示两个对象要进行or操作的拼接,这样就可以实现类似ORM框架里的还原SQL字符串 拼接操作了
上下文管理
上下文管理是 Python 中用于管理资源的一种机制,它可以确保在进入和离开代码块时资源得到正确的管理和释放。在使用 with 语句时,Python 会调用对象的 __enter__ 方法来获取上下文管理器,并在离开代码块时调用 __exit__ 方法来释放资源。
class MyResource:
def __enter__(self):
print("Entering the resource")
return self
def __exit__(self, exc_type, exc_value, traceback):
print("Exiting the resource")
def operation(self):
print("Performing operation")
# 使用上下文管理器
with MyResource() as resource:
resource.operation()
# Outputs
# Entering the resource
# Performing operation
# Exiting the resource
在这个示例中,MyResource 类实现了上下文管理器。当进入 with 代码块时,Python 会调用 __enter__ 方法,然后执行其中的代码;当离开 with 代码块时,Python 会调用 __exit__ 方法,以确保资源被正确释放。
可调用对象
__call__ 方法使得一个对象可以像函数一样被调用,这对于实现可调用对象非常有用。当调用对象时,Python 解释器会自动调用对象的 __call__ 方法。
class MyCallable:
def __call__(self, x):
return x * 2
# 创建可调用对象
callable_obj = MyCallable()
# 调用可调用对象
result = callable_obj(5)
print(result) # 输出:10
在这个示例中,MyCallable 类实现了 __call__ 方法,使得它的实例可以像函数一样被调用。当调用 callable_obj(5) 时,Python 实际上调用了 callable_obj.__call__(5)。
__new__ 和 __init__
__new__ 方法和 __init__ 方法都是 Python 中用于创建类实例的特殊方法,但它们的作用略有不同。
__new__ 方法在实例创建之前调用,用于创建实例对象,并返回一个新创建的实例。
__init__ 方法在实例创建之后调用,用于初始化实例对象的属性。
class MyClass:
def __new__(cls, *args, **kwargs):
print("Creating instance")
instance = super().__new__(cls)
return instance
def __init__(self, x):
print("Initializing instance")
self.x = x
# 创建实例
obj = MyClass(5)
# Outputs
# Creating instance
# Initializing instance
在这个示例中,__new__ 方法在实例创建之前被调用,用于创建实例对象,而 __init__ 方法在实例创建之后被调用,用于初始化实例对象的属性。
总结
Python 魔术方法是一种特殊的命名约定,用双下划线包围,用于实现对象的特殊行为和操作。它们包括但不限于初始化、字符串表示、比较、算术运算、上下文管理等方面。通过合理使用魔术方法,可以提高代码的可读性、可维护性,并实现更灵活的编程逻辑,是面向对象编程里一种非常重要的实现方法。