Python3 中元类的使用问题,求指点
# -*- 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__()方法中却可以?
Python3 中元类的使用问题,求指点
元类是用来操作类的,所以 myclass 不实例化也可以访问到 age 属性,在这里 age 和 gendar 都是类属性而不是实例属性
因为 new 的调用在 myclass 被创建之前,修改了创建类的参数(就是调用 type.__new__的那一句),而 init 的调用在 myclass 被创建之后,类对象已经创建完了
在Python3里,元类(metaclass)这玩意儿主要是用来定制类的创建过程的。简单说,type是所有类的默认元类,但你可以通过继承type来自定义自己的元类。
最常见的使用场景是在类定义时自动修改或注入属性。比如,你想让一个类的所有方法名都自动变成大写,就可以用元类来实现。下面是一个完整的例子:
class UpperCaseMeta(type):
def __new__(cls, name, bases, dct):
# 遍历类字典,将所有方法名改为大写
uppercase_attrs = {}
for attr_name, attr_value in dct.items():
if not attr_name.startswith('__'): # 避免修改特殊方法
uppercase_attrs[attr_name.upper()] = attr_value
else:
uppercase_attrs[attr_name] = attr_value
# 调用type的__new__创建新类
return super().__new__(cls, name, bases, uppercase_attrs)
class MyClass(metaclass=UpperCaseMeta):
def say_hello(self):
return "Hello"
def another_method(self):
return "Another"
# 测试
obj = MyClass()
print(obj.SAY_HELLO()) # 输出: Hello
# print(obj.say_hello()) # 这行会报错,因为方法名已改为大写
在这个例子里,UpperCaseMeta 元类把 MyClass 里定义的方法名全转成了大写。注意,我们是通过 metaclass=UpperCaseMeta 来指定元类的。
另一个经典用途是自动注册子类,比如在Web框架里注册URL处理器。这里再给个简单的注册例子:
class PluginMeta(type):
registry = {}
def __new__(cls, name, bases, dct):
new_class = super().__new__(cls, name, bases, dct)
if name != 'BasePlugin': # 避免注册基类
cls.registry[name] = new_class
return new_class
class BasePlugin(metaclass=PluginMeta):
pass
class PluginA(BasePlugin):
pass
class PluginB(BasePlugin):
pass
print(PluginMeta.registry) # 输出: {'PluginA': <class '__main__.PluginA'>, 'PluginB': <class '__main__.PluginB'>}
元类很强大,但别滥用,通常只在框架开发或需要深度定制类行为时才用。对于日常业务代码,用类装饰器往往更简单直观。
总结:元类是控制类创建的终极工具,但复杂度高,慎用。
你在元类定义的属性都是以其为元类的类属性啊 你类的实例不能访问 这样就可以访问 MyClass.age MyClass.addr
感谢前面的回复,我的疑问是普通类可以在__init__()中初始化成员,但是为什么在元类中的__init__()却不可以?Trim21
元类 init 对应的 cls 就是那个普通类,你在这里的确修改了普通类的类属性,跟你在普通类的 init 里面修改 self.attribute 是同一个道理
因为元类里面的普通类就对应普通类里面的类实例
感觉你是想了解 python 底层的运行机制 https://github.com/zpoint/CPython-Internals/blob/master/BasicObject/type/type_cn.md 配合 https://github.com/python/cpython 源码 应该能满足你的需求
就算我在元类 init 方法里修改的是普通类的属性,那为什么 m.age 访问不了?
可以访问啊,你访问不了是因为你把__new__里面的 attr[‘age’]里面给注释掉了
所以老哥你误解了我的问题了,我描述里面已经说了我在__new__方法里注释掉了那个 age 的赋值,把它挪到—__init__方法去了,我想问的就是为什么放在__new__方法里可以访问,但是放到__init__方法里却不行…
(__new__方法里那个 age 的赋值是我故意注释掉的)
#9 我没理解错啊, 我上面解释过了, 你在__init__里面要修改 age 属性的话, 要通过 cls.age, 不能通过 attr[‘age’]
我不是没有尝试去理解你的话,但是我还是不能明白为什么我 在__init__里面要修改 age 属性的话, 要通过 cls.age, 不能通过 attr[‘age’]…
在普通类里的 init 都可以修改,还是说我不能这样子对比?
可以这样子对比啊
普通类里面, 赋值是给 self.age 赋值, self 是__init__的第一个参数
所以在元类里面,也是给__init__的第一个参数的 age 属性赋值, 也就是修改 cls.age, 这不是一样的吗
区别在于一个赋值是给普通类的实例属性, 一个是类属性
我懂你的意思,但是我的问题是,为什么在元类的__init__方法中的第四个参数,这里我写作 attr,添加 age 元素后,没办法访问到 age 属性呢?
#13 init 里面的 attr 参数是从你 new 里面那个 attr 参数传递过来的,如果你在 new 里面没加 age 属性,在 init 里面也找不到 attr[‘age’
我明白楼主的意思了,楼主的意思是为什么可以在__new__里面动态给 attr 添加属性而__init__里面不可以
type.__init__具体做了什么我也不清楚。结论就是,设计就是这样,如果要给 attr 添加属性,就要在__new__里面做。至于为什么这就要看源码实现了。
这个是通过观察得来的结论了,就是想知道具体原因才这么问的
元类的__init__调用的时候, 类已经创建完毕, 你要给 MyClass 赋新的类属性 age, 当然是要用 cls.age = 0 咯, 在元类__init__里面, 你对原来的参数 attr 里面赋值, 又有什么用呢?
对 Myclass 增加属性, 要么是在类创建之前, 对参数修改, 然后被 type.__new__调用. 要么在 type.__init__里面, 类已经创建, 再对 cls 赋值属性
我的一点简单理解,供参考,对 C 不太熟,不一定对,楼主也可以自己看一下,逻辑在 Objects/typeobject.c 中。
new 和 init 中的 attr 本身就只是个 dict,并没有什么特殊的意义,区别在于 type.new 和 type.init 对 attr 的处理。
在 type_new 中: py<br> static PyObject *<br>type_new(PyTypeObject *metatype, PyObject *args, PyObject *kwds)<br><br> /* Check arguments: (name, bases, dict) */<br> if (!PyArg_ParseTuple(args, "UO!O!:type.__new__", &name, &PyTuple_Type,<br> &bases, &PyDict_Type, &orig_dict))<br> return NULL;<br><br> ...<br> dict = PyDict_Copy(orig_dict);<br> ....<br><br> type->tp_dict = dict;<br>
即 attr 中最终传入到 tp_dict 中,也就是作为了 类的 member。
在 type_init 中,源码中并未对 attr 做特殊处理。要想修改类,只能修改 cls。py<br>static int<br> type_init(PyObject *cls, PyObject *args, PyObject *kwds)<br>
良心解答,以感谢
这位老哥已经解答了, 我再补充下
执行到如下这行的时候
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 本身 你定义的时候重名了
更正一下错别字
调用 MyMeta.new 生成一个类, 这里生成的类名称叫做 MyClass, new 是上面你自己定义的, 其中你调用了 type.new(cls, name, bases, attr), 这一步会把 attr 中的值都复制到 MyClass 对应的属性中, attr 只是个字典


