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

2019-07-12 21:03:00 +08:00
 waibunleung
# -*- 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__()方法中却可以?

2200 次点击
所在节点    Python
22 条回复
Trim21
2019-07-12 21:54:29 +08:00
元类是用来操作类的,所以 myclass 不实例化也可以访问到 age 属性,在这里 age 和 gendar 都是类属性而不是实例属性

因为 new 的调用在 myclass 被创建之前,修改了创建类的参数(就是调用 type.__new__的那一句),而 init 的调用在 myclass 被创建之后,类对象已经创建完了
txy3000
2019-07-13 11:56:27 +08:00
你在元类定义的属性都是以其为元类的类属性啊 你类的实例不能访问 这样就可以访问 MyClass.age MyClass.addr
waibunleung
2019-07-13 15:03:50 +08:00
感谢前面的回复,我的疑问是普通类可以在__init__()中初始化成员,但是为什么在元类中的__init__()却不可以?@Trim21
@txy3000
Trim21
2019-07-13 15:17:30 +08:00
元类不是普通类的父类,普通类是实例化的元类
你在元类 init 的时候修改的类属性修改的是普通类的类属性变量,而不是像普通类一样修改的是普通类实例的属性变量
Trim21
2019-07-13 15:20:49 +08:00
@Trim21 元类 init 对应的 cls 就是那个普通类,你在这里的确修改了普通类的类属性,跟你在普通类的 init 里面修改 self.attribute 是同一个道理
因为元类里面的普通类就对应普通类里面的类实例
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
2019-07-13 20:04:35 +08:00
@Trim21 就算我在元类 init 方法里修改的是普通类的属性,那为什么 m.age 访问不了?
Trim21
2019-07-13 20:10:00 +08:00
可以访问啊,你访问不了是因为你把__new__里面的 attr['age']里面给注释掉了

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

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

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

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

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

对 Myclass 增加属性, 要么是在类创建之前, 对参数修改, 然后被 type.__new__调用. 要么在 type.__init__里面, 类已经创建, 再对 cls 赋值属性
telnetning
2019-07-16 19:30:22 +08:00
我的一点简单理解,供参考,对 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
2019-07-16 23:29:30 +08:00
@telnetning 良心解答,以感谢

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

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

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

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

© 2021 V2EX