MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python类的魔法方法解析

2021-09-017.2k 阅读

Python类的魔法方法基础概念

在Python中,类的魔法方法(Magic Methods)是一种特殊的方法,它们的方法名以双下划线开始和结束(例如 __init____add__ 等)。这些魔法方法为类提供了一种方式,使其能够与Python的内置操作符、函数以及各种系统行为进行交互。魔法方法赋予了Python类强大的功能,使得我们可以像使用内置类型一样使用自定义类。

构造和初始化魔法方法

__new__ 方法

__new__ 是一个类方法,用于创建类的新实例。它在 __init__ 方法之前被调用,并且是类实例化过程中的第一个步骤。__new__ 方法的第一个参数是类本身(通常命名为 cls),后续参数是传递给类构造函数的参数。

class MyClass:
    def __new__(cls, *args, **kwargs):
        print(f"__new__ method called for {cls.__name__}")
        return super().__new__(cls)


obj = MyClass()

在上述代码中,__new__ 方法打印了一条消息,然后调用 super().__new__(cls) 来创建新实例。super().__new__(cls) 实际上调用了 object 类的 __new__ 方法,该方法负责分配内存并返回一个新的实例对象。

__new__ 方法在某些特殊情况下非常有用,例如实现单例模式。

class Singleton:
    _instance = None

    def __new__(cls, *args, **kwargs):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance


singleton1 = Singleton()
singleton2 = Singleton()
print(singleton1 is singleton2)

在这个单例模式的实现中,__new__ 方法检查是否已经存在一个实例,如果不存在,则创建一个新实例,否则返回已有的实例。这样,无论创建多少个 Singleton 类的实例,实际上都是同一个对象。

__init__ 方法

__init__ 方法是我们最常使用的初始化方法,它在实例创建之后被调用。__init__ 方法的第一个参数是实例本身(通常命名为 self),后续参数是传递给类构造函数的参数。

class Point:
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y


p = Point(1, 2)
print(p.x, p.y)

在上述代码中,__init__ 方法接受 xy 参数,并将它们赋值给实例的属性 self.xself.y。这样,在创建 Point 实例时,就可以同时初始化其坐标。

__init__ 方法主要用于对实例进行初始化设置,而 __new__ 方法主要负责创建实例对象。

__del__ 方法

__del__ 方法是析构方法,在对象被销毁时调用。当对象的引用计数变为零(即没有任何变量引用该对象),或者程序结束时,Python的垃圾回收机制会调用 __del__ 方法。

class Resource:
    def __init__(self):
        print("Resource created")

    def __del__(self):
        print("Resource destroyed")


r = Resource()
del r

在上述代码中,当创建 Resource 实例时,__init__ 方法打印 “Resource created”。当使用 del r 删除对象引用时,__del__ 方法打印 “Resource destroyed”。

需要注意的是,由于Python的垃圾回收机制的复杂性,__del__ 方法并不总是会立即被调用。此外,循环引用等情况可能会导致对象无法被正确销毁,从而 __del__ 方法也不会被调用。

数学运算相关魔法方法

一元运算符魔法方法

__neg__ 方法

__neg__ 方法用于实现一元负号操作(-)。它返回一个与原对象数值相反的新对象。

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __neg__(self):
        return MyNumber(-self.value)


num = MyNumber(5)
neg_num = -num
print(neg_num.value)

在上述代码中,MyNumber 类实现了 __neg__ 方法。当对 MyNumber 实例使用一元负号操作时,__neg__ 方法会创建一个新的 MyNumber 实例,其值为原实例值的相反数。

__pos__ 方法

__pos__ 方法用于实现一元正号操作(+)。通常情况下,它返回对象本身,因为一元正号操作不改变对象的值。

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __pos__(self):
        return self


num = MyNumber(5)
pos_num = +num
print(pos_num.value)

在这个例子中,__pos__ 方法简单地返回 self,即对象本身。

__abs__ 方法

__abs__ 方法用于实现 abs() 函数,返回对象的绝对值。

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __abs__(self):
        return MyNumber(abs(self.value))


num = MyNumber(-5)
abs_num = abs(num)
print(abs_num.value)

