Py面试必问:细说魔术方法与应用

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值