Python技术迷

魔法函数 __call__怎么使用?

有一次我半夜写代码,脑袋迷糊到不行,本来要写个函数,手一抖就多加了个 __call__,结果程序居然还跑通了,我还以为是我电脑懂事了,自动帮我补代码。后来仔细想想,哦,原来是这个魔法方法在作怪。

先别急,啥是 __call__?

简单说,你平时定义一个类,然后用括号 () 去调用它的实例,是不是报错? 普通类实例默认是不能“像函数一样被调用”的,但如果你在类里写了 __call__,那这个实例就变成了一个可调用对象,就像函数一样用括号传参数。

来个简单的栗子:

classGreeter:
def__init__(self, name):
        self.name = name

def__call__(self, message):
        print(f"{self.name} 说: {message}")

g = Greeter("小明")
g("早上好")  # 相当于 g.__call__("早上好")

输出:

小明 说: 早上好

看到没?g("早上好") 其实背后是调用了 g.__call__("早上好")。

那它有啥用?

说实话,一开始我也觉得这是个“花里胡哨”的功能,但后来在几个场景下用得还挺爽:

  1. 把对象当函数用比如写一个计数器,既能保存状态,还能直接“调用”更新数值。
classCounter:
def__init__(self):
        self.count = 0

def__call__(self, step=1):
        self.count += step
return self.count

c = Counter()
print(c())    # 1
print(c(5))   # 6
print(c())    # 7

  1. 做回调有些框架或者 API 要求你传一个函数,但你希望这个“函数”还能带点内部状态,就可以用类+__call__ 搞定。

  2. 替代函数装饰器如果你写了个装饰器类,让它实现 __call__,就能像普通装饰器一样用 @ 语法。

classLogger:
def__init__(self, prefix):
        self.prefix = prefix

def__call__(self, func):
defwrapper(*args, **kwargs):
            print(f"[{self.prefix}] 调用 {func.__name__}")
return func(*args, **kwargs)
return wrapper

@Logger("DEBUG")
defhello():
    print("Hello world!")

hello()

输出:

[DEBUG] 调用 hello
Hello world!

注意点

  • __call__ 不是构造函数,不要和 __init__ 搞混了。
  • 如果参数太多,还是老老实实用普通方法,别全塞到 __call__ 里,不然读代码的人会一脸问号。
  • 它的主要价值是让实例既能保存数据,又能像函数一样用。

我自己的感受是,__call__ 属于那种“平时不用,但关键时刻能省半天工”的技能,尤其是写框架、工具类的时候,很香。 不过第一次见它,很多人都会愣一下,觉得“类怎么还能当函数用”,所以用的时候最好配点注释,免得队友骂你写的代码像变魔术。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

🔥虎哥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》,总量高达650GB,点击下方公众号回复关键字 python 全部免费领