在上述代码中,__abs__ 方法创建一个新的 MyNumber 实例,其值为原实例值的绝对值。

二元运算符魔法方法

__add__ 方法

__add__ 方法用于实现加法操作(+)。它接受另一个对象作为参数,并返回两个对象相加的结果。

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)


v1 = Vector(1, 2)
v2 = Vector(3, 4)
v_sum = v1 + v2
print(v_sum.x, v_sum.y)

在上述代码中,Vector 类实现了 __add__ 方法。当两个 Vector 实例相加时,__add__ 方法会创建一个新的 Vector 实例,其 xy 坐标分别为两个操作数的 xy 坐标之和。

__sub__ 方法

__sub__ 方法用于实现减法操作(-)。它接受另一个对象作为参数,并返回两个对象相减的结果。

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)


v1 = Vector(3, 5)
v2 = Vector(1, 2)
v_diff = v1 - v2
print(v_diff.x, v_diff.y)

在这个例子中,Vector 类的 __sub__ 方法创建一个新的 Vector 实例,其 xy 坐标分别为两个操作数的 xy 坐标之差。

__mul__ 方法

__mul__ 方法用于实现乘法操作(*)。

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)


v = Vector(2, 3)
scaled_v = v * 2
print(scaled_v.x, scaled_v.y)

在上述代码中,Vector 类的 __mul__ 方法实现了向量与标量的乘法。它返回一个新的 Vector 实例,其 xy 坐标分别为原向量坐标与标量的乘积。

__truediv__ 方法

__truediv__ 方法用于实现真除法操作(/)。

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __truediv__(self, other):
        return MyNumber(self.value / other.value)


num1 = MyNumber(10)
num2 = MyNumber(2)
result = num1 / num2
print(result.value)

在上述代码中,MyNumber 类的 __truediv__ 方法实现了两个 MyNumber 实例的真除法操作,并返回一个新的 MyNumber 实例。

__floordiv__ 方法

