创建了两个类,其中 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:如果这段看起来有点费劲,可以直接跳过看示例):
元类就是类的类,它的实例是类;
type() 并非函数,而是所有类的元类(至于为什么 type 要小写见这里,而 type 和 object 的关系,见“object 与 type”);
type() 既可以用于返回对象类型如:type(int),也可以用于类的创建如 :type(myList,List,{ }),事实上所有类的创建最终都是调用 type() 实现;
类创建时,python 首先会检测该类是否指定了元类,如果没有指定,则会去检测父类是否指定了元类…都没有找到则会检测该模块是否指定了元类,直至找到指定的元类,然后依据元类的定义创建并初始化该类(!!! 注意是创建的是该类,而不是该类的实例),不管元类如何定义,元类最终都会将自身以及其他指定的参数直接或间接的传递给 type() 创建该类 ;如果 python 最终未能找到指定的元类,便会将指定的参数直接传递给 type() 创建该类。
元类作为类的直接创建者,可以在类创建时拦截该类本身的创建程序,并可根据元类的意图创建并返回被修改后的类,因此,所有该类的实例都是被元类修改过的类的实例。这也是元类在应用中最大的价值!
Python 是动态语言,因此类只在该类作用域被创建时才会被创建,而元类则只会在该类被创建时才能做出响应,因此元类不会影响类的实例属性(因为实例属性只有在类实例创建时才会产生),元类也只能修改类的类属性,而不能修改该类的实例属性,但元类对类属性的修改,会影响到该类所有实例的属性,因为所有实例都是被修改过的类的实例。
事实上,被指定元类的类,其内定义的 new 和 init 是毫无意义的,因为该类不仅是通过元类创建的,也是通过元类实现初始化的。
示例:
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?
你这样写,只对类属性有效