MetaClass元类,本质也是一个类,但和普通类的用法不同,它可以对类内部的定义(包括类属性和类方法)进行动态的修改。可以这么说,使用元类的主要目的就是为了实现在创建类时,能够动态地改变类中定义的属性或者方法。
一、type() 函数还有一个更高级的用法,即创建一个自定义类型(也就是创建一个类)。
type() 函数的语法格式有 2 种,分别如下:
type(obj)
type(name, bases, dict)
class type(name, bases, dict)
使用1个参数,返回对象的类型。就像object.__class__。内置函数isinstance()被用来测试对象的类型,因为他会考虑到子类。
用3个参数,返回一个新类型对象。本质上,这是类声明的一种动态形式。
参数name是一个字符串,表示类名称,并记录为__name__属性;
参数bases是一个元组,一个个记下基础类,并记录为__bases__属性,
参数dict是一个字典,包含类本体的命名空间并被赋值到标准字典。并记录为__dict__属性。
示例:
#定义一个实例方法
def say(self):
print("这是 Python!")
#使用 type() 函数创建类
CLanguage = type("CLanguage",(object,),dict(say = say, name = "python语言"))
#创建一个 CLanguage 实例对象
clangs = CLanguage()
#调用 say() 方法和 name 属性
clangs.say()
print(clangs.name)
二、MetaClass元类,本质也是一个类,但是它可以动态的定制或修改继承它的子类。
metaclass 是 type 的子类,通过替换 type 的 __call__ 运算符重载机制
用户自定义类,只不过是 type 类的 __call__ 运算符重载
一个类设计成 MetaClass 元类,其必须符合以下条件:
必须显式继承自 type 类;
类中需要定义并实现 __new__() 方法,该方法一定要返回该类的一个实例对象,因为在使用元类创建类时,该 __new__() 方法会自动被执行,用来修改新建的类
#定义一个元类
class FirstMetaClass(type):
# cls代表动态修改的类
# name代表动态修改的类名
# bases代表被动态修改的类的所有父类
# attr代表被动态修改的类的所有属性、方法组成的字典
def __new__(cls, name, bases, attrs):
# 动态为该类添加一个name属性
attrs['name'] = "python语言"
attrs['say'] = lambda self: print("调用 say() 实例方法")
return super().__new__(cls,name,bases,attrs)
#定义类时,指定元类
class CLanguage(object,metaclass=FirstMetaClass):
pass
clangs = CLanguage()
print(clangs.name)
clangs.say()
用方法来创建元类
def upper_attr(future_class_name, future_class_parents, future_class_attr):
'''返回一个类对象,将属性都转为大写形式'''
# 选择所有不以'__'开头的属性
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
# 将它们转为大写形式
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# 通过'type'来做类对象的创建
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # 这会作用到这个模块中的所有类
class Foo(object):
# 我们也可以只在这里定义__metaclass__,这样就只会作用于这个类中
bar = 'bip'
print(hasattr(Foo, 'bar'))
# 输出: False
print(hasattr(Foo, 'BAR'))
# 输出:True
f = Foo()
print(f.BAR)、
元类定义了__prepare__以后,会最先执行__prepare__方法,返回一个空的定制的字典,然后再执行类的语句,类中定义的各种属性被收集入定制的字典,最后传给new和init方法
3.6版本以前,__prepare__方法主要用来返回一个orderdict对象,以保存类中属性的添加顺序。而3.6版本以后,默认已经是保持顺序的了。
class member_table(dict):
def __init__(self):
self.member_names = []
def __setitem__(self, key, value):
if key not in self:
self.member_names.append(key)
dict.__setitem__(self, key, value)
class OrderedClass(type):
@classmethod
def __prepare__(metacls, name, bases):
classdict = member_table()
print("prepare return dict id is:", id(classdict))
return classdict
def __new__(metacls, name, bases, classdict):
print("new get dict id is:", id(classdict))
result = type.__new__(metacls, name, bases, dict(classdict))
result.member_names = classdict.member_names
print("the class's __dict__ id is:", id(result.__dict__))
return result
def __init__(cls, name, bases, classdict):
print("init get dict id is ", id(classdict))
super().__init__(name, bases, classdict)
class MyClass(metaclass=OrderedClass):
def method1(self):
pass
def method2(self):
pass
print("MyClass locals() id is ", id(locals()))
在python中,类的__new__、__init__、__call__等方法不是必须写的,会默认调用,如果自己定义了,就是override,可以custom。既然override了,通常也会显式调用进行补偿以达到extend的目的。
__call__ : 对象可call,注意不是类,是对象。
如果元类中定义了__call__,此方法必须返回一个对象,否则类的实例化就不会起作用。(实例化得到的结果为__call__的返回值)
三、类的__slots__ 属性只能限制为实例对象动态添加属性和方法,而无法限制动态地为类添加属性和方法。
__slots__ 属性值其实就是一个元组,只有其中指定的元素,才可以作为动态添加的属性或者方法的名称。举个例子
class CLanguage:
__slots__ = ('name','add','info')
这意味着,该类的实例对象仅限于动态添加 name、add、info 这 3 个属性以及 name()、add() 和 info() 这 3 个方法。
注意,对于动态添加的方法,__slots__ 限制的是其方法名,并不限制参数的个数。
__slots__ 属性对由该类派生出来的子类,也是不起作用的,因为_slots__ 属性限制的对象是类的实例对象,而不是类