Python 类和对象——魔法方法(一)

本文详细介绍了Python中的魔法方法,包括__init__、__new__、__del__等生命周期方法,__str__、__repr__等转换方法,以及比较、算术和位运算相关的重载方法。通过实例展示了如何使用这些方法增强类的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下代码仅为满足好奇心而为,充满了形式主义。大多数代码都可通过普通方式实现。

 我自己的C++学习小群(尽管现在只有我一个人(或许现在有两个人)):洛谷的团队向着胜利奔赴 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

什么是魔法方法

我们知道,在python的类中,我们可以定义各种各样的方法(method),而大部分方法需要手动调用,就像这样:

class Example(object):
    def hello(self):
        print("hello")
        
        
obj = Example()
obj.hello()

除此以外,通俗点说,不需要手动调用,就是解释器内部在某些特殊情况自动调用的方法,就是魔法方法。

在C++中,重载魔法方法就是重载运算符,比如int operator+(const int other);等

魔法方法为避免和普通方法冲突,都以“__”开头,并以其结尾。

魔法方法知多少

那有哪些不需要手动调用的方法呢?其实我们至少知道一个:

__init__

函数原型:def __init__(self) -> None

在类成员初始化的过程中,我们就用到了这个方法。

注意:__init__函数永远返回None

class Example(object):
    def __init__(self):
        self.name = "example"

    def for_example(self):
        print(f"for {self.name}")


obj = Example()
obj.for_example()

而在初始化成员的过程中,我们已经自动调用过这个方法了。那么,__init__方法在初始化过程中是不是第一个被调用的方法呢?

__new__

函数原型:def __new__(cls, *args, **kwargs)

实际上,在__init__方法执行之前,还有一个方法——__new__方法会先被执行。

需要注意的是,这虽然是一个类方法(class method),但是是唯一一个不需要使用@classmethod描述符的类方法。

记住,__new__方法的返回值是类对象(通过super调用),而参数第一个是cls(代表当前类),余下的是__init__函数的参数(除了self)。当然,如果你怕麻烦,也可以直接使用*args, **kwargs来节省时间(敲代码的时间)。

 一个简单的例子:

class Example(object):
    def __new__(cls, name):
        print(name)
        return super().__new__(cls)

    def __init__(self, name):
        self.name = name
        print("example")

    def for_example(self):
        print(f"for example, it's called {self.name}")


obj = Example("jin")
obj.for_example()

结果表明,new是先于init执行的。实际上,编译器的工作原理是先从__new__方法获得对象,再把它放进__init__方法进行初始化,__new__方法是探索荒地,__init__方法是拓荒。

obj = __new__ -> __init__(obj)

__del__

函数原型:def __del__(self)

使用完对象后,我们自然要使用del关键字进行销毁。因此,我们又有了__del__魔法方法,在del obj的时候会进行调用。

class Example(object):
    def __new__(cls, name):
        print(name)
        return super().__new__(cls)

    def __init__(self, name):
        self.name = name
        print("example")

    def __del__(self):
        print("deleted")

    def for_example(self):
        print(f"for example, it's called {self.name}")


obj = Example("jin")
obj.for_example()
del obj

通过刚刚的代码,我相信你对del函数已经有了一个清楚的了解。

__call__

函数原型:def __call__(self, *args, **kwargs)

有的时候,如果我还希望这个对象能像函数一样工作,比如说在多态的运用中:

def run_func(func, *args, **kwargs):
    return func(*args, **kwargs)


run_func(print, 1)
run_func(input)

这样就可以进行函数的调用,并把函数的调用的这一过程进行了封装。不过很明显,这里并不能传一个自定义对象

class Example(object):
    pass


def run_func(func, *args, **kwargs):
    func(*args, **kwargs)


obj = Example()
run_func(obj)

如果尝试运行这段代码,会报出错误:

Traceback (most recent call last):
          line 10, in <module>
                    run_func(obj)
          line 6, in run_func
                    func(*args, **kwargs)

TypeError: 'Example' object is not callable

错误的原因是因为对象是不可调用(not callable)的,那么如何让它变得可调用呢?不要担心,Python给我们提供了一个魔法方法来实现此功能。

__call__

比如说,下面这段代码:

class Example(object):
    def __call__(self, *args, **kwargs):
        print("For example")


def run_func(func, *args, **kwargs):
    func(*args, **kwargs)


obj = Example()
run_func(obj)

在执行run_func中的func的过程中,func实际上就是Example类的__call__方法,如果我们直接使用obj()来调用,会得到相同的结果。

class Example(object):
    def __call__(self, *args, **kwargs):
        print("For example")


obj = Example()
obj()

__int__/__bool__/__complex__

函数原型:

def __int__(self) -> int

def __bool__(self) -> bool

def __complex__(self) -> complex

这三个函数的功能比较简单,__int__方法就是对此对象进行int(obj)方法返回的结果;__complex__方法就是对此对象进行complex(obj)方法返回的结果;__bool__方法就是对此对象进行bool(obj)方法返回的结果,同时,if obj和while obj中的obj的布尔值也是通过bool(obj),也就是obj.__bool__()来决定的,若__bool__未定义,bool(obj)的值永远为True。

For example:

class Example(object):
    def __call__(self, *args, **kwargs):
        print("For example")

    
    def __int__(self):
        return 100

    
    def __bool__(self):
        return False


