首页 > python 使用元类修改类的创建

python 使用元类修改类的创建

创建了两个类,其中 Human 是 Person 的元类,想要在实例化 Person 时,通过元类 Human 将 name 和 age 属性改为 NAME、AGE,但是没有成功,不知道哪里出错了,请大神看看:

class Human(type):
    def __new__(cls, name, bases, dct):
        attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
        uppercase_attr = dict((name.upper(), value) for name, value in attrs)
        return super(Human, cls).__new__(cls, name, bases, uppercase_attr)


class Person():
    __metaclass__ = Human
    country = ''
    """Person class."""
    def __init__(self, name, age):
        self.name = name
        self.age = age
        def intro(self):
            """Return an introduction."""
            
            return "Hello, my name is %s and I'm %s." % (self.name, self.age)

运行如下:

In [23]: a = Person('dobi', 2)

In [24]: a.__dict__
Out[24]: {'age': 2, 'name': 'dobi'}

In [25]: Person.__dict__
Out[25]: mappingproxy({'country': 'gg', '__init__': <function Person.__init__ at 0x0000025E52BD9C80>, '__module__': '__main__', '__doc__': None, '__metaclass__': <class '__main__.Human'>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>})

总结一下(ps:如果这段看起来有点费劲,可以直接跳过看示例):

  1. 元类就是类的类,它的实例是类;

  2. type() 并非函数,而是所有类的元类(至于为什么 type 要小写见这里,而 type 和 object 的关系,见“object 与 type”);

  3. type() 既可以用于返回对象类型如:type(int),也可以用于类的创建如 :type(myList,List,{ }),事实上所有类的创建最终都是调用 type() 实现;

  4. 类创建时,python 首先会检测该类是否指定了元类,如果没有指定,则会去检测父类是否指定了元类…都没有找到则会检测该模块是否指定了元类,直至找到指定的元类,然后依据元类的定义创建并初始化该类(!!! 注意是创建的是该类,而不是该类的实例),不管元类如何定义,元类最终都会将自身以及其他指定的参数直接或间接的传递给 type() 创建该类 ;如果 python 最终未能找到指定的元类,便会将指定的参数直接传递给 type() 创建该类。

  5. 元类作为类的直接创建者,可以在类创建时拦截该类本身的创建程序,并可根据元类的意图创建并返回被修改后的类,因此,所有该类的实例都是被元类修改过的类的实例。这也是元类在应用中最大的价值!

  6. Python 是动态语言,因此类只在该类作用域被创建时才会被创建,而元类则只会在该类被创建时才能做出响应,因此元类不会影响类的实例属性(因为实例属性只有在类实例创建时才会产生),元类也只能修改类的类属性,而不能修改该类的实例属性,但元类对类属性的修改,会影响到该类所有实例的属性,因为所有实例都是被修改过的类的实例。

  7. 事实上,被指定元类的类,其内定义的 newinit 是毫无意义的,因为该类不仅是通过元类创建的,也是通过元类实现初始化的。

示例:

    class Human(type):
        def __new__(cls, name, bases, dct):
            attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
            uppercase_attr = dict((name.upper(), value) for name, value in attrs)
            return super(Human, cls).__new__(cls, name, bases, uppercase_attr)
    
    class Person(metaclass = Human):
        country = ''
        """Person class."""
        def __init__(self, name, age):
            self.name = name
            self.age = age
            
        def intro(self): #这里的 self 不要忘记,否则类实例无法调用
            """Return an introduction."""
            print('who am I?')

上例中,Person 指定 Human 为其元类,一旦 Person 所在的作用域被执行,Human 会将创建 Person 类,并将 Person 类中所有非"__"开头的类属性标识符全部变为大写,并返回被修改后的 Person,当我们运行这段代码后:

runfile('C:/Users/xu_zh/.spyder2-py3/temp.py', wdir='C:/Users/...')

Person.country
Traceback (most recent call last):

  File "<ipython-input-63-fc010f0a9d88>", line 1, in <module>
    Person.country

AttributeError: type object 'Person' has no attribute 'country'


Person.COUNTRY
Out[64]: ''

Person.INTRO
Out[65]: <function __main__.Person.intro>

Person 类中定义的 country 属性根本不存在,只存在被元类修改过的 COUNTRY,INTRO 方法也是如此,另外,我们再调用 init 方法看看:

Person.__init__
Out[66]: <slot wrapper '__init__' of 'object' objects>

可以发现完成 Person 类初始化的是 object 的 init 而非 Person 本身的 init,原因在于 Person 类是通过元类 Human 创建的,而 Human 并未定义 init 而是默认继承 object 的 init,因此 Person 类的创建就由 object 的 init 完成,如果此时我们按照 Person 本身的定义实例化一个 Person 对象,就会出现:

dobi = Person('dobi', 2)
Traceback (most recent call last):

  File "<ipython-input-67-6bb1c8d0ad4d>", line 1, in <module>
    dobi = Person('dobi', 2)

TypeError: object() takes no parameters

类型错误提示:object 对象无参数….
这样印证了 Person 类初始化程序是调用 object 对象的初始化程序实现的,那么 Person 类中定义的 name 和 age 等实例属性就是毫无意义的,它根本不可能存在于实例中:

dir(dobi)
Out[68]: 
['COUNTRY',
 'INTRO',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']


dobi.INTRO()
Out[69]: who am I?

你这样写,只对类属性有效

【热门文章】
【热门文章】