# -*- 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__()方法中却可以?
1
Trim21 2019-07-12 21:54:29 +08:00 via Android 1
元类是用来操作类的,所以 myclass 不实例化也可以访问到 age 属性,在这里 age 和 gendar 都是类属性而不是实例属性
因为 new 的调用在 myclass 被创建之前,修改了创建类的参数(就是调用 type.__new__的那一句),而 init 的调用在 myclass 被创建之后,类对象已经创建完了 |
2
txy3000 2019-07-13 11:56:27 +08:00
你在元类定义的属性都是以其为元类的类属性啊 你类的实例不能访问 这样就可以访问 MyClass.age MyClass.addr
|
3
waibunleung OP 感谢前面的回复,我的疑问是普通类可以在__init__()中初始化成员,但是为什么在元类中的__init__()却不可以?@Trim21
@txy3000 |
4
Trim21 2019-07-13 15:17:30 +08:00 via iPhone
元类不是普通类的父类,普通类是实例化的元类
你在元类 init 的时候修改的类属性修改的是普通类的类属性变量,而不是像普通类一样修改的是普通类实例的属性变量 |
5
Trim21 2019-07-13 15:20:49 +08:00 via iPhone
@Trim21 元类 init 对应的 cls 就是那个普通类,你在这里的确修改了普通类的类属性,跟你在普通类的 init 里面修改 self.attribute 是同一个道理
因为元类里面的普通类就对应普通类里面的类实例 |
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 源码 应该能满足你的需求
|
7
waibunleung OP @Trim21 就算我在元类 init 方法里修改的是普通类的属性,那为什么 m.age 访问不了?
|
8
Trim21 2019-07-13 20:10:00 +08:00
可以访问啊,你访问不了是因为你把__new__里面的 attr['age']里面给注释掉了
|
9
waibunleung OP @Trim21 所以老哥你误解了我的问题了,我描述里面已经说了我在__new__方法里注释掉了那个 age 的赋值,把它挪到—__init__方法去了,我想问的就是为什么放在__new__方法里可以访问,但是放到__init__方法里却不行....
(__new__方法里那个 age 的赋值是我故意注释掉的) |
10
Trim21 2019-07-13 20:28:32 +08:00
@waibunleung #9 我没理解错啊, 我上面解释过了, 你在__init__里面要修改 age 属性的话, 要通过 cls.age, 不能通过 attr['age']
|
11
waibunleung OP @Trim21 我不是没有尝试去理解你的话,但是我还是不能明白为什么我 在__init__里面要修改 age 属性的话, 要通过 cls.age, 不能通过 attr['age']....
在普通类里的 init 都可以修改,还是说我不能这样子对比? |
12
Trim21 2019-07-13 21:04:38 +08:00
可以这样子对比啊
普通类里面, 赋值是给 self.age 赋值, self 是__init__的第一个参数 所以在元类里面,也是给__init__的第一个参数的 age 属性赋值, 也就是修改 cls.age, 这不是一样的吗 区别在于一个赋值是给普通类的实例属性, 一个是类属性 |
13
waibunleung OP @Trim21 我懂你的意思,但是我的问题是,为什么在元类的__init__方法中的第四个参数,这里我写作 attr,添加 age 元素后,没办法访问到 age 属性呢?
|
14
Trim21 2019-07-13 21:31:16 +08:00
@waibunleung #13 init 里面的 attr 参数是从你 new 里面那个 attr 参数传递过来的,如果你在 new 里面没加 age 属性,在 init 里面也找不到 attr['age'
|
15
frostming 2019-07-13 22:55:43 +08:00
我明白楼主的意思了,楼主的意思是为什么可以在__new__里面动态给 attr 添加属性而__init__里面不可以
type.__init__具体做了什么我也不清楚。结论就是,设计就是这样,如果要给 attr 添加属性,就要在__new__里面做。至于为什么这就要看源码实现了。 |
16
waibunleung OP @frostming 这个是通过观察得来的结论了,就是想知道具体原因才这么问的
|
17
waibunleung OP @Trim21 我不需要从 init 里面找到 age 属性,我就是想通过 init 方法去给它加上 age 这个属性
|
18
todd7zhang 2019-07-15 11:26:28 +08:00
元类的__init__调用的时候, 类已经创建完毕, 你要给 MyClass 赋新的类属性 age, 当然是要用 cls.age = 0 咯, 在元类__init__里面, 你对原来的参数 attr 里面赋值, 又有什么用呢?
对 Myclass 增加属性, 要么是在类创建之前, 对参数修改, 然后被 type.__new__调用. 要么在 type.__init__里面, 类已经创建, 再对 cls 赋值属性 |
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) ``` |
20
waibunleung OP @telnetning 良心解答,以感谢
|
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 本身 你定义的时候重名了 |
22
zpoint 2019-07-18 17:12:15 +08:00
更正一下错别字
调用 MyMeta.__new__ 生成一个类, 这里生成的类名称叫做 MyClass, __new__ 是上面你自己定义的, 其中你调用了 type.__new__(cls, name, bases, attr), 这一步会把 attr 中的值都复制到 MyClass 对应的属性中, attr 只是个字典 |