__floordiv__ 方法用于实现整除操作(//)。

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __floordiv__(self, other):
        return MyNumber(self.value // other.value)


num1 = MyNumber(10)
num2 = MyNumber(3)
result = num1 // num2
print(result.value)

在这个例子中,MyNumber 类的 __floordiv__ 方法实现了整除操作,并返回一个新的 MyNumber 实例。

比较运算符魔法方法

__lt__ 方法

__lt__ 方法用于实现小于比较操作(<)。它返回一个布尔值,表示当前对象是否小于另一个对象。

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __lt__(self, other):
        return self.value < other.value


num1 = MyNumber(5)
num2 = MyNumber(10)
print(num1 < num2)

在上述代码中,MyNumber 类的 __lt__ 方法比较两个 MyNumber 实例的值,并返回比较结果。

__le__ 方法

__le__ 方法用于实现小于等于比较操作(<=)。

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __le__(self, other):
        return self.value <= other.value


num1 = MyNumber(5)
num2 = MyNumber(5)
print(num1 <= num2)

在这个例子中,MyNumber 类的 __le__ 方法实现了小于等于比较操作。

__eq__ 方法

__eq__ 方法用于实现等于比较操作(==)。

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __eq__(self, other):
        return self.value == other.value


num1 = MyNumber(5)
num2 = MyNumber(5)
print(num1 == num2)

在上述代码中,MyNumber 类的 __eq__ 方法比较两个 MyNumber 实例的值是否相等。

__ne__ 方法

__ne__ 方法用于实现不等于比较操作(!=)。

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __ne__(self, other):
        return self.value != other.value


num1 = MyNumber(5)
num2 = MyNumber(10)
print(num1 != num2)

在这个例子中,MyNumber 类的 __ne__ 方法实现了不等于比较操作。

__gt__ 方法

__gt__ 方法用于实现大于比较操作(>)。

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __gt__(self, other):
        return self.value > other.value


num1 = MyNumber(10)
num2 = MyNumber(5)
print(num1 > num2)

在上述代码中,MyNumber 类的 __gt__ 方法比较两个 MyNumber 实例的值,并返回大于比较的结果。

__ge__ 方法

__ge__ 方法用于实现大于等于比较操作(>=)。

class MyNumber:
    def __init__(self, value):
        self.value = value

    def __ge__(self, other):
        return self.value >= other.value


num1 = MyNumber(10)
num2 = MyNumber(10)
print(num1 >= num2)

在这个例子中,MyNumber 类的 __ge__ 方法实现了大于等于比较操作。

容器相关魔法方法

序列相关魔法方法

__len__ 方法

__len__ 方法用于实现 len() 函数,返回容器对象的长度。

class MyList:
    def __init__(self, data):
        self.data = data

    def __len__(self):
        return len(self.data)


my_list = MyList([1, 2, 3, 4])
print(len(my_list))

在上述代码中,MyList 类实现了 __len__ 方法,使得可以对 MyList 实例使用 len() 函数获取其内部数据的长度。

__getitem__ 方法

__getitem__ 方法用于实现通过索引获取容器元素的操作。

class MyList:
    def __init__(self, data):
        self.data = data

    def __getitem__(self, index):
        return self.data[index]


my_list = MyList([10, 20, 30])
print(my_list[1])

在这个例子中,MyList 类的 __getitem__ 方法允许通过索引访问内部数据列表中的元素。

__setitem__ 方法

__setitem__ 方法用于实现通过索引设置容器元素的操作。

class MyList:
    def __init__(self, data):
        self.data = data

    def __setitem__(self, index, value):
        self.data[index] = value


my_list = MyList([1, 2, 3])
my_list[1] = 10
print(my_list.data)

在上述代码中,MyList 类的 __setitem__ 方法允许通过索引修改内部数据列表中的元素。

__delitem__ 方法

__delitem__ 方法用于实现通过索引删除容器元素的操作。

class MyList:
    def __init__(self, data):
        self.data = data

    def __delitem__(self, index):
        del self.data[index]


my_list = MyList([1, 2, 3])
del my_list[1]
print(my_list.data)

在这个例子中,MyList 类的 __delitem__ 方法允许通过索引删除内部数据列表中的元素。

映射相关魔法方法

__getitem__ 方法(映射版本)

在映射类型(如字典)中,__getitem__ 方法用于通过键获取值。

class MyDict:
    def __init__(self):
        self.data = {}

    def __getitem__(self, key):
        return self.data[key]


my_dict = MyDict()
my_dict.data['name'] = 'John'
print(my_dict['name'])

在上述代码中,MyDict 类实现了 __getitem__ 方法,使得可以像使用字典一样通过键获取值。

__setitem__ 方法(映射版本)

__setitem__ 方法在映射类型中用于通过键设置值。

class MyDict:
    def __init__(self):
        self.data = {}

    def __setitem__(self, key, value):
        self.data[key] = value


my_dict = MyDict()
my_dict['age'] = 30
print(my_dict.data)

在这个例子中,MyDict 类的 __setitem__ 方法允许通过键值对的方式设置内部数据字典中的值。

__delitem__ 方法(映射版本)

__delitem__ 方法在映射类型中用于通过键删除键值对。

class MyDict:
    def __init__(self):
        self.data = {}

    def __delitem__(self, key):
        del self.data[key]


my_dict = MyDict()
my_dict['city'] = 'New York'
del my_dict['city']
print(my_dict.data)

在上述代码中,MyDict 类的 __delitem__ 方法允许通过键删除内部数据字典中的键值对。

上下文管理相关魔法方法

__enter____exit__ 方法

__enter____exit__ 方法用于实现上下文管理器协议。上下文管理器允许我们在代码块进入和退出时执行特定的操作,通常用于资源管理,如文件操作、数据库连接等。

class FileManager:
    def __init__(self, filename, mode):
        self.filename = filename
        self.mode = mode

    def __enter__(self):
        self.file = open(self.filename, self.mode)
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.file.close()


with FileManager('test.txt', 'w') as f:
    f.write('Hello, World!')

在上述代码中,FileManager 类实现了 __enter____exit__ 方法。__enter__ 方法打开文件并返回文件对象,__exit__ 方法在代码块结束时关闭文件。通过 with 语句使用 FileManager 实例,确保文件在使用完毕后正确关闭,无论代码块中是否发生异常。

__exit__ 方法的参数 exc_typeexc_valexc_tb 分别表示异常类型、异常值和追溯对象。如果代码块中没有发生异常,这三个参数都为 None。如果需要在发生异常时进行特殊处理,可以在 __exit__ 方法中进行判断和处理。

class ErrorHandler:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type:
            print(f"An error occurred: {exc_type.__name__}, {exc_val}")
            return True
        return False


with ErrorHandler() as eh:
    result = 1 / 0

在这个例子中,ErrorHandler 类的 __exit__ 方法捕获并打印了异常信息,并返回 True 表示异常已被处理。如果返回 False,异常将继续向上传播。

可调用对象相关魔法方法

__call__ 方法

__call__ 方法使得类的实例可以像函数一样被调用。

class Adder:
    def __init__(self, num):
        self.num = num

    def __call__(self, other):
        return self.num + other


adder = Adder(5)
result = adder(3)
print(result)

在上述代码中,Adder 类实现了 __call__ 方法。创建 Adder 实例 adder 后,可以像调用函数一样调用它,并传入参数 3__call__ 方法返回 self.num 与传入参数之和。

这种将实例变成可调用对象的方式在很多场景下非常有用,例如实现装饰器类。

class Logger:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        print(f"Calling function {self.func.__name__}")
        result = self.func(*args, **kwargs)
        print(f"Function {self.func.__name__} returned {result}")
        return result


@Logger
def add(a, b):
    return a + b


result = add(2, 3)

在这个例子中,Logger 类是一个装饰器类,通过 __call__ 方法在被装饰函数调用前后打印日志信息。

字符串表示相关魔法方法

__str__ 方法

__str__ 方法用于返回对象的字符串表示,通常用于用户友好的输出。当使用 print() 函数或 str() 函数转换对象为字符串时,会调用 __str__ 方法。

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Point(x={self.x}, y={self.y})"


p = Point(1, 2)
print(p)

在上述代码中,Point 类的 __str__ 方法返回一个描述点坐标的字符串,使得 print(p) 输出一个易于理解的字符串表示。

__repr__ 方法

__repr__ 方法也用于返回对象的字符串表示,但它主要用于开发和调试目的,返回的字符串应该是一个可以用来重新创建对象的表达式。当在交互式环境中输入对象或使用 repr() 函数时,会调用 __repr__ 方法。

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Point({self.x}, {self.y})"


p = Point(1, 2)
print(repr(p))

在这个例子中,Point 类的 __repr__ 方法返回的字符串可以直接用于创建相同的 Point 对象,方便在开发过程中进行调试和对象的重建。

通常,__repr__ 方法返回的字符串应该比 __str__ 方法返回的字符串更详细、更适合开发者阅读。如果没有定义 __str__ 方法,Python会尝试使用 __repr__ 方法来提供字符串表示。

描述符相关魔法方法

__get____set____delete__ 方法

描述符是一个具有 __get____set____delete__ 方法的对象。描述符允许我们自定义属性的访问行为。

class Integer:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise ValueError('Expected an integer')
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]


class MyClass:
    age = Integer('age')


obj = MyClass()
obj.age = 30
print(obj.age)

在上述代码中,Integer 类是一个描述符类。MyClass 类中的 age 属性是 Integer 类的实例。当访问 obj.age 时,会调用 Integer 类的 __get__ 方法;当设置 obj.age 时,会调用 __set__ 方法,并且在设置值之前会检查值是否为整数类型。__delete__ 方法用于删除属性。

描述符在很多高级Python编程场景中非常有用,例如实现数据验证、属性代理等功能。

通过深入理解和运用Python类的魔法方法,我们能够极大地扩展自定义类的功能,使其与Python的各种内置机制无缝集成,编写出更加简洁、高效和优雅的代码。无论是实现数学运算、容器操作,还是进行上下文管理和描述符定义,魔法方法都为我们提供了强大的工具。在实际编程中,根据具体需求合理选择和实现魔法方法,将有助于提升代码的质量和可维护性。