首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python 学习手册
Python Cookbook
Python 基础教程
Python Sites
PyPI - Python Package Index
http://www.simple-is-better.com/
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
V2EX  ›  Python

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

  •  
  •   waibunleung · 3 天前 · 833 次点击
    # -*- 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__()方法中却可以?

    18 回复  |  直到 2019-07-15 11:26:28 +08:00
        1
    Trim21   3 天前 via Android
    元类是用来操作类的,所以 myclass 不实例化也可以访问到 age 属性,在这里 age 和 gendar 都是类属性而不是实例属性

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

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

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

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

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

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

    对 Myclass 增加属性, 要么是在类创建之前, 对参数修改, 然后被 type.__new__调用. 要么在 type.__init__里面, 类已经创建, 再对 cls 赋值属性
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1057 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 17ms · UTC 18:15 · PVG 02:15 · LAX 11:15 · JFK 14:15
    ♥ Do have faith in what you're doing.