oop python小白 - 什麼是Python中的元類?




metaclass教學 python教程下载 (13)

元類的一個用途是自動向實例添加新屬性和方法。

例如,如果你看看Django模型 ,他們的定義看起來有點令人困惑。 看起來好像只是定義了類屬性:

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

但是,在運行時,Person對象充滿了各種有用的方法。 查看source了解一些神奇的元素。

什麼是元類,我們用它們做什麼?


tl;博士版

type(obj)函數可以獲取對象的類型。

type()一類是它的元類

要使用元類:

class Foo(object):
    __metaclass__ = MyMetaClass

type()函數可以返回對象的類型或創建新類型,

例如,我們可以使用type()函數創建一個Hi類,而不需要使用類Hi(object):

def func(self, name='mike'):
    print('Hi, %s.' % name)

Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.

type(Hi)
type

type(h)
__main__.Hi

除了使用type()動態創建類之外,您還可以控制類的創建行為並使用元類。

根據Python對像模型,類是對象,因此該類必須是另一個特定類的實例。默認情況下,Python類是類型類的實例。也就是說,type是大多數內置類的元類和用戶定義類的元類。

class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class CustomList(list, metaclass=ListMetaclass):
    pass

lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')

lst
['custom_list_1', 'custom_list_2']

當我們在元類中傳遞關鍵字參數時,Magic將生效,它表示Python解釋器通過ListMetaclass創建CustomList。new(),此時,我們可以修改類定義,例如,添加一個新方法然後返回修改後的定義。


Python類本身就是它們的元類的對象 - 例如 - 。

默認元類,在您將類確定為時應用:

class foo:
    ...

元類用於將一些規則應用於整個類集。例如,假設您正在構建一個ORM來訪問數據庫,並且您希望每個表中的記錄都是映射到該表的類(基於字段,業務規則等),可能使用元類例如,連接池邏輯,它由所有表的所有記錄類共享。另一個用途是支持外鍵的邏輯,它涉及多類記錄。

當你定義元類時,你是子類類型,並且可以覆蓋以下魔術方法來插入你的邏輯。

class somemeta(type):
    __new__(mcs, name, bases, clsdict):
      """
  mcs: is the base metaclass, in this case type.
  name: name of the new class, as provided by the user.
  bases: tuple of base classes 
  clsdict: a dictionary containing all methods and attributes defined on class

  you must return a class object by invoking the __new__ constructor on the base metaclass. 
 ie: 
    return type.__call__(mcs, name, bases, clsdict).

  in the following case:

  class foo(baseclass):
        __metaclass__ = somemeta

  an_attr = 12

  def bar(self):
      ...

  @classmethod
  def foo(cls):
      ...

      arguments would be : ( somemeta, "foo", (baseclass, baseofbase,..., object), {"an_attr":12, "bar": <function>, "foo": <bound class method>}

      you can modify any of these values before passing on to type
      """
      return type.__call__(mcs, name, bases, clsdict)


    def __init__(self, name, bases, clsdict):
      """ 
      called after type has been created. unlike in standard classes, __init__ method cannot modify the instance (cls) - and should be used for class validaton.
      """
      pass


    def __prepare__():
        """
        returns a dict or something that can be used as a namespace.
        the type will then attach methods and attributes from class definition to it.

        call order :

        somemeta.__new__ ->  type.__new__ -> type.__init__ -> somemeta.__init__ 
        """
        return dict()

    def mymethod(cls):
        """ works like a classmethod, but for class objects. Also, my method will not be visible to instances of cls.
        """
        pass

無論如何,這兩個是最常用的鉤子。元類化是強大的,並且上面是遠程接近和詳盡的元類別用途列表。


除了已發布的答案,我可以說a metaclass定義了一個類的行為。因此,您可以明確設置您的元類。每當Python獲得一個關鍵字,class它就會開始搜索metaclass。如果找不到 - 默認的元類類型用於創建類的對象。使用該__metaclass__屬性,您可以設置metaclass您的類:

class MyClass:
   __metaclass__ = type
   # write here other method
   # write here one more method

print(MyClass.__metaclass__)

它將產生如下輸出:

class 'type'

當然,您可以創建自己的類metaclass來定義使用您的類創建的任何類的行為。

為此,metaclass必須繼承您的默認類型類,因為這是主要的metaclass

class MyMetaClass(type):
   __metaclass__ = type
   # you can write here any behaviour you want

