python3 的元类问题 心地善良的给些指点吧 - V2EX
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
waibunleung
V2EX    Python

python3 的元类问题 心地善良的给些指点吧

  •  
  •   waibunleung 2019-07-12 21:03:00 +08:00 2705 次点击
    这是一个创建于 2290 天前的主题,其中的信息可能已经有所发展或是发生改变。
    # -*- coding:utf-8 -*- class MyMeta(type): def __new__(cls, name, bases, attr): # attr['add'] = lambda self, x, y : x+y # attr['age'] = 0 attr['addr'] = 'gz' return type.__new__(cls, name, bases, attr) def __init__(cls, name, bases, attr): super(MyMeta, cls).__init__(name, bases, attr) attr['age'] = 0 cls.gender = 'male' print(cls) print(name) print(bases) print(attr) class MyClass(object, metaclass=MyMeta): def __init__(self): # self.age = 1 self.name = 'hh' m = MyClass() print(m.name) print(m.gender) print(m.addr) print(m.age) # output: <class '__main__.MyClass'> MyClass (<class 'object'>,) {'__module__': '__main__', '__qualname__': 'MyClass', '__init__': <function MyClass.__init__ at 0x1100cb268>, 'addr': 'gz', 'age': <function MyMeta.__init__.<locals>.<lambda> at 0x1100cb2f0>} hh male gz Traceback (most recent call last): File "/Users/luengwaiban/Desktop/meta.py", line 32, in <module> print(m.age) AttributeError: 'MyClass' object has no attribute 'age' 

    上面的代码里,MyMeta 是元类,MyClass 是使用元类实例化的类。 我在元类中的__new__()方法中的 addr 参数里添加 age 元素后,在 MyClass 实例化后是可以正常访问到 age 的。 但是现在屏蔽掉__new__()方法中的往 addr 参数里添加 age 元素的语句,将它放到__init__()方法中的 addr 参数里,却发现在 MyClass 实例化后是访问不到 age,但是将 age 绑在__init__()的 cls 上却可以访问(类似于 gender 的绑定)。

    这样子我就不是很理解了,为什么在元类中的__init__()方法里,将属性添加到 attr 后,MyClass 实例化完成后却访问不到对应的属性?但是将同样的操作放到元类中的__new__()方法中却可以?

    22 条回复    2019-07-18 17:12:15 +08:00
    Trim21
        1
    Trim21  
       2019-07-12 21:54:29 +08:00 via Android   1
    元类是用来操作类的,所以 myclass 不实例化也可以访问到 age 属性,在这里 age 和 gendar 都是类属性而不是实例属性

    因为 new 的调用在 myclass 被创建之前,修改了创建类的参数(就是调用 type.__new__的那一句),而 init 的调用在 myclass 被创建之后,类对象已经创建完了
    txy3000
        2
    txy3000  
       2019-07-13 11:56:27 +08:00
    你在元类定义的属性都是以其为元类的类属性啊 你类的实例不能访问 这样就可以访问 MyClass.age MyClass.addr
    waibunleung
        3
    waibunleung  
    OP
       2019-07-13 15:03:50 +08:00
    感谢前面的回复,我的疑问是普通类可以在__init__()中初始化成员,但是为什么在元类中的__init__()却不可以?@Trim21
    @txy3000
    Trim21
        4
    Trim21  
       2019-07-13 15:17:30 +08:00 via iPhone
    元类不是普通类的父类,普通类是实例化的元类
    你在元类 init 的时候修改的类属性修改的是普通类的类属性变量,而不是像普通类一样修改的是普通类实例的属性变量
    Trim21
        5
    Trim21  
       2019-07-13 15:20:49 +08:00 via iPhone
    @Trim21 元类 init 对应的 cls 就是那个普通类,你在这里的确修改了普通类的类属性,跟你在普通类的 init 里面修改 self.attribute 是同一个道理
    因为元类里面的普通类就对应普通类里面的类实例
    txy3000
        6
    txy3000  
       2019-07-13 15:58:33 +08:00
    感觉你是想了解 python 底层的运行机制 https://github.com/zpoint/CPython-Internals/blob/master/BasicObject/type/type_cn.md 配合 https://github.com/python/cpython 源码 应该能满足你的需求
    waibunleung
        7
    waibunleung  
    OP
       2019-07-13 20:04:35 +08:00
    @Trim21 就算我在元类 init 方法里修改的是普通类的属性,那为什么 m.age 访问不了?
    Trim21
        8
    Trim21  
       2019-07-13 20:10:00 +08:00
    可以访问啊,你访问不了是因为你把__new__里面的 attr['age']里面给注释掉了

    waibunleung
        9
    waibunleung  
    OP
       2019-07-13 20:24:24 +08:00
    @Trim21 所以老哥你误解了我的问题了,我描述里面已经说了我在__new__方法里注释掉了那个 age 的赋值,把它挪到__init__方法去了,我想问的就是为什么放在__new__方法里可以访问,但是放到__init__方法里却不行....
    (__new__方法里那个 age 的赋值是我故意注释掉的)
    Trim21
        10
    Trim21  
       2019-07-13 20:28:32 +08:00
    @waibunleung #9 我没理解错啊, 我上面解释过了, 你在__init__里面要修改 age 属性的话, 要通过 cls.age, 不能通过 attr['age']
    waibunleung
        11
    waibunleung  
    OP
       2019-07-13 20:51:40 +08:00
    @Trim21 我不是没有尝试去理解你的话,但是我还是不能明白为什么我 在__init__里面要修改 age 属性的话, 要通过 cls.age, 不能通过 attr['age']....
    在普通类里的 init 都可以修改,还是说我不能这样子对比?
    Trim21
        12
    Trim21  
       2019-07-13 21:04:38 +08:00
    可以这样子对比啊

    普通类里面, 赋值是给 self.age 赋值, self 是__init__的第一个参数

    所以在元类里面,也是给__init__的第一个参数的 age 属性赋值, 也就是修改 cls.age, 这不是一样的吗

    区别在于一个赋值是给普通类的实例属性, 一个是类属性
    waibunleung
        13
    waibunleung  
    OP
       2019-07-13 21:18:23 +08:00
    @Trim21 我懂你的意思,但是我的问题是,为什么在元类的__init__方法中的第四个参数,这里我写作 attr,添加 age 元素后,没办法访问到 age 属性呢?
    Trim21
        14
    Trim21  
       2019-07-13 21:31:16 +08:00
    @waibunleung #13 init 里面的 attr 参数是从你 new 里面那个 attr 参数传递过来的,如果你在 new 里面没加 age 属性,在 init 里面也找不到 attr['age'
    frostming
        15
    frostming  
       2019-07-13 22:55:43 +08:00
    我明白楼主的意思了,楼主的意思是为什么可以在__new__里面动态给 attr 添加属性而__init__里面不可以

    type.__init__具体做了什么我也不清楚。结论就是,设计就是这样,如果要给 attr 添加属性,就要在__new__里面做。至于为什么这就要看源码实现了。
    waibunleung
        16
    waibunleung  
    OP
       2019-07-14 14:20:33 +08:00
    @frostming 这个是通过观察得来的结论了,就是想知道具体原才这么问的
    waibunleung
        17
    waibunleung  
    OP
       2019-07-14 14:31:59 +08:00
    @Trim21 我不需要从 init 里面找到 age 属性,我就是想通过 init 方法去给它加上 age 这个属性
    todd7zhang
        18
    todd7zhang  
       2019-07-15 11:26:28 +08:00
    元类的__init__调用的时候, 类已经创建完毕, 你要给 MyClass 赋新的类属性 age, 当然是要用 cls.age = 0 咯, 在元类__init__里面, 你对原来的参数 attr 里面赋值, 又有什么用呢?

    对 Myclass 增加属性, 要么是在类创建之前, 对参数修改, 然后被 type.__new__调用. 要么在 type.__init__里面, 类已经创建, 再对 cls 赋值属性
    telnetning
        19
    telnetning  
       2019-07-16 19:30:22 +08:00   1
    我的一点简单理解,供参考,对 C 不太熟,不一定对,楼主也可以自己看一下,逻辑在 Objects/typeobject.c 中。

    __new__ 和 __init__ 中的 attr 本身就只是个 dict,并没有什么特殊的意义,区别在于 type.__new__ 和 type.__init__ 对 attr 的处理。
    在 type_new 中:

    ```py
    static PyObject *
    type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)

    /* Check arguments: (name, bases, dict) */
    if (!PyArg_ParseTuple(args, "UO!O!:type.__new__", &name, &PyTuple_Type,
    &bases, &PyDict_Type, &orig_dict))
    return NULL;

    ...
    dict = PyDict_Copy(orig_dict);
    ....

    type->tp_dict = dict;
    ```

    即 attr 中最终传入到 tp_dict 中,也就是作为了 类的 member。

    在 type_init 中,源码中并未对 attr 做特殊处理。要想修改类,只能修改 cls。

    ```py
    static int
    type_init(PyObject *cls, PyObject *args, PyObject *kwds)
    ```
    waibunleung
        20
    waibunleung  
    OP
       2019-07-16 23:29:30 +08:00
    @telnetning 良心解答,以感谢
    zpoint
        21
    zpoint  
       2019-07-18 17:09:49 +08:00   1
    @telnetning 这位老哥已经解答了, 我再补充下


    执行到如下这行的时候

    class MyClass(object, metaclass=MyMeta):

    会调用 type(MyMeta).__call__ 去创建这个类, 这个 __call__ 函数在 C 里面的流程可以

    简单的理解为 1. 调用 MyMeta.__new__ 生成一个类, 叫做 MyClass, __new__ 是上面你自己定义的, 其中你调用了 type.__new__(cls, name, bases, attr), 这一步会把 attr 中的值都复制到 MyMeta 对应的属性中, attr 只是个字典而已

    2. 判断一下 issubclass(type(MyClass), MyMeta) 是否为 True, 是的话再调用一下 type(MyClass).__init__(MyClass, name, bases, attr), 这里你没有写任何代码处理 attr 和自身属性的关联, 同样的, attr 还是同一个字典

    到这里, 类已经创建完了, 接下来创建实例, 过程类似
    区别就是 __new__ 你写了一行代码 type.__new__(cls, name, bases, attr) 创建了一个类, 创建的过程中会把 attr 中的值都复制到新创建的类中对应的属性上

    而 __init__ 你没有做对应的操作


    还有, metaclass 的 __new__ 的第一个参数应该是 mcs, 为你定义的 metaclass 本身
    而 metaclass 的 __init__ 的第一个参数应该是 cls, 为 metaclass 的 __new__ 函数创建并返回的新的类, 并不是 metaclass 本身 你定义的时候重名了
    zpoint
        22
    zpoint  
       2019-07-18 17:12:15 +08:00
    更正一下错别字

    调用 MyMeta.__new__ 生成一个类, 这里生成的类名称叫做 MyClass, __new__ 是上面你自己定义的, 其中你调用了 type.__new__(cls, name, bases, attr), 这一步会把 attr 中的值都复制到 MyClass 对应的属性中, attr 只是个字典
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2581 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 07:53 PVG 15:53 LAX 00:53 JFK 03:53
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86