obj = Example()
print(bool(obj))
print(int(obj))

__str__/__repr__

函数原型:

def __str__(self) -> str

def __repr__(self) -> str

这两个方法都是用来获得他们的字符串值的,print函数是调用了它的__str__方法,命令行窗口中直接obj会返回他的__repr__值,__str__方法对应的是str函数,__repr__方法对应的是repr函数。通俗的说str的值是给人(程序员)看的,repr的值是给system(Python解释器)看的。在__str__函数未定义的情况下,print(obj)会打印出一个莫名其妙的值。

下面给出了一个小例子:

class Example(object):
    def __str__(self):
        return "I'm an example."

    def __repr__(self):
        return "example"


obj = Example()
print(obj)
print(repr(obj))

__eq__/__ne__/__le__/__ge__/__lt__/__gt__/__cmp__

函数原型:def __func__(self, other) -> bool

下面我们要来介绍以下七个比较函数,他们的作用就是重载"==""!=""<="">=""<"">"运算符,而最后一个__cmp__魔法方法,它代表的是使用sort进行排序时的默认比较函数。由于比较简单,我就不放例子了。

注意:__cmp__魔法方法应当在self < other时返回负整数,self == other时返回0,self > other时返回正整数。

__add__/__sub__/__mul__/__truediv__/__floordiv__/__mod__/__pow__

函数原型:def __func__(self, other)

这七个方法同样比较简单,就是加、减、乘、除、地板除、模、乘方七个函数,下面放一个简单的小例子:

class Example(object):
    name = ""

    def __add__(self, string):
        new_obj = Example()
        new_obj.name = self.name + string
        return new_obj


obj = Example()
print(obj.name)
obj = obj + "for"
print(obj.name)
obj = obj + " example"
print(obj.name)

__iadd__/__isub__/__imul__/__itruediv__/__ifloordiv__/__imod__/__ipow__

函数原型:def __func__(self, other)

这七个函数和前面很像,它们定义的赋值运算,如__iadd__定义的是+=操作符,这是一个小例子:

class Example(object):
    name = ""

    def __iadd__(self, string):
        self.name += string
        return self


obj = Example()
print(obj.name)
obj += "for"
print(obj.name)
obj += " example"
print(obj.name)

注意:这七个函数最后一定要返回self,因为它实际上就是__add__函数,只是解释器自动把原来的obj更新成了__iadd__函数返回的self,也就是obj += other是obj = obj.__iadd__(other);obj = obj + other是obj = obj.__add__(other)

__radd__/__rsub__/__rmul__/__rtruediv__/__rfloordiv__/__rmod__/__rpow__

函数原型:def __func__(self, other)

显然,这七个函数和前面的也很像,如

obj + other,当obj不支持__add__方法或__add__方法返回NotImplemented时被调用,也就是:

def add(a, b):
    try:
        value = a.__add__(b)
        if value == NotImplemented:
            raise TypeError
        return value
    except TypeError:
        value = b.__radd__(a)
        if value == NotImplemented:
            raise TypeError
        return value

下面放一个简单的小例子,其实就是把前面__add__函数改成了__radd__函数:

class Example(object):
    name = ""

    def __radd__(self, string):
        new_obj = Example()
        new_obj.name = self.name + string
        return new_obj


obj = Example()
print(obj.name)
obj = "for" + obj
print(obj.name)
obj = " example" + obj
print(obj.name)

__pos__/__neg__/__abs__

函数原型:

def __pos__(self)

def __neg__(self)

def __abs__(self)

这三个函数的重载也比较简单,我就不放demo了。其中__pos__的意思是正号的结果,__neg__是负号的结果,__abs__是abs(obj)的结果,即:

+obj => obj.__pos__()

-obj => obj.__neg__()

abs(obj) => obj.__abs__()

__lshift__/__rshift__/__and__/__or__/__xor__/__invert__

函数原型:

def __lshift__(self, other)

def __rshift__(self, other)

def __and__(self, other)

def __or__(self, other)

def __xor__(self, other)

def __invert__(self)

这六个函数都是和位运算相关的,可能稍许有些难度,就不展开讲了,它们分别重载了

"<<"">>""&""|""^""~"运算符,有需要可酌情使用。

一道练习题

实现一个高精度类型(即它的每一位数字都存储在一个列表里)

E.G.

class LongInt(object):
    value = []

    def init(self, value):
        while value:
            self.value.insert(0, value % 10)
            value //= 10


integer = LongInt()
integer.init(13579)
print(integer.value)

你需要重载LongInt类型的运算符,任务如下:

必做:

在无符号范围内:

  • 比较运算符;
  • 加减运算符;
  • 对应的赋值运算符和反运算符;
  • 初始化(重载__new__,可以不重载__init__);
  • 转int,float类型;
  • 字符串表示及repr表示;
  • 销毁运算(重载del obj).

选做:

  • 位运算;
  • 乘除运算符;
  • 乘方运算,模运算,地板除法运算;
  • 带符号运算(正负号),绝对值;
  • 一个Complex类:复数的四则运算;
  • 自定义快读/快写函数.

如果你写好了,可以把代码发到评论区里(如果你不想被抄袭,也可以直接发给我)。

Time to 点赞

看完后,别忘了

点赞!

收藏!

Thanks……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

嘉定世外的JinJiayang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值