class MyTestClass:
   __metaclass__ = MyMetaClass

Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)

輸出將是:

class '__main__.MyMetaClass'
class 'type'

__call__()創建類實例時元類' 方法的作用

如果你已經完成Python編程超過幾個月,你最終會偶然發現如下所示的代碼:

# define a class
class SomeClass(object):
    # ...
    # some definition here ...
    # ...

# create an instance of it
instance = SomeClass()

# then call the object as if it's a function
result = instance('foo', 'bar')

當你__call__()在類上實現魔術方法時,後者是可能的。

class SomeClass(object):
    # ...
    # some definition here ...
    # ...

    def __call__(self, foo, bar):
        return bar + foo

__call__()當類的實例用作可調用對象時,將調用該方法。但正如我們從前面的答案中看到的,類本身是元類的一個實例,所以當我們將類用作可調用的時(即當我們創建它的實例時),我們實際上正在調用它的元類' __call__()方法。在這一點上,大多數Python程序員都有點困惑,因為他們被告知在創建這樣的實例時,instance = SomeClass()你正在調用它的__init__()方法。有些誰已經挖一個深一點知道,之前__init__()__new__()。好吧,今天還有另一層真相被揭示出來,然後__new__()才有了元類' __call__()

讓我們從創建類實例的角度研究方法調用鏈。

這是一個元類,它精確記錄實例創建之前的時刻以及它將要返回的時刻。

class Meta_1(type):
    def __call__(cls):
        print "Meta_1.__call__() before creating an instance of ", cls
        instance = super(Meta_1, cls).__call__()
        print "Meta_1.__call__() about to return instance."
        return instance

這是一個使用該元類的類

class Class_1(object):

    __metaclass__ = Meta_1

    def __new__(cls):
        print "Class_1.__new__() before creating an instance."
        instance = super(Class_1, cls).__new__(cls)
        print "Class_1.__new__() about to return instance."
        return instance

    def __init__(self):
        print "entering Class_1.__init__() for instance initialization."
        super(Class_1,self).__init__()
        print "exiting Class_1.__init__()."

現在讓我們創建一個實例 Class_1

instance = Class_1()
# Meta_1.__call__() before creating an instance of <class '__main__.Class_1'>.
# Class_1.__new__() before creating an instance.
# Class_1.__new__() about to return instance.
# entering Class_1.__init__() for instance initialization.
# exiting Class_1.__init__().
# Meta_1.__call__() about to return instance.

觀察上面的代碼實際上並沒有做任何事情,只記錄任務。每個方法都將實際工作委託給其父實現,從而保持默認行為。由於typeMeta_1父類(type作為默認父元類)並考慮上面輸出的排序順序,我們現在有一個線索,關於什麼是偽實現type.__call__()

class type:
    def __call__(cls, *args, **kwarg):

        # ... maybe a few things done to cls here

        # then we call __new__() on the class to create an instance
        instance = cls.__new__(cls, *args, **kwargs)

        # ... maybe a few things done to the instance here

        # then we initialize the instance with its __init__() method
        instance.__init__(*args, **kwargs)

        # ... maybe a few more things done to instance here

        # then we return it
        return instance

我們可以看到元類的__call__()方法是第一個被調用的方法。然後它將實例的創建委託給類的__new__()方法並初始化為實例__init__()。它也是最終返回實例的那個。

從上面可以看出,元類' __call__()也有機會決定是否打電話Class_1.__new__()Class_1.__init__()最終打電話。在執行過程中,它實際上可以返回一個未被這些方法觸及的對象。以單例模式的這種方法為例:

class Meta_2(type):
    singletons = {}

    def __call__(cls, *args, **kwargs):
        if cls in Meta_2.singletons:
            # we return the only instance and skip a call to __new__()
            # and __init__()
            print ("{} singleton returning from Meta_2.__call__(), "
                   "skipping creation of new instance.".format(cls))
            return Meta_2.singletons[cls]

        # else if the singleton isn't present we proceed as usual
        print "Meta_2.__call__() before creating an instance."
        instance = super(Meta_2, cls).__call__(*args, **kwargs)
        Meta_2.singletons[cls] = instance
        print "Meta_2.__call__() returning new instance."
        return instance

class Class_2(object):

    __metaclass__ = Meta_2

    def __new__(cls, *args, **kwargs):
        print "Class_2.__new__() before creating instance."
        instance = super(Class_2, cls).__new__(cls)
        print "Class_2.__new__() returning instance."
        return instance

    def __init__(self, *args, **kwargs):
        print "entering Class_2.__init__() for initialization."
        super(Class_2, self).__init__()
        print "exiting Class_2.__init__()."

