徹底理解python3 metaclass

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的實(shí)例是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 自動(dòng)添加方法
* automatically propery creation 自動(dòng)創(chuàng)建屬性
* proxies 代理
* automatic resource locking/synchronization 自動(dòng)資源鎖和同步

metaclass內(nèi)置魔力方法研究(magic method)

Principially, metaclasses are defined like any other Python class, but they are classes that inherit from type.
原則上,metaclass定義想普通類一樣, 但他們繼承自type.

美化輸出函數(shù),主要用于打印函數(shù)內(nèi)部參數(shù)和內(nèi)部變量.以下演示都將用到此函數(shù)

from tabulate import tabulate #自動(dòng)輸出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):
        """定義一個(gè)由此元類創(chuàng)建的class時(shí)觸發(fā)此__init__,
        相當(dāng)于給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)

# 定義一個(gè)繼承自str類, 并由LittleMeta創(chuàng)建的類
# 使用LittleMeta創(chuàng)建類的兩種方法
#方法1
class A(str, metaclass=LittleMeta):
    pass
# 方法2
B = LittleMeta('B', (str,), {})

  • 定義A類時(shí) __new__ is called
param value
cls <class 'main.LittleMeta'>
clsname A
superclasses (<class 'str'>,)
attributedict {'module': 'main', 'qualname': 'A', 'name': 'A$tom'}

首先LittleMeta的__new__被調(diào)用, 第一個(gè)參數(shù)是LittleMeta自己,這里的attributedict是A.__dict__, attributedict['name'] = clsname + '$tom'給類A增加
了一個(gè)name屬性.


  • 定義class A時(shí)元類的 __init__ 被調(diào)用
param value
cls <class 'main.A'>
clsname A
superclasses (<class 'str'>,)
attributedict {'module': 'main', 'qualname': 'A', 'name': 'A$tom'}

這里發(fā)現(xiàn)attributedict里面沒有age, 是因?yàn)橹挥性惖?code>__new__才能直接通過設(shè)置A的attributedict的方式給A增加屬性,在元類的__init__ 通過cls.attributename = value的方式增加類屬性, 也就是說在類A定義之后增加到A.__dict__
可以通過print(A.__dict__) 觀察


  • 函數(shù)式調(diào)用LittleMeta創(chuàng)建B時(shí)__new__ is called
param value
cls <class 'main.LittleMeta'>
clsname B
superclasses (<class 'str'>,)
attributedict {'name': 'B$tom'}


  • 函數(shù)式調(diào)用LittleMeta創(chuàng)建B時(shí)__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
        當(dāng)元類的實(shí)例被調(diào)用, 也就是classA被實(shí)例化的時(shí)候觸發(fā).
        這里當(dāng)class A被實(shí)例化的時(shí)候增加一個(gè)run方法.聯(lián)想class的```__call__```使得其 
        實(shí)例可以被調(diào)用.
        """
        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__, 此時(shí)才給A綁定一個(gè)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>}

此時(shí)新增了一個(gè)run方法


Creating instance cache using metaclass(使用元類實(shí)現(xiàn)實(shí)例緩存)

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(繼承實(shí)現(xiàn)實(shí)例緩存)

class InstCache(object):
    _instances = {}
    def __new__(cls, *args, **kwargs):
        """"實(shí)例創(chuàng)建時(shí)觸發(fā)
        """
        _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

只要實(shí)例參數(shù)的值一致

The "count calls" metaclass 類方法調(diào)用次數(shù)的元類

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)  #相當(dāng)于裝飾器
                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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容