美文网首页
彻底理解python3 metaclass

彻底理解python3 metaclass

作者: CalvinKyte | 来源:发表于2020-01-06 13:32 被阅读0次

    A metaclass is a class whose instances are classes. Like an "ordinary" class defines the behavior of instancs of the class,
    a metaclass defines the behavior of classes and their instances.
    metaclass的实例是class, 就像普通的class定义其instance一样.
    There are numberours use cases for metaclasses. Just to name a few:
    * logging and profiling 日志和解析
    * interface checking 接口检查
    * registering classes at creation time
    * automatically adding new methods 自动添加方法
    * automatically propery creation 自动创建属性
    * proxies 代理
    * automatic resource locking/synchronization 自动资源锁和同步

    metaclass内置魔力方法研究(magic method)

    Principially, metaclasses are defined like any other Python class, but they are classes that inherit from type.
    原则上,metaclass定义想普通类一样, 但他们继承自type.

    美化输出函数,主要用于打印函数内部参数和内部变量.以下演示都将用到此函数

    from tabulate import tabulate #自动输出markdown格式表格
    import pandas as pd
    
    
    def pprint(name, **kwargs):
        print('****')
        print('*', '```' + name + '```', 'is called\n')
        df = pd.DataFrame(kwargs.items(), columns=['param', 'value'])
        print(tabulate(df, tablefmt='pipe', headers='keys', showindex=False))
        print('****')
    

    __init____new__

    class LittleMeta(type):
        def __init__(cls, clsname, superclasses, attributedict):
            """定义一个由此元类创建的class时触发此__init__,
            相当于给class增加类属性(class的attribute),就像
            class A:
                age = 18  
            """
            cls.age = 18    
            pprint('__init__', **locals())
        def __new__(cls, clsname, superclasses, attributedict):
            attributedict['name'] = clsname + '$tom'
            pprint('__new__', **locals())
            return type.__new__(cls, clsname, superclasses, attributedict)
    
    # 定义一个继承自str类, 并由LittleMeta创建的类
    # 使用LittleMeta创建类的两种方法
    #方法1
    class A(str, metaclass=LittleMeta):
        pass
    # 方法2
    B = LittleMeta('B', (str,), {})
    

    • 定义A类时 __new__ is called
    param value
    cls <class 'main.LittleMeta'>
    clsname A
    superclasses (<class 'str'>,)
    attributedict {'module': 'main', 'qualname': 'A', 'name': 'A$tom'}

    首先LittleMeta的__new__被调用, 第一个参数是LittleMeta自己,这里的attributedict是A.__dict__, attributedict['name'] = clsname + '$tom'给类A增加
    了一个name属性.


    • 定义class A时元类的 __init__ 被调用
    param value
    cls <class 'main.A'>
    clsname A
    superclasses (<class 'str'>,)
    attributedict {'module': 'main', 'qualname': 'A', 'name': 'A$tom'}

    这里发现attributedict里面没有age, 是因为只有元类的__new__才能直接通过设置A的attributedict的方式给A增加属性,在元类的__init__ 通过cls.attributename = value的方式增加类属性, 也就是说在类A定义之后增加到A.__dict__
    可以通过print(A.__dict__) 观察


    • 函数式调用LittleMeta创建B时__new__ is called
    param value
    cls <class 'main.LittleMeta'>
    clsname B
    superclasses (<class 'str'>,)
    attributedict {'name': 'B$tom'}


    • 函数式调用LittleMeta创建B时__init__ is called
    param value
    cls <class 'main.B'>
    clsname B
    superclasses (<class 'str'>,)
    attributedict {'name': 'B$tom'}

    __call__

    class LittleMeta(type):
        def __init__(cls, clsname, superclasses, attributedict):
            pprint('__init__', **locals())
        def __new__(cls, clsname, superclasses, attributedict):
            attributedict['name'] = clsname + '$tom'
            pprint('__new__', **locals())
            return type.__new__(cls, clsname, superclasses, attributedict)
    
        def __call__(cls, *args, **kwargs):
            """when the instance of LittleMeta is called, in other words,
            class is instantiate, e.g. a = A(),
            this magic method __call__ is called, bind `run` method to cls:A
            当元类的实例被调用, 也就是classA被实例化的时候触发.
            这里当class A被实例化的时候增加一个run方法.联想class的```__call__```使得其 
            实例可以被调用.
            """
            cls.run = lambda self, x: print(f'{self} run {x} meters')
            pprint('__call__', **locals())
            return super().__call__(*args, **kwargs)
    
    class S:
        pass
    
    class A(S, metaclass=LittleMeta):
        pass
    print(A.__dict__)
    a = A() # trigger LittleMeta's __call__, 此时才给A绑定一个run方法
    a.run(88)
    print(A.__dict__)
    

    • __new__ is called
    param value
    cls <class 'main.LittleMeta'>
    clsname A
    superclasses (<class 'main.S'>,)
    attributedict {'module': 'main', 'qualname': 'A', 'name': 'A$tom'}


    • __init__ is called
    param value
    cls <class 'main.A'>
    clsname A
    superclasses (<class 'main.S'>,)
    attributedict {'module': 'main', 'qualname': 'A', 'name': 'A$tom'}

    {'module': 'main', 'name': 'A$tom', 'doc': None}


    • __call__ is called
    param value
    cls <class 'main.A'>
    args ()
    kwargs {}
    class <class 'main.LittleMeta'>

    <main.A object at 0x7f41e4e694e0> run 88 meters
    {'module': 'main', 'name': 'A$tom', 'doc': None, 'run': <function LittleMeta.call.<locals>.<lambda> at 0x7f41d4ef2ae8>}

    此时新增了一个run方法


    Creating instance cache using metaclass(使用元类实现实例缓存)

    class InstCache(type):
        _instances = {}  #元类属性
        def __call__(cls, *args, **kwargs):
            _kw = dict(sorted(kwargs.items()))
            _key = (*args, *_kw.values())
            if _key not in cls._instances:
                cls._instances[_key] = super().__call__(*args, **kwargs)
                #  cls._instances[_key] = type.__call__(cls, *args, **kwargs)
            pprint('__call__', **locals())
            return cls._instances[_key]
    
    class S(metaclass=InstCache):
        def __init__(self, name, age=18):
            self.name = name
            self.age = age
    
    x = S('tom', 19)
    y = S('tom', age=19)
    print(x, y, x is y)
    

    • __call__ is called
    param value
    cls <class 'main.S'>
    args ('tom', 19)
    kwargs {}
    _kw {}
    _key ('tom', 19)
    class <class 'main.InstCache'>

    • __call__ is called
    param value
    cls <class 'main.S'>
    args ('tom',)
    kwargs {'age': 19}
    _kw {'age': 19}
    _key ('tom', 19)
    class <class 'main.InstCache'>

    <main.S object at 0x7fc356f557b8> <main.S object at 0x7fc356f557b8> True

    Creating instance cache using inhrit(继承实现实例缓存)

    class InstCache(object):
        _instances = {}
        def __new__(cls, *args, **kwargs):
            """"实例创建时触发
            """
            _kw = dict(sorted(kwargs.items()))
            _key = (*args, *_kw.values())
            if _key not in cls._instances:
                cls._instances[_key] = super().__new__(cls)
                #  cls._instances[_key] = object.__new__(cls)
            return cls._instances[_key]
    
    class S(InstCache):
        def __init__(self, name, age=18):
            self.name = name
            self.age = age
    
    x = S('tom', 19)
    y = S('tom', age=19)
    print(x is y)
    

    output

    True

    只要实例参数的值一致

    The "count calls" metaclass 类方法调用次数的元类

    from functools import wraps
    
    class FuncCalls(type):
        @staticmethod
        def call_counter(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
                wrapper.calls += 1
                return func(*args, **kwargs)
            wrapper.calls = 0
            return wrapper
    
        def __new__(cls, clsname, superclasses, attributedict):
            for attr, value in attributedict.items():
                if callable(value) and not attr.startswith('__'):
                    value = cls.call_counter(value)  #相当于装饰器
                    attributedict[attr] = value
            return super().__new__(cls, clsname, superclasses, attributedict)
    
    class A(metaclass=FuncCalls):
        def foo(self):
            pass
    
    a = A()
    a.foo()
    print(a.foo.calls)
    a.foo()
    print(a.foo.calls)
    # output
    1
    2
    

    参考:https://www.python-course.eu/python3_metaclasses.php

    相关文章

      网友评论

          本文标题:彻底理解python3 metaclass

          本文链接:https://www.haomeiwen.com/subject/nuqkactx.html