讓我們觀察一下重複嘗試創建類型對象時會發生什麼 Class_2

a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.

b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.

a is b is c # True

Python 3更新

(在這一點上)元類中有兩個關鍵方法:

  • __prepare__ ,和
  • __new__

__prepare__允許您提供OrderedDict在創建類時用作命名空間的自定義映射(例如)。您必須返回您選擇的任何名稱空間的實例。如果沒有實現__prepare__正常dict使用。

__new__ 負責最終課程的實際創建/修改。

一個簡單的,無所事事的額外元類想要:

class Meta(type):

    def __prepare__(metaclass, cls, bases):
        return dict()

    def __new__(metacls, cls, bases, clsdict):
        return super().__new__(metacls, cls, bases, clsdict)

一個簡單的例子:

假設您希望在您的屬性上運行一些簡單的驗證代碼 - 例如它必須始終為a int或a str。沒有元類,您的類看起來像:

class Person:
    weight = ValidateType('weight', int)
    age = ValidateType('age', int)
    name = ValidateType('name', str)

如您所見,您必須重複兩次屬性的名稱。這使得拼寫錯誤以及惱人的錯誤成為可能。

一個簡單的元類可以解決這個問題:

class Person(metaclass=Validator):
    weight = ValidateType(int)
    age = ValidateType(int)
    name = ValidateType(str)

這就是元類的外觀(不使用,__prepare__因為它不需要):

class Validator(type):
    def __new__(metacls, cls, bases, clsdict):
        # search clsdict looking for ValidateType descriptors
        for name, attr in clsdict.items():
            if isinstance(attr, ValidateType):
                attr.name = name
                attr.attr = '_' + name
        # create final class and return it
        return super().__new__(metacls, cls, bases, clsdict)

示例運行:

p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'

生產:

9
Traceback (most recent call last):
  File "simple_meta.py", line 36, in <module>
    p.weight = '9'
  File "simple_meta.py", line 24, in __set__
    (self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')

注意:這個例子很簡單,它也可以用類裝飾器完成,但可能是一個實際的元類會做得更多。

'ValidateType'類供參考:

class ValidateType:
    def __init__(self, type):
        self.name = None  # will be set by metaclass
        self.attr = None  # will be set by metaclass
        self.type = type
    def __get__(self, inst, cls):
        if inst is None:
            return self
        else:
            return inst.__dict__[self.attr]
    def __set__(self, inst, value):
        if not isinstance(value, self.type):
            raise TypeError('%s must be of type(s) %s (got %r)' %
                    (self.name, self.type, value))
        else:
            inst.__dict__[self.attr] = value

type實際上是一個metaclass創建另一個類的類。大多數metaclass是。的子類type。所述metaclass接收new類作為其第一個參數,如下面所提到提供訪問與細節類對象:

>>> class MetaClass(type):
...     def __init__(cls, name, bases, attrs):
...         print ('class name: %s' %name )
...         print ('Defining class %s' %cls)
...         print('Bases %s: ' %bases)
...         print('Attributes')
...         for (name, value) in attrs.items():
...             print ('%s :%r' %(name, value))
... 

>>> class NewClass(object, metaclass=MetaClass):
...    get_choch='dairy'
... 
class name: NewClass
Bases <class 'object'>: 
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'

Note:

請注意,該類在任何時候都沒有實例化; 創建類的簡單行為觸發了執行metaclass


元類是一個類,它告訴我們應該如何(某些)創建其他類。

這是我看到元類作為我的問題的解決方案的情況:我有一個非常複雜的問題,可能已經以不同的方式解決了,但我選擇使用元類來解決它。由於其複雜性,它是我編寫的少數幾個模塊之一,其中模塊中的註釋超過了已編寫的代碼量。這裡是...

#!/usr/bin/env python

# Copyright (C) 2013-2014 Craig Phillips.  All rights reserved.

# This requires some explaining.  The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried.  I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to.  See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType.  This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient.  The complicated bit
# comes from requiring the GsyncOptions class to be static.  By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace.  Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet.  The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method.  This is the first and only time the class will actually have its
# dictionary statically populated.  The docopt module is invoked to parse the
# usage document and generate command line options from it.  These are then
# paired with their defaults and what's in sys.argv.  After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored.  This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times.  The __getattr__ call hides this by default, returning the
# last item in a property's list.  However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
    class GsyncListOptions(object):
        __initialised = False

    class GsyncOptionsType(type):
        def __initialiseClass(cls):
            if GsyncListOptions._GsyncListOptions__initialised: return

            from docopt import docopt
            from libgsync.options import doc
            from libgsync import __version__

            options = docopt(
                doc.__doc__ % __version__,
                version = __version__,
                options_first = True
            )

            paths = options.pop('<path>', None)
            setattr(cls, "destination_path", paths.pop() if paths else None)
            setattr(cls, "source_paths", paths)
            setattr(cls, "options", options)

            for k, v in options.iteritems():
                setattr(cls, k, v)

            GsyncListOptions._GsyncListOptions__initialised = True

        def list(cls):
            return GsyncListOptions

        def __getattr__(cls, name):
            cls.__initialiseClass()
            return getattr(GsyncListOptions, name)[-1]

        def __setattr__(cls, name, value):
            # Substitut option names: --an-option-name for an_option_name
            import re
            name = re.sub(r'^__', "", re.sub(r'-', "_", name))
            listvalue = []

            # Ensure value is converted to a list type for GsyncListOptions
            if isinstance(value, list):
                if value:
                    listvalue = [] + value
                else:
                    listvalue = [ None ]
            else:
                listvalue = [ value ]

            type.__setattr__(GsyncListOptions, name, listvalue)

    # Cleanup this module to prevent tinkering.
    import sys
    module = sys.modules[__name__]
    del module.__dict__['GetGsyncOptionsType']

    return GsyncOptionsType

# Our singlton abstract proxy class.
class GsyncOptions(object):
    __metaclass__ = GetGsyncOptionsType()

注意,這個答案適用於Python 2.x,因為它是在2008年編寫的,元類在3.x中略有不同,請參閱註釋。

元類是使“階級”工作的秘訣。 新樣式對象的默認元類稱為“類型”。

class type(object)
  |  type(object) -> the object's type
  |  type(name, bases, dict) -> a new type

元類需要3個參數。 ' name ',' bases '和' dict '

這是秘密開始的地方。 在此示例類定義中查找name,bases和dict的來源。

class ThisIsTheName(Bases, Are, Here):
    All_the_code_here
    def doesIs(create, a):
        dict

讓我們定義一個元類,它將演示' class: '如何調用它。

def test_metaclass(name, bases, dict):
    print 'The Class Name is', name
    print 'The Class Bases are', bases
    print 'The dict has', len(dict), 'elems, the keys are', dict.keys()

    return "yellow"

class TestName(object, None, int, 1):
    __metaclass__ = test_metaclass
    foo = 1
    def baz(self, arr):
        pass

print 'TestName = ', repr(TestName)

# output => 
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName =  'yellow'

現在,一個實際意味著什麼的例子,這將自動使列表中的變量“屬性”設置在類上,並設置為None。

def init_attributes(name, bases, dict):
    if 'attributes' in dict:
        for attr in dict['attributes']:
            dict[attr] = None

    return type(name, bases, dict)

class Initialised(object):
    __metaclass__ = init_attributes
    attributes = ['foo', 'bar', 'baz']

print 'foo =>', Initialised.foo
# output=>
foo => None

請注意,通過使用元類init_attributes獲得的“初始化”的神奇行為不會傳遞給Initalised的子類。

這是一個更具體的例子,展示瞭如何子類化'type'來創建一個在創建類時執行操作的元類。 這非常棘手:

class MetaSingleton(type):
    instance = None
    def __call__(cls, *args, **kw):
        if cls.instance is None:
            cls.instance = super(MetaSingleton, cls).__call__(*args, **kw)
        return cls.instance

 class Foo(object):
     __metaclass__ = MetaSingleton

 a = Foo()
 b = Foo()
 assert a is b

元類是類的類。 就像類定義了類的實例的行為一樣,元類定義了類的行為方式。 類是元類的實例。

雖然在Python中你可以為元類使用任意的callables(比如Jerub節目),實際上更有用的方法是使它成為一個真正的類本身。 type是Python中常用的元類。 如果您想知道,是的, type本身就是一個類,它是它自己的類型。 你將無法在Python中重新創建純type東西,但是Python會有所作為。 要在Python中創建自己的元類,你真的只想子type

元類最常用作類工廠。 就像你通過調用類來創建類的實例一樣,Python通過調用元類來創建一個新類(當它執行'class'語句時)。 結合正常的__init____new__方法,元類因此允許您在創建類時執行“額外的事情”,例如使用某個註冊表註冊新類,或者甚至完全用其他類替換該類。

執行class語句時,Python首先執行class語句的主體作為正常的代碼塊。 生成的命名空間(dict)保存了將要進行的類的屬性。 元類是通過在__metaclass__類(如果有)的__metaclass__屬性或__metaclass__全局變量中查看要被定義的類的基類(繼承元類)來確定的。 然後使用類的名稱,基數和屬性調用元類來實例化它。

但是,元類實際上定義了類的類型 ,而不僅僅是它的工廠,所以你可以用它們做更多的事情。 例如,您可以在元類上定義常規方法。 這些元類方法類似於類方法,因為它們可以在沒有實例的類上調用,但它們也不像類方法,因為它們不能在類的實例上調用。 type.__subclasses__()type元類的方法示例。 您還可以定義常規的“魔術”方法,如__add__ __iter____getattr__ __add____getattr__ ,以實現或更改類的行為方式。

這是比特和碎片的匯總示例:

def make_hook(f):
    """Decorator to turn 'foo' method into '__foo__'"""
    f.is_hook = 1
    return f

class MyType(type):
    def __new__(mcls, name, bases, attrs):

        if name.startswith('None'):
            return None

        # Go over attributes and see if they should be renamed.
        newattrs = {}
        for attrname, attrvalue in attrs.iteritems():
            if getattr(attrvalue, 'is_hook', 0):
                newattrs['__%s__' % attrname] = attrvalue
            else:
                newattrs[attrname] = attrvalue

        return super(MyType, mcls).__new__(mcls, name, bases, newattrs)

    def __init__(self, name, bases, attrs):
        super(MyType, self).__init__(name, bases, attrs)

        # classregistry.register(self, self.interfaces)
        print "Would register class %s now." % self

    def __add__(self, other):
        class AutoClass(self, other):
            pass
        return AutoClass
        # Alternatively, to autogenerate the classname as well as the class:
        # return type(self.__name__ + other.__name__, (self, other), {})

    def unregister(self):
        # classregistry.unregister(self)
        print "Would unregister class %s now." % self

class MyObject:
    __metaclass__ = MyType


class NoneSample(MyObject):
    pass

# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)

class Example(MyObject):
    def __init__(self, value):
        self.value = value
    @make_hook
    def add(self, other):
        return self.__class__(self.value + other.value)

# Will unregister the class
Example.unregister()

inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()

print inst + inst
class Sibling(MyObject):
    pass

ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__

其他人已經解釋了元類如何工作以及它們如何適合Python類型系統。 以下是它們可用於什麼的示例。 在我編寫的測試框架中,我想跟踪定義類的順序,以便稍後我可以按此順序實例化它們。 我發現使用元類這樣做最容易。

class MyMeta(type):

    counter = 0

    def __init__(cls, name, bases, dic):
        type.__init__(cls, name, bases, dic)
        cls._order = MyMeta.counter
        MyMeta.counter += 1

class MyType(object):              # Python 2
    __metaclass__ = MyMeta

class MyType(metaclass=MyMeta):    # Python 3
    pass

然後,任何屬於MyType子類的東西都會獲得一個類屬性_order ,它記錄了類的定義順序。


在本頁的答案中,我一直在閱讀這麼多內容。 我會說,如果你知道這件事,肯定會理解那些答案,否則,你仍然感到困惑。

簡而言之,您需要了解以下幾點:

  1. import a動作實際運行所有可以在“a”中運行import a動作

  2. 由於第1點,您可能不希望在導入時在“a”中運行所有內容

  3. 要解決第2點中的問題,python允許您進行條件檢查

  4. __name__是所有.py模塊中的隱式變量; 導入__name__ ,a.py模塊的__name__值設置為文件名“a”; 當a.py直接使用“python a.py”運行時,這意味著a.py是入口點,那麼a.py模塊的__name__值設置為字符串__main__

  5. 根據python如何__name__為每個模塊設置變量的機制,你知道如何實現第3點嗎?答案很簡單,對吧?放一個if條件:if __name__ == "__main__": ...; 你可以__name__ == "a"根據你的功能需要放置

python特別重要的是第4點!其餘的只是基本邏輯。







python oop metaclass python-datamodel