在中高级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 魔术方法是一种特殊的命名约定,用双下划线包围,用于实现对象的特殊行为和操作。它们包括但不限于初始化、字符串表示、比较、算术运算、上下文管理等方面。通过合理使用魔术方法,可以提高代码的可读性、可维护性,并实现更灵活的编程逻辑,是面向对象编程里一种非常重要的实现方法。