likes
comments
collection
share

Python中的魔法方法:__new__、__init__和__call__的功能和应用

作者站长头像
站长
· 阅读数 84

前言

python中有很多魔法方法,好多刚接触python编程的同学对此一头雾水,更不清楚实践中该如何使用。这里先主要介绍3个魔法方法以及一些相关实践。

神奇的魔法

__new__

__new__是构造函数,负责对象的创建,他需要返回一个实例

案例一

class ClassA:

    def __new__(cls, *args, **kwargs):
        print("ClassA.__new__")
        return super().__new__(cls)


ClassA() # ClassA.__new__

可以看到输出ClassA.__new__,表明在创建对象时就会调用__new__方法

案例二

再看下面这个例子,我们让__new__不返回对象,会出现啥情况呢?

class ClassA:

    def __new__(cls, *args, **kwargs):
        print("ClassA.__new__")
        
a = ClassA()
print(a) 

'''
ClassA.__new__
None
'''

a的值为None,为什么呢?就是因为没有在__new__中返回任何对象。

注意:__new__是构造函数,负责对象的创建,他需要返回一个实例

案例三

下面这个例子,我们在__new__中返回一个其他对象,会出现啥情况呢?

class ClassA:

    def __new__(cls, *args, **kwargs):
        print("ClassA.__new__")
        return 1
        
a = ClassA()
print(a) 

'''
ClassA.__new__
1
'''

可以看到a的值为1,而不是ClassA object。真是一个神奇的方法,这喝案例表明,我们可以通过重写__new__方法来控制类对象的实例化过程。

当然也可以在ClassA的__new__方法中创建其他对象,比如下面这样

class ClassA:

    def __new__(cls, *args, **kwargs):
        print("ClassA.__new__")
        return super().__new__(ClassB)
        # return ClassB() # 也可以这样写

class ClassB:
    pass

a = ClassA()
print(a)

'''
ClassA.__new__
<__main__.ClassB object at 0x103b17a00>
'''

这样就在ClassA中创建了ClassB对象。

注意: 这里只是举个例子来讲述__new__的魔法,实践项目中避免这种写法。

__init__

__init__是一个初始化函数,负责对__new__实例化的对象进行初始化。这样看来,应该是先调用__new__再调用__init__

案例一

class ClassA:

    def __new__(cls, *args, **kwargs):
        print("ClassA.__new__ ")
        return super().__new__(cls)

    def __init__(self):
        print("ClassA.__init__")

ClassA()

'''
ClassA.__new__ 
ClassA.__init__
'''

证实了我们上面的观点,创建一个对象时,会先调用__new__方法,再调用__init__方法

__new__方法必须有返回值,那__init__方法呢?我们看下面的案例

案例二

class ClassA:
    def __init__(self, value):
        self.value = value
        return "Initialization complete"

obj = ClassA(10)

运行发现报如下错误TypeError: __init__() should return None, not 'str'

很显然,不允许有返回值。

__call__

内建函数callable 如果callable的对象参数显示为可调用,则返回True,否则返回False.如果返回True,则调用仍然可能失败;但如果为False,则调用对象永远不会成功。 我们平时自定义的函数、内置函数和类都属于可调用对象,但凡可以把一对括号"()"应用到某个对象身上时,都可称之为可调用对象

def funTest(name):
    print("this is test function, name:", name)

print(callable(filter)) # True
print(callable(max)) # True
print(callable(object)) # True
print(callable(funTest)) # True
var = "test"
print(callable(var)) # False
funTest("python")

__call__的作用就是声明这个类的对象是可调用的(callable)。即实现__call__方法之后,用callable调用这个类的对象时,结果都为True.

class ClassA:
    pass

a = ClassA()
print(callable(a)) # False

实现__call__的类

class ClassA:

    def __call__(self, *args, **kwargs):
        print("this is __call__ function, args", args)

a = ClassA()
print(callable(a)) # True
a("arg1", "arg2") # this is __call__ function, args ('arg1', 'arg2')

a是ClassA的实例对象,同时还是可调用对象,因此就可以像调用函数一样调用它

实践一:

还记得类装饰器吗,类装饰器主要依赖于函数__call__(),每当你调用一个类的实例时,函数__call__()就会被执行一次

日志功能,比如下面这段代码

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

    def __call__(self, *args, **kwargs):
        print('Calling function:', self.func.__name__)
        return self.func(*args, **kwargs)

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

result = add(3, 5)
# 输出:
# Calling function: add
# 8

在上面的示例中,Logger 类实现了 __call__ 方法,使其实例可以像函数一样被调用。它接收原始函数作为参数,并在调用时打印函数名。通过将 Logger 实例应用为装饰器,我们可以将其作用于 add 函数上,从而在函数调用时添加额外的行为。

实践二

创建一个可以像函数一样被调用的对象,而不是一个普通的函数

class Calculator:
    def __init__(self):
        self.result = 0

    def __call__(self, a, b):
        self.result = a + b
        return self.result

calc = Calculator()
result = calc(3, 5)  # 调用可调用对象
print(result)  # 输出: 8

在上面的示例中,Calculator 类实现了 __call__ 方法,使其实例可以像函数一样被调用。通过调用 calc 实例并传递参数,会触发 __call__ 方法的执行,并返回计算的结果。

总之,__call__ 方法可以在实际项目中提供更灵活和自定义的对象行为。通过实现 __call__ 方法,类的实例可以表现得像函数一样,从而增加了代码的可读性和可扩展性。

最后

详细到这里已经基本了解这三个魔法方法以及各自的用途了。但这里还是做一个总结:

  • __new__ 方法是一个类方法,负责创建并返回一个新的实例对象。它是在对象实际被创建之前调用的,因此其参数传递的是类本身(通常被称为 cls)以及 __new__ 方法所需的其他参数。__new__ 方法的返回值通常是通过调用父类的 __new__ 方法来获得新的实例对象。这个新的实例对象随后将被传递给 __init__ 方法进行初始化。
  • __init__ 方法是一个实例方法,在对象创建后被调用,负责对象的初始化。它接收的第一个参数是 self,表示正在初始化的实例对象。__init__ 方法用于设置对象的初始状态,设置属性值,执行其他必要的初始化操作。它一般不返回任何值,只是对对象进行初始化。
  • __call__ 方法是一个实例方法,用于使对象变为可调用(类似于函数)。当调用一个对象时,Python 会查找并调用该对象的 __call__ 方法。__call__ 方法可以使对象像函数一样被调用,接收参数并执行相应的操作。通过定义 __call__ 方法,可以使类的实例表现得像函数一样可调用。

再简短点概括:

  • __new__ 方法负责对象的创建和内存分配,返回一个新实例对象。
  • __init__ 方法负责对象的初始化和属性设置。
  • __call__ 方法使对象变为可调用(类似于函数)