V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
saximi
V2EX  ›  Python

请问怎么理解例子中类实例化的输出?

  •  
  •   saximi · 2017-07-25 21:41:11 +08:00 · 1721 次点击
    这是一个创建于 2679 天前的主题,其中的信息可能已经有所发展或是发生改变。
    class Meta_1(type):
    def __call__(cls, *a, **kw):
    print("entering Meta_1.__call__()") #语句 1
    rv = super(Meta_1, cls).__call__(*a, **kw) #语句 2
    print("exiting Meta_1.__call__()") #语句 3
    return rv

    class Class_1(object,metaclass = Meta_1):
    def __new__(cls, *a, **kw):
    print("entering Class_1.__new__()") #语句 4
    rv = super(Class_1, cls).__new__(cls, *a, **kw)
    print("exiting Class_1.__new__()")
    return rv

    def __init__(self, *a, **kw):
    print("executing Class_1.__init__()")
    super(Class_1,self).__init__(*a, **kw)

    print("---test---")
    c = Class_1()


    输出如下:
    ---test---
    entering Meta_1.__call__()
    entering Class_1.__new__()
    exiting Class_1.__new__()
    executing Class_1.__init__()
    exiting Meta_1.__call__()


    我的问题是:
    1、模块被加载时,类定义的__new__和__init__就应该被执行了吧?为何从上面程序的输出来看并没有执行,因为第一个输出就是"---test---",说明是等到实际执行程序主体时才发生的输出。
    2、类定义的__new__和__init__应该是在__call__方法之前执行的吧,但为何实际上先执行的却是 Meta_1 的__call__方法,而不是 Class_1 的__new__和__init__?
    2、从输出来看,语句 1 执行完毕后,不是执行紧接着的语句 2,而是去执行语句 4 了,这又是为什么呢?
    17 条回复    2017-07-29 01:55:03 +08:00
    ivechan
        1
    ivechan  
       2017-07-26 15:57:08 +08:00
    1. 类在被实例化的时候才会调用类方法__new__ , 之后才会调用实例的 __init__ 方法. 模块被加载你指的是什么时候?
    import module 的时候 ?
    2. 前面一句没有错. 但是 Meta_1 和 Class_1 又不是同属一个类. 一般元类的__call__ 里面才会调用 Class_1 的 __new__来生成一个新的类.
    3. 没有证据看到不是执行语句 2...不知道怎么说
    saximi
        2
    saximi  
    OP
       2017-07-26 20:22:08 +08:00
    @ivechan 非常感谢指点!
    1、我说的模块被加载的时候就是指 import 的时候,也就是在执行主程序体第一条语句( print("---test---") )之前的时间。
    2、这个例子中,元类的__call__ 里面并没有调用 Class_1 的 __new__吧?
    3、语句 2 是执行的是 Meta_1 的元类,也就是 type 的__call__方法没错吧?难道这条语句会导致语句 4 的执行,如果不是导致执行语句 4 的话,就不会有这个输出了:“ entering Class_1.__new__() ”
    wwqgtxx
        3
    wwqgtxx  
       2017-07-26 22:40:44 +08:00 via iPhone
    首先,类只有在实例化(即 类名(args))的时候才会先调用__new__再调用__init__
    第二,metaclass 就是负责实例化这个过程的,即 类名(args)这个调用等同于 Meta 类名.__call__(类名,args)
    而你自定义的类中的__new__和__init__本质上是 type.__call__调用的,所以造成了你看到的输出顺序
    saximi
        4
    saximi  
    OP
       2017-07-27 21:18:12 +08:00
    我之所以说在 import module 时,类定义中的__new__和__init__会被先执行是有例子的,参见下面的代码,
    在主程序第一条语句(语句 1)之前,语句 2 和语句 3 就先被执行了,也就说__new__和__init__方法在执行主程序之前就执行了。我的理解对么?

    <p>class&nbsp;MyType(type):&nbsp;&nbsp;</p>
    <p>&nbsp;&nbsp;&nbsp;&nbsp;def&nbsp;__init__(self,&nbsp;what,&nbsp;bases=None,&nbsp;dict=None):&nbsp;&nbsp;</p>
    <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;super().__init__(what,&nbsp;bases,&nbsp;dict)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print("call&nbsp;MyType.__init__()")&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#语句 3
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;def&nbsp;__new__(cls,&nbsp;name,&nbsp;bases,&nbsp;attrs):&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print("call&nbsp;MyType.__new__()")&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#语句 2
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;type.__new__(cls,&nbsp;name,&nbsp;bases,&nbsp;attrs)&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;def&nbsp;__call__(self,&nbsp;*args,&nbsp;**kwargs):&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print("MyType.__call__&nbsp;")
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;obj&nbsp;=&nbsp;self.__new__(self,&nbsp;*args,&nbsp;**kwargs)&nbsp;&nbsp;&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.__init__(obj)&nbsp;

    </p><p>class&nbsp;Foo(object,&nbsp;metaclass=MyType):&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;def&nbsp;__init__(self,&nbsp;name=None):&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;self.name&nbsp;=&nbsp;name&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print("Foo.__init__&nbsp;self=",&nbsp;self)&nbsp;&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;def&nbsp;__new__(cls,&nbsp;*args,&nbsp;**kwargs):&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print("Foo.__new__&nbsp;cls=",&nbsp;cls)&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return(object.__new__(cls,&nbsp;*args,&nbsp;**kwargs))&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;def&nbsp;__call__(self,&nbsp;cls):&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print("Foo.__call__&nbsp;cls=",&nbsp;cls)&nbsp;&nbsp;
    </p><p>&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    </p><p>if&nbsp;__name__&nbsp;==&nbsp;'__main__':&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;print("---------test1---------")&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#语句 1&nbsp;
    </p><p>&nbsp;&nbsp;&nbsp;&nbsp;obj2=Foo()</p><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

    </p><p>输出如下:
    </p><p>call&nbsp;MyType.__new__()
    </p><p>call&nbsp;MyType.__init__()
    </p><p>---------test1---------
    </p><p>MyType.__call__&nbsp;
    </p><p>Foo.__new__&nbsp;cls=&nbsp;<class&nbsp;'__main__.Foo'>
    </p><p>Foo.__init__&nbsp;self=&nbsp;<__main__.Foo&nbsp;object&nbsp;at&nbsp;0x01C2B2B0>
    </p>
    saximi
        5
    saximi  
    OP
       2017-07-27 21:19:33 +08:00
    抱歉上面的格式太乱了,我不知道回复时不会做格式转换,下面是重新整理后的内容。
    我之所以说在 import module 时,类定义中的__new__和__init__会被先执行是有例子的,参见下面的代码,
    在主程序第一条语句(语句 1)之前,语句 2 和语句 3 就先被执行了,也就说__new__和__init__方法在执行主程序之前就执行了。我的理解对么?

    class MyType(type):
    def __init__(self, what, bases=None, dict=None):
    super().__init__(what, bases, dict)
    print("call MyType.__init__()") #语句 3
    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__ ")
    obj = self.__new__(self, *args, **kwargs)
    self.__init__(obj)

    class Foo(object, metaclass=MyType):
    def __init__(self, name=None):
    self.name = name
    print("Foo.__init__ self=", self)
    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("---------test1---------") #语句 1
    obj2=Foo()

    输出如下:
    call MyType.__new__()
    call MyType.__init__()
    ---------test1---------
    MyType.__call__
    Foo.__new__ cls= <class '__main__.Foo'>
    Foo.__init__ self= <__main__.Foo object at 0x01C2B2B0>
    saximi
        6
    saximi  
    OP
       2017-07-27 21:25:00 +08:00
    @wwqgtxx 关于您说的第二点:“ metaclass 就是负责实例化这个过程的,即 类名(args)这个调用等同于 Meta 类名.__call__(类名,args) 而你自定义的类中的__new__和__init__本质上是 type.__call__调用的,所以造成了你看到的输出顺序”。我还是不明白。
    我知道“ c = Class_1() ”会调用 Meta 类的 call 方法,我不理解的是执行这个 call 方法时,遇到这条语句“ rv = super(Meta_1, cls).__call__(*a, **kw) ”时到底发生了什么事,这时不就是执行 type.__call__方法么?怎么会输出“ entering Class_1.__new__() ” ? 这个输出难道不是只有在执行 Class_1.__new__方法时才会输出的么?
    wwqgtxx
        7
    wwqgtxx  
       2017-07-27 21:30:21 +08:00 via iPhone
    @saximi 因为 type.__call__.调用了 Class_1.__new__呀…
    wwqgtxx
        8
    wwqgtxx  
       2017-07-27 21:33:24 +08:00 via iPhone
    @saximi 你上面那个情况先出来 call 两个 MyType 的情况是因为你指定了 MyType 作为 Class_1 的 metaclass,所以 python 先实例化了一个 MyType 给 Class_1 备用
    wwqgtxx
        9
    wwqgtxx  
       2017-07-27 21:37:07 +08:00 via iPhone
    你要明白 python 是解析式语言,所以语句是顺序执行的,当你定义 Class_1 并指定 MyType 作为它的 metaclass 的时候就隐含的初始化了一个 MyType 的实例
    而你的 main 是放在最后的,所以当然是后输出___test1___
    wwqgtxx
        10
    wwqgtxx  
       2017-07-27 21:44:08 +08:00 via iPhone
    你要注意看就会发现__call__方法是一个绑定方法,即他需要一个 self 参数,也就是说他必须在一个对象实例上调用,而 python 不可能每次在实例化 Class_1 的时候都实例化一个新的 MyType,这样又耗费性能和内存,又完全没有意义,所以他是在你类定义的时候就初始化了一个 MyType 的实例,然后每次实例化 Class_1 的时候都调用这个 MyType 的实例的__call__方法
    最后,实际上你初始化一个 Class_1 实例的调用栈是这样的

    Class_1() -> 定义类的时候保存的 MyType 的实例.__call() -> type.__call__ -> Class_1.__new__ -> object.__new__ -> Class_1.__init__

    然后反向出栈
    saximi
        11
    saximi  
    OP
       2017-07-27 22:33:14 +08:00
    @wwqgtxx 非常感谢您的耐心解答,我发现我现在就卡在这一句话上了:
    "因为 type.__call__.调用了 Class_1.__new__呀…" 不明白这是一个什么知识点,我也查过一些博文和书,但是都没有深入提到这一点,能否再详细说明一下,或者有关于这点的透彻说明的文档推荐也行,万分感谢了!
    wwqgtxx
        12
    wwqgtxx  
       2017-07-28 06:36:32 +08:00 via iPhone
    @saximi 这个地方你不需要明白,只要记住就行了,所有__xx__的方法都是用来给解析器或者系统库隐式调用的,至于具体为什么,你需要去看 cpython 的源代码,我记得这一部分是用 c 写的。就好比 c++初始化一个类就会调用构造方法一样,这个是编译器行为,过度探究原因并没有什么意义
    wwqgtxx
        13
    wwqgtxx  
       2017-07-28 10:01:02 +08:00
    @saximi 我给你找了一下 type.__call__的具体实现,你可以自己看看
    https://github.com/python/cpython/blob/master/Objects/typeobject.c#L921
    saximi
        14
    saximi  
    OP
       2017-07-28 21:15:52 +08:00
    @wwqgtxx 太感谢你了!
    既然 MyType 作为 Class_1 的 metaclass,导致 Class_1 类定义的时候会自动实例化一个 MyType 实例。
    那么如果程序还有一个类 Class_2,且 Class_1 是 Class_2 的父类,那么在执行 Class_2 类定义的时候,是不是还会另外多执行一遍 MyType 的实例化动作(执行 new 和 init)?也就是说 Class_1 有多少个之类,MyType 就会被自动实例化多少次?
    我从代码的输出来看,推断应该是这个处理方式,但是没想明白,为何在定义子类的时候,竟然会去将父类(Class_1)的元类进行实例化? 以及为何每个子类都要实例化一次元类,而不能所有的子类公用定义父类时实例化出来的元类?
    saximi
        15
    saximi  
    OP
       2017-07-28 22:13:10 +08:00
    @wwqgtxx cpython 的源码我准备去看看,但是未必都能看懂,关于这一个知识点我准备硬记下来,请您看看我这么归纳对么?
    存在一个类 Meta,Meta 是类 Clas 的元类,那么对于 Meta 类定义中的 type.__call__方法调用,实际上是去调用了类 Clas 的__new__和__init__方法
    wwqgtxx
        16
    wwqgtxx  
       2017-07-28 23:06:01 +08:00 via iPhone   ❤️ 1
    @saximi 至于为什么子类不公用父类的元类实例,这个问题其实很好解释,你有注意到 Meta 类的__init__方法了么,他传递进来了一个 what 参数不知道你注意到没,这个参数实际上传进来的就是你定义的 Class 这个类本身(注意不是 Class 的实例,是 Class.__class__),而如果你的子类公用了父类的元类实例,这个参数就会导致问题,他将无法正常初始化一个子类对象而仅仅是初始化出来一个父类对象而已

    至于你说的第二点,仔细看看我给你找的源码就应该知道,type.__call__做的事情其实就是调用了类的__new__来生成了一个空对象,然后把这个对象传入__init__来初始化,所以你的归纳基本上正确
    saximi
        17
    saximi  
    OP
       2017-07-29 01:55:03 +08:00
    @wwqgtxx 顿首拜!!!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1773 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 16:27 · PVG 00:27 · LAX 08:27 · JFK 11:27
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.