Benjamin

静以修身,俭以养德,非澹薄无以明志,非宁静无以致远。
随笔 - 279, 文章 - 0, 评论 - 196, 引用 - 0
数据加载中……

python中的MetaClass(元类)

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__ 属性限制的对象是类的实例对象,而不是类

posted on 2020-06-28 14:40 Benjamin 阅读(43) 评论(0)  编辑 收藏 引用 所属分类: python


只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理