请教关于类实例化时调用__call__方法的问题,为何有时会调用,有时却不会?

2017-07-19 20:38:36 +08:00
 saximi

class MyType(type):

 def __init__(self, what, bases=None, dict=None):   
     print('call myType.__init__()')   #语句 1  
     super().__init__(what, bases, dict)   

 def __new__(cls, name, bases, attrs):   
     print("call MyType.__new__()")      #语句 2  
     return type.__new__(cls, name, bases, attrs)   

 def __call__(self, *args, **kwargs):  
     print("MyType.__call__")  

class Foo(object, metaclass=MyType):

 def __init__(self, name=None):           
     self.name = name   
     print("Foo self.name=", self.name)   
 def __new__(cls, *args, **kwargs):   
     print("Foo.__new__ cls=", cls)   
     return(object.__new__(cls, *args, **kwargs))   
 def __call__(self, cls):   
     print("Foo.__call__ cls=", cls)   

if name == 'main':
print("---------test---------") #语句 3
obj=Foo() #语句 4

上面代码在 PYTHON3.6 中输出如下:
call MyType.new() #语句 2 的输出 call myType.init() #语句 1 的输出
---------test---------
MyType.call #语句 4 的输出

请问,为何语句 4 “ obj=Foo()” 会导致__call__方法的执行? 我看了这篇博文 python.jobbole.com/83747/ 博文的“ 5.call”小节写了这段话:“注:构造方法的执行是由创建对象触发的,即:对象=类名();而对于__call__方法的执行是由对象后加括号触发的,即:对象()或者类()()”

看了这段话后再结合上面这个程序,我就觉得有问题,obj=Foo()这个语句应该是博文中的 对象=类名() 这个形式,所以不应该调用 Foo.call()方法才对啊? 关于这个观点,我还有一个例子可以说明,比如下面这段代码和对应的输出,请注意语句 1 就是一个把对象实例化的语句,这个语句并没有调用类的__call__()方法,所以两个例子出现了矛盾,同样是类的实例化,上面的程序调用了类的 call 方法,下面的却没有:

class Deco:

def __init__(self,func): 
    self.func=func 
    print("__init__执行完毕。func=",self.func) 

def __call__(self,*arg,**arg2): 
    print("开始执行__call__。") 
    self.func('abc') 
    print(self,arg,arg2) 

class MyCls():

@Deco 
def myFunc(self): 
    print('this is my work arg is %s'%self) 

mycls=MyCls()
deco=Deco(mycls.myFunc)

代码输出如下: __init__执行完毕。func= <function MyCls.myFunc at 0x01C49AE0>

__init__执行完毕。func= <main.Deco object at 0x01C4B190>

1907 次点击
所在节点    Python
8 条回复
lolizeppelin
2017-07-19 22:23:31 +08:00
初学不要去关心 new 之类的黑魔法 先避开

装饰器也尽量避开不要折腾

装饰器只是个套娃语法糖,
具体工作的时候涉及到闭包还有描述器之类的黑魔法

等你比较熟了再回来弄这个
lrxiao
2017-07-19 23:11:08 +08:00
你自己写的 metaclass 的__call__啊
yufpga
2017-07-19 23:11:16 +08:00
注意到 Foo 中的 metaclass 了么,这里面涉及到元类,这个东西一时半会讲不清楚(限于自身水平原因),你可以去掉 metaclass 部分,打印一下 obj.__class__, 自己慢慢体会下
saximi
2017-07-19 23:15:39 +08:00
@lrxiao 为何写了 metaclass 的__call__就会导致实例化时的调用呢,我也看过一些介绍 metaclass 的博文,但是没有针对这个问题作出解释
saximi
2017-07-19 23:23:44 +08:00
@yufpga 去掉 metaclass 的话,确实就不会发生调用 Foo 类的__call__方法了,但是就是不明白为何加上 metaclass 就会如此呢
raptium
2017-07-19 23:49:19 +08:00
https://stackoverflow.com/questions/6966772/using-the-call-method-of-a-metaclass-instead-of-new

感觉就是在做蠢事,metaclass 上定义了 __call__,导致根本没法通过()实例化了。
lrxiao
2017-07-20 00:17:02 +08:00
@saximi 因为 class 是 metaclass 的实例 相当于你这个 Foo 已经是 MyType(....) 出来的 就有 MyType.__call__
但是装饰器之间没有实例关系 不调用__call__
lrxiao
2017-07-20 00:19:27 +08:00
@raptium 实际上很常用啊

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/376509

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX