Siêu dữ liệu trong Python là gì?


Câu trả lời:


2869

Một metaclass là lớp của một lớp. Một lớp định nghĩa cách một thể hiện của lớp (tức là một đối tượng) hành xử trong khi siêu dữ liệu định nghĩa cách một lớp hành xử. Một lớp là một thể hiện của siêu dữ liệu.

Mặc dù trong Python, bạn có thể sử dụng các hàm gọi tùy ý cho các siêu dữ liệu (như các chương trình Jerub ), cách tiếp cận tốt hơn là biến nó thành một lớp thực sự. typelà siêu dữ liệu thông thường trong Python. typebản thân nó là một lớp và nó là kiểu riêng của nó Bạn sẽ không thể tạo lại một cái gì đó giống như typehoàn toàn trong Python, nhưng Python gian lận một chút. Để tạo siêu dữ liệu của riêng bạn trong Python, bạn thực sự chỉ muốn phân lớp type.

Một metaclass được sử dụng phổ biến nhất như là một nhà máy đẳng cấp. Khi bạn tạo một đối tượng bằng cách gọi lớp, Python sẽ tạo một lớp mới (khi nó thực thi câu lệnh 'class') bằng cách gọi siêu dữ liệu. Do đó, kết hợp với các phương thức __init____new__phương thức thông thường , siêu dữ liệu cho phép bạn thực hiện 'những điều bổ sung' khi tạo một lớp, như đăng ký lớp mới bằng một số đăng ký hoặc thay thế hoàn toàn lớp bằng một thứ khác.

Khi classcâu lệnh được thực thi, trước tiên Python thực thi phần thân của classcâu lệnh như một khối mã thông thường. Không gian tên kết quả (một dict) giữ các thuộc tính của lớp tương đương. Siêu dữ liệu được xác định bằng cách nhìn vào các lớp cơ sở của lớp tương đương (siêu dữ liệu được kế thừa), tại __metaclass__thuộc tính của lớp tương đương (nếu có) hoặc __metaclass__biến toàn cục. Siêu dữ liệu sau đó được gọi với tên, cơ sở và thuộc tính của lớp để khởi tạo nó.

Tuy nhiên, siêu dữ liệu thực sự xác định loại của một lớp, không chỉ là một nhà máy cho nó, vì vậy bạn có thể làm nhiều hơn với chúng. Ví dụ, bạn có thể định nghĩa các phương thức bình thường trên siêu dữ liệu. Các phương thức siêu dữ liệu này giống như các phương thức phân loại trong đó chúng có thể được gọi trên lớp mà không cần một thể hiện, nhưng chúng cũng không giống như các phương thức phân loại trong đó chúng không thể được gọi trong một thể hiện của lớp. type.__subclasses__()là một ví dụ về một phương thức trên typesiêu dữ liệu. Bạn cũng có thể định nghĩa các phương thức 'ma thuật' thông thường, như __add__, __iter____getattr__, để thực hiện hoặc thay đổi cách lớp hoạt động.

Đây là một ví dụ tổng hợp của các bit và miếng:

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__

13
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b
pppery

20
ppperry rõ ràng có nghĩa là bạn không thể tạo lại loại mà không sử dụng chính nó như một siêu dữ liệu. Đó là đủ công bằng để nói.
Holle van

3
Không nên hủy đăng ký () theo thể hiện của lớp Ví dụ?
Ciasto piekarz

5
Lưu ý rằng __metaclass__không được hỗ trợ trong Python 3. Trong sử dụng Python 3 class MyObject(metaclass=MyType), hãy xem python.org/dev/peps/pep-3115 và câu trả lời bên dưới.
BlackShift

2
Các tài liệu mô tả cách siêu dữ liệu được chọn . Siêu dữ liệu không được kế thừa nhiều như nó có nguồn gốc. Nếu bạn chỉ định một siêu dữ liệu, nó phải là một kiểu con của mỗi siêu dữ liệu lớp cơ sở; mặt khác, bạn sẽ sử dụng siêu dữ liệu lớp cơ sở là một kiểu con của siêu dữ liệu lớp cơ sở khác. Lưu ý rằng có thể không tìm thấy siêu dữ liệu hợp lệ và định nghĩa sẽ không thành công.
chepner

6819

Các lớp học như các đối tượng

Trước khi hiểu siêu dữ liệu, bạn cần thành thạo các lớp trong Python. Và Python có một ý tưởng rất đặc biệt về các lớp là gì, được mượn từ ngôn ngữ Smalltalk.

Trong hầu hết các ngôn ngữ, các lớp chỉ là những đoạn mã mô tả cách tạo ra một đối tượng. Điều đó cũng đúng trong Python:

>>> class ObjectCreator(object):
...       pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

Nhưng các lớp nhiều hơn thế trong Python. Các lớp học cũng là đối tượng.

Vâng, các đối tượng.

Ngay khi bạn sử dụng từ khóa class, Python sẽ thực thi nó và tạo một ĐỐI TƯỢNG. Hướng dẫn

>>> class ObjectCreator(object):
...       pass
...

tạo trong bộ nhớ một đối tượng có tên "ObjectCreator".

Đối tượng này (lớp) tự nó có khả năng tạo các đối tượng (các thể hiện) và đây là lý do tại sao nó là một lớp .

Tuy nhiên, nó vẫn là một đối tượng, và do đó:

  • bạn có thể gán nó cho một biến
  • bạn có thể sao chép nó
  • bạn có thể thêm thuộc tính cho nó
  • bạn có thể truyền nó dưới dạng tham số hàm

ví dụ:

>>> print(ObjectCreator) # you can print a class because it's an object
<class '__main__.ObjectCreator'>
>>> def echo(o):
...       print(o)
...
>>> echo(ObjectCreator) # you can pass a class as a parameter
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # you can add attributes to a class
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # you can assign a class to a variable
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

Tạo các lớp động

Vì các lớp là các đối tượng, bạn có thể tạo chúng một cách nhanh chóng, giống như bất kỳ đối tượng nào.

Đầu tiên, bạn có thể tạo một lớp trong một hàm bằng cách sử dụng class:

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>

Nhưng nó không quá năng động, vì bạn vẫn phải tự viết cả lớp.

Vì các lớp là các đối tượng, chúng phải được tạo bởi một cái gì đó.

Khi bạn sử dụng classtừ khóa, Python sẽ tự động tạo đối tượng này. Nhưng như với hầu hết mọi thứ trong Python, nó cung cấp cho bạn một cách để làm điều đó bằng tay.

Ghi nhớ chức năng type? Hàm cũ tốt cho phép bạn biết loại đối tượng là gì:

>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

Vâng, typecó một khả năng hoàn toàn khác, nó cũng có thể tạo ra các lớp học một cách nhanh chóng. typecó thể lấy mô tả của một lớp làm tham số và trả về một lớp.

(Tôi biết, thật ngớ ngẩn khi cùng một chức năng có thể có hai cách sử dụng hoàn toàn khác nhau theo các tham số bạn truyền cho nó. Đó là một vấn đề do khả năng tương thích ngược trong Python)

type hoạt động theo cách này:

type(name, bases, attrs)

Ở đâu:

  • name: tên của lớp
  • bases: tuple của lớp cha (đối với thừa kế, có thể để trống)
  • attrs: từ điển chứa tên và giá trị thuộc tính

ví dụ:

>>> class MyShinyClass(object):
...       pass

có thể được tạo thủ công theo cách này:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # create an instance with the class
<__main__.MyShinyClass object at 0x8997cec>

Bạn sẽ nhận thấy rằng chúng tôi sử dụng "MyShinyClass" làm tên của lớp và là biến để giữ tham chiếu lớp. Họ có thể khác nhau, nhưng không có lý do để phức tạp hóa mọi thứ.

typechấp nhận một từ điển để xác định các thuộc tính của lớp. Vì thế:

>>> class Foo(object):
...       bar = True

Có thể được dịch thành:

>>> Foo = type('Foo', (), {'bar':True})

Và được sử dụng như một lớp học bình thường:

>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

Và tất nhiên, bạn có thể thừa hưởng từ nó, vì vậy:

>>>   class FooChild(Foo):
...         pass

sẽ là:

>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True

Cuối cùng, bạn sẽ muốn thêm phương thức vào lớp của mình. Chỉ cần xác định một hàm với chữ ký thích hợp và gán nó làm thuộc tính.

>>> def echo_bar(self):
...       print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

Và bạn có thể thêm các phương thức thậm chí nhiều hơn sau khi bạn tự động tạo lớp, giống như thêm các phương thức vào một đối tượng lớp được tạo bình thường.

>>> def echo_bar_more(self):
...       print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

Bạn thấy nơi chúng ta sẽ đến: trong Python, các lớp là các đối tượng và bạn có thể tạo một lớp một cách linh hoạt.

Đây là những gì Python làm khi bạn sử dụng từ khóa classvà nó làm như vậy bằng cách sử dụng siêu dữ liệu.

Metaclass là gì (cuối cùng)

Metaclass là 'công cụ' tạo ra các lớp.

Bạn định nghĩa các lớp để tạo đối tượng, phải không?

Nhưng chúng ta đã học được rằng các lớp Python là các đối tượng.

Vâng, siêu dữ liệu là những gì tạo ra các đối tượng. Chúng là các lớp của lớp, bạn có thể hình dung chúng theo cách này:

MyClass = MetaClass()
my_object = MyClass()

Bạn đã thấy typecho phép bạn làm một cái gì đó như thế này:

MyClass = type('MyClass', (), {})

Đó là bởi vì chức năng typetrên thực tế là một siêu dữ liệu. typelà siêu dữ liệu Python sử dụng để tạo tất cả các lớp phía sau hậu trường.

Bây giờ bạn tự hỏi tại sao cái quái đó được viết bằng chữ thường, và không Type?

Chà, tôi đoán đó là vấn đề nhất quán với str, lớp tạo ra các đối tượng chuỗi và intlớp tạo các đối tượng nguyên. typechỉ là lớp tạo ra các đối tượng lớp.

Bạn thấy rằng bằng cách kiểm tra __class__thuộc tính.

Mọi thứ, và ý tôi là tất cả mọi thứ, là một đối tượng trong Python. Điều đó bao gồm ints, chuỗi, hàm và lớp. Tất cả đều là đồ vật. Và tất cả chúng đã được tạo ra từ một lớp:

>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

Bây giờ, cái gì là __class__bất kỳ __class__?

>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

Vì vậy, siêu dữ liệu chỉ là thứ tạo ra các đối tượng lớp.

Bạn có thể gọi nó là một 'nhà máy đẳng cấp' nếu bạn muốn.

type là siêu dữ liệu tích hợp Python sử dụng, nhưng tất nhiên, bạn có thể tạo siêu dữ liệu của riêng mình.

các __metaclass__thuộc tính

Trong Python 2, bạn có thể thêm một __metaclass__thuộc tính khi bạn viết một lớp (xem phần tiếp theo cho cú pháp Python 3):

class Foo(object):
    __metaclass__ = something...
    [...]

Nếu bạn làm như vậy, Python sẽ sử dụng siêu dữ liệu để tạo lớp Foo.

Cẩn thận, nó khó khăn.

Bạn viết class Foo(object)trước, nhưng đối tượng lớp Foochưa được tạo trong bộ nhớ.

Python sẽ tìm __metaclass__trong định nghĩa lớp. Nếu nó tìm thấy nó, nó sẽ sử dụng nó để tạo lớp đối tượng Foo. Nếu không, nó sẽ sử dụng typeđể tạo lớp.

Đọc mà nhiều lần.

Khi bạn làm:

class Foo(Bar):
    pass

Python thực hiện như sau:

Có một __metaclass__thuộc tính trong Foo?

Nếu có, hãy tạo trong bộ nhớ một đối tượng lớp (tôi đã nói một đối tượng lớp, ở lại với tôi ở đây), với tên Foobằng cách sử dụng những gì trong đó __metaclass__.

Nếu Python không thể tìm thấy __metaclass__, nó sẽ tìm kiếm __metaclass__ở cấp độ MODULE và cố gắng làm điều tương tự (nhưng chỉ đối với các lớp không kế thừa bất cứ thứ gì, về cơ bản là các lớp kiểu cũ).

Sau đó, nếu nó không thể tìm thấy bất kỳ thứ __metaclass__gì, nó sẽ sử dụng Barsiêu dữ liệu riêng của cha mẹ (có thể là mặc định type) để tạo đối tượng lớp.

Hãy cẩn thận ở đây rằng __metaclass__thuộc tính sẽ không được kế thừa, siêu dữ liệu của cha mẹ ( Bar.__class__) sẽ là. Nếu Barđược sử dụng một __metaclass__thuộc tính được tạo Barbằng type()(và không type.__new__()), các lớp con sẽ không kế thừa hành vi đó.

Bây giờ câu hỏi lớn là, những gì bạn có thể đặt vào __metaclass__?

Câu trả lời là: một cái gì đó có thể tạo ra một lớp.

Và những gì có thể tạo ra một lớp học? typehoặc bất cứ thứ gì mà phân lớp hoặc sử dụng nó.

Siêu dữ liệu trong Python 3

Cú pháp đặt siêu dữ liệu đã được thay đổi trong Python 3:

class Foo(object, metaclass=something):
    ...

tức là __metaclass__thuộc tính không còn được sử dụng, có lợi cho một đối số từ khóa trong danh sách các lớp cơ sở.

Hành vi của metaclasses tuy nhiên vẫn chủ yếu là giống nhau .

Một điều được thêm vào siêu dữ liệu trong python 3 là bạn cũng có thể chuyển các thuộc tính dưới dạng đối số từ khóa vào siêu dữ liệu, như vậy:

class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
    ...

Đọc phần bên dưới để biết cách python xử lý việc này.

Siêu dữ liệu tùy chỉnh

Mục đích chính của siêu dữ liệu là tự động thay đổi lớp khi nó được tạo.

Bạn thường làm điều này cho các API, nơi bạn muốn tạo các lớp phù hợp với bối cảnh hiện tại.

Hãy tưởng tượng một ví dụ ngu ngốc, nơi bạn quyết định rằng tất cả các lớp trong mô-đun của bạn nên có các thuộc tính của chúng được viết bằng chữ in hoa. Có một số cách để làm điều này, nhưng một cách là đặt __metaclass__ở cấp độ mô-đun.

Theo cách này, tất cả các lớp của mô-đun này sẽ được tạo bằng siêu dữ liệu này và chúng ta chỉ cần nói với siêu dữ liệu để biến tất cả các thuộc tính thành chữ hoa.

May mắn thay, __metaclass__thực sự có thể là bất kỳ ai có thể gọi được, nó không cần phải là một lớp chính thức (tôi biết, một cái gì đó với 'lớp' trong tên của nó không cần phải là một lớp, hãy tìm hiểu ... nhưng nó hữu ích).

Vì vậy, chúng ta sẽ bắt đầu với một ví dụ đơn giản, bằng cách sử dụng một hàm.

# the metaclass will automatically get passed the same argument
# that you usually pass to `type`
def upper_attr(future_class_name, future_class_parents, future_class_attrs):
    """
      Return a class object, with the list of its attribute turned
      into uppercase.
    """
    # pick up any attribute that doesn't start with '__' and uppercase it
    uppercase_attrs = {
        attr if attr.startswith("__") else attr.upper(): v
        for attr, v in future_class_attrs.items()
    }

    # let `type` do the class creation
    return type(future_class_name, future_class_parents, uppercase_attrs)

__metaclass__ = upper_attr # this will affect all classes in the module

class Foo(): # global __metaclass__ won't work with "object" though
    # but we can define __metaclass__ here instead to affect only this class
    # and this will work with "object" children
    bar = 'bip'

Hãy kiểm tra:

>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'

Bây giờ, chúng ta hãy làm chính xác như vậy, nhưng sử dụng một lớp thực cho siêu dữ liệu:

# remember that `type` is actually a class like `str` and `int`
# so you can inherit from it
class UpperAttrMetaclass(type):
    # __new__ is the method called before __init__
    # it's the method that creates the object and returns it
    # while __init__ just initializes the object passed as parameter
    # you rarely use __new__, except when you want to control how the object
    # is created.
    # here the created object is the class, and we want to customize it
    # so we override __new__
    # you can do some stuff in __init__ too if you wish
    # some advanced use involves overriding __call__ as well, but we won't
    # see this
    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in future_class_attrs.items()
        }
        return type(future_class_name, future_class_parents, uppercase_attrs)

Bây giờ chúng ta hãy viết lại ở trên, nhưng với các tên biến ngắn hơn và thực tế hơn bây giờ chúng ta biết ý nghĩa của chúng:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type(clsname, bases, uppercase_attrs)

Bạn có thể đã nhận thấy các đối số thêm cls. Không có gì đặc biệt về nó: __new__luôn nhận được lớp được định nghĩa, là tham số đầu tiên. Giống như bạn có selfcác phương thức thông thường nhận cá thể làm tham số đầu tiên hoặc lớp định nghĩa cho các phương thức lớp.

Nhưng đây không phải là OOP thích hợp. Chúng tôi đang gọi typetrực tiếp và chúng tôi không ghi đè hoặc gọi cho phụ huynh __new__. Hãy làm điều đó thay vào đó:

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return type.__new__(cls, clsname, bases, uppercase_attrs)

Chúng ta có thể làm cho nó thậm chí sạch hơn bằng cách sử dụng super, điều này sẽ giảm bớt sự kế thừa (bởi vì, vâng, bạn có thể có siêu dữ liệu, kế thừa từ siêu dữ liệu, kế thừa từ loại):

class UpperAttrMetaclass(type):
    def __new__(cls, clsname, bases, attrs):
        uppercase_attrs = {
            attr if attr.startswith("__") else attr.upper(): v
            for attr, v in attrs.items()
        }
        return super(UpperAttrMetaclass, cls).__new__(
            cls, clsname, bases, uppercase_attrs)

Ồ, và trong python 3 nếu bạn thực hiện cuộc gọi này với các đối số từ khóa, như thế này:

class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
    ...

Nó dịch điều này trong siêu dữ liệu để sử dụng nó:

class MyMetaclass(type):
    def __new__(cls, clsname, bases, dct, kwargs1=default):
        ...

Đó là nó. Thực sự không có gì hơn về metaclass.

Lý do đằng sau sự phức tạp của mã sử dụng siêu dữ liệu không phải là do siêu dữ liệu, đó là vì bạn thường sử dụng siêu dữ liệu để thực hiện các công cụ xoắn dựa vào nội tâm, thao tác kế thừa, vars __dict__, v.v.

Thật vậy, siêu dữ liệu đặc biệt hữu ích để làm ma thuật đen, và do đó những thứ phức tạp. Nhưng bản thân họ, họ đơn giản:

  • đánh chặn một sáng tạo lớp
  • sửa đổi lớp học
  • trả lại lớp đã sửa đổi

Tại sao bạn sẽ sử dụng các lớp siêu dữ liệu thay vì các hàm?

__metaclass__có thể chấp nhận bất kỳ cuộc gọi nào, tại sao bạn sẽ sử dụng một lớp vì nó rõ ràng phức tạp hơn?

Có một số lý do để làm như vậy:

  • Ý định là rõ ràng. Khi bạn đọc UpperAttrMetaclass(type), bạn biết những gì sẽ theo dõi
  • Bạn có thể sử dụng OOP. Metaclass có thể kế thừa từ metaclass, ghi đè các phương thức cha. Metaclass thậm chí có thể sử dụng metaclass.
  • Các lớp con của một lớp sẽ là các thể hiện của siêu dữ liệu của nó nếu bạn đã chỉ định một lớp siêu dữ liệu, nhưng không phải với hàm siêu dữ liệu.
  • Bạn có thể cấu trúc mã của bạn tốt hơn. Bạn không bao giờ sử dụng siêu dữ liệu cho một cái gì đó tầm thường như ví dụ trên. Nó thường cho một cái gì đó phức tạp. Có khả năng thực hiện một số phương thức và nhóm chúng trong một lớp là rất hữu ích để làm cho mã dễ đọc hơn.
  • Bạn có thể nối vào __new__, __init____call__. Điều này sẽ cho phép bạn làm những thứ khác nhau. Ngay cả khi thường bạn có thể làm tất cả trong đó __new__, một số người vẫn thoải mái hơn khi sử dụng __init__.
  • Chúng được gọi là metaclass, chết tiệt! Nó phải có ý nghĩa gì đó!

Tại sao bạn lại sử dụng metaclass?

Bây giờ là câu hỏi lớn. Tại sao bạn sẽ sử dụng một số tính năng dễ bị lỗi tối nghĩa?

Chà, thường thì bạn không:

Metaclass là ma thuật sâu sắc hơn mà 99% người dùng không bao giờ nên lo lắng. Nếu bạn tự hỏi liệu bạn có cần họ không, bạn không (những người thực sự cần họ biết chắc chắn rằng họ cần họ, và không cần một lời giải thích về lý do tại sao).

Peters Tim Tim Peters

Trường hợp sử dụng chính cho siêu dữ liệu là tạo API. Một ví dụ điển hình của việc này là Django ORM. Nó cho phép bạn định nghĩa một cái gì đó như thế này:

class Person(models.Model):
    name = models.CharField(max_length=30)
    age = models.IntegerField()

Nhưng nếu bạn làm điều này:

person = Person(name='bob', age='35')
print(person.age)

Nó sẽ không trả lại một IntegerFieldđối tượng. Nó sẽ trả về một int, và thậm chí có thể lấy nó trực tiếp từ cơ sở dữ liệu.

Điều này là có thể bởi vì models.Modelđịnh nghĩa __metaclass__và nó sử dụng một số phép thuật sẽ biến Personbạn vừa xác định bằng các câu lệnh đơn giản thành một móc nối phức tạp vào trường cơ sở dữ liệu.

Django làm cho một cái gì đó phức tạp trông đơn giản bằng cách hiển thị một API đơn giản và sử dụng siêu dữ liệu, tạo lại mã từ API này để thực hiện công việc thực sự đằng sau hậu trường.

Lời cuối

Đầu tiên, bạn biết rằng các lớp là các đối tượng có thể tạo các thể hiện.

Trong thực tế, các lớp học là chính họ. Của metaclass.

>>> class Foo(object): pass
>>> id(Foo)
142630324

Mọi thứ đều là một đối tượng trong Python và tất cả chúng đều là các thể hiện của các lớp hoặc các thể hiện của siêu dữ liệu.

Ngoại trừ type.

typethực sự là siêu dữ liệu của chính nó. Đây không phải là thứ bạn có thể sao chép bằng Python thuần túy và được thực hiện bằng cách gian lận một chút ở cấp độ thực hiện.

Thứ hai, metaclass rất phức tạp. Bạn có thể không muốn sử dụng chúng cho các thay đổi lớp rất đơn giản. Bạn có thể thay đổi các lớp bằng cách sử dụng hai kỹ thuật khác nhau:

99% thời gian bạn cần thay đổi lớp, bạn nên sử dụng những thứ này.

Nhưng 98% thời gian, bạn hoàn toàn không cần thay đổi lớp.


30
Dường như trong Django, models.Modelnó không sử dụng __metaclass__mà chỉ class Model(metaclass=ModelBase):để tham khảo một ModelBaselớp mà sau đó thực hiện phép thuật siêu hình đã nói ở trên. Bài đăng tuyệt vời! Đây là nguồn Django: github.com/django/django/blob/master/django/db/models/ Kẻ
Max Goodridge

15
<< Hãy cẩn thận ở đây rằng __metaclass__thuộc tính sẽ không được kế thừa, siêu dữ liệu của cha mẹ ( Bar.__class__) sẽ là. Nếu Barđược sử dụng một __metaclass__thuộc tính được tạo Barbằng type()(và không type.__new__()), các lớp con sẽ không kế thừa hành vi đó. >> - Bạn có thể / ai đó vui lòng giải thích sâu hơn một chút về đoạn văn này không?
petrux

15
@Maxoodridge Đó là cú pháp Python 3 cho siêu dữ liệu. Xem Mô hình dữ liệu Python 3.6 VS Mô hình dữ liệu Python 2.7
TBBle

2
Now you wonder why the heck is it written in lowercase, and not Type?- tốt bởi vì nó được triển khai trong C - đó là lý do tương tự defaultdict là chữ thường trong khi OrderedDict (trong python 2) là CamelCase bình thường
Mr_and_Mrs_D

15
Đó là câu trả lời wiki cộng đồng (vì vậy, những người bình luận với sửa chữa / cải tiến có thể xem xét chỉnh sửa nhận xét của họ thành câu trả lời, nếu họ chắc chắn rằng họ đúng).
Brōtsyorfuzthrāx

403

Lưu ý, câu trả lời này là dành cho Python 2.x vì nó được viết vào năm 2008, siêu dữ liệu hơi khác nhau trong 3.x.

Metaclass là nước sốt bí mật làm cho 'đẳng cấp' hoạt động. Siêu dữ liệu mặc định cho một đối tượng kiểu mới được gọi là 'loại'.

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

Metaclass mất 3 args. ' tên ', ' căn cứ ' và ' dict '

Đây là nơi bí mật bắt đầu. Tìm kiếm tên, căn cứ và dict đến từ định nghĩa lớp ví dụ này.

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

Hãy xác định một siêu dữ liệu sẽ thể hiện cách ' class: ' gọi nó.

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'

Và bây giờ, một ví dụ thực sự có ý nghĩa gì đó, điều này sẽ tự động làm cho các biến trong danh sách "thuộc tính" được đặt trên lớp và được đặt thành Không.

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

Lưu ý rằng hành vi ma thuật Initialisedthu được bằng cách có siêu dữ liệu init_attributeskhông được truyền vào một lớp con Initialised.

Dưới đây là một ví dụ cụ thể hơn, cho thấy cách bạn có thể phân lớp 'loại' để tạo một siêu dữ liệu thực hiện một hành động khi lớp được tạo. Điều này khá khó khăn:

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

169

Những người khác đã giải thích cách siêu dữ liệu hoạt động và cách chúng phù hợp với hệ thống loại Python. Đây là một ví dụ về những gì họ có thể được sử dụng cho. Trong một khung kiểm tra mà tôi đã viết, tôi muốn theo dõi thứ tự các lớp được định nghĩa, để sau này tôi có thể khởi tạo chúng theo thứ tự này. Tôi thấy dễ dàng nhất để làm điều này bằng cách sử dụng một siêu dữ liệu.

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

Bất cứ điều gì là một lớp con MyTypesau đó có một thuộc tính lớp _orderghi lại thứ tự mà các lớp được định nghĩa.


Cảm ơn ví dụ. Tại sao bạn thấy điều này dễ dàng hơn là kế thừa từ MyBase, người __init__(self)nói type(self)._order = MyBase.counter; MyBase.counter += 1?
Michael Gundlach

1
Tôi muốn các lớp mình, không phải cá thể của chúng, được đánh số.
loại

Phải rồi, duh. Cảm ơn. Mã của tôi sẽ đặt lại thuộc tính của MyType trên mỗi lần khởi tạo và sẽ không bao giờ đặt thuộc tính nếu một phiên bản của MyType không bao giờ được tạo. Giáo sư. (Và một thuộc tính lớp cũng có thể hoạt động, nhưng không giống như siêu dữ liệu, nó không cung cấp vị trí rõ ràng để lưu trữ quầy.)
Michael Gundlach

1
Đây là một ví dụ thú vị, không chỉ bởi vì người ta có thể thực sự thấy lý do tại sao một siêu dữ liệu có thể được xem xét với điều này, để cung cấp một giải pháp cho một khó khăn cụ thể. OTOH Tôi đấu tranh để được thuyết phục rằng bất kỳ ai cũng thực sự cần phải khởi tạo các đối tượng theo thứ tự các lớp của chúng được xác định: Tôi đoán chúng ta chỉ cần lấy từ của bạn cho điều đó :).
mike gặm nhấm

159

Một cách sử dụng cho siêu dữ liệu là tự động thêm các thuộc tính và phương thức mới vào một thể hiện.

Ví dụ, nếu bạn nhìn vào các mô hình Django , định nghĩa của chúng có vẻ hơi khó hiểu. Có vẻ như bạn chỉ xác định các thuộc tính lớp:

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

Tuy nhiên, trong thời gian chạy, các đối tượng Person chứa đầy các loại phương thức hữu ích. Xem nguồn cho một số siêu dữ liệu tuyệt vời.


6
Không phải việc sử dụng các lớp meta thêm các thuộc tính và phương thức mới vào một lớp chứ không phải là một thể hiện? Theo như tôi hiểu thì lớp meta làm thay đổi chính lớp đó và kết quả là các thể hiện có thể được xây dựng khác nhau bởi lớp bị thay đổi. Có thể là một chút sai lầm cho những người cố gắng để có được bản chất của một lớp meta. Có phương pháp hữu ích trên các trường hợp có thể đạt được bằng sự kế thừa bình thường. Việc tham chiếu đến mã Django là một ví dụ là tốt, mặc dù.
trixn

119

Tôi nghĩ phần giới thiệu ONLamp về lập trình siêu dữ liệu được viết tốt và giới thiệu thực sự tốt về chủ đề mặc dù đã được vài năm tuổi.

http://www.onlamp.com/pub/a/python/2003/04/17/metac gốm.html (được lưu trữ tại https://web.archive.org/web/20080206005253/http://www.onlamp. com / pub / a / python / 2003/04/17 / metaclass.html )

Tóm lại: Một lớp là một bản thiết kế để tạo ra một cá thể, một siêu dữ liệu là một bản thiết kế để tạo ra một lớp. Có thể dễ dàng nhận thấy rằng trong các lớp Python cũng cần phải là đối tượng hạng nhất để kích hoạt hành vi này.

Tôi chưa bao giờ tự viết một cái, nhưng tôi nghĩ rằng một trong những cách sử dụng siêu hình đẹp nhất có thể được nhìn thấy trong khung Django . Các lớp mô hình sử dụng một cách tiếp cận siêu dữ liệu để cho phép một kiểu khai báo viết các mô hình mới hoặc các lớp biểu mẫu. Trong khi siêu dữ liệu đang tạo lớp, tất cả các thành viên có khả năng tùy chỉnh lớp đó.

Điều còn lại để nói là: Nếu bạn không biết siêu dữ liệu là gì, xác suất bạn sẽ không cần đến chúng là 99%.


109

Siêu dữ liệu là gì? Bạn sử dụng chúng để làm gì?

TLDR: Một siêu dữ liệu khởi tạo và định nghĩa hành vi cho một lớp giống như một lớp khởi tạo và định nghĩa hành vi cho một thể hiện.

Mã giả:

>>> Class(...)
instance

Ở trên nên nhìn quen. Chà, từ đâu Classđến? Đây là một ví dụ của siêu dữ liệu (cũng là mã giả):

>>> Metaclass(...)
Class

Trong mã thực, chúng ta có thể vượt qua siêu dữ liệu mặc định type, mọi thứ chúng ta cần để khởi tạo một lớp và chúng ta có một lớp:

>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>

Đặt nó khác nhau

  • Một lớp là một thể hiện như một siêu dữ liệu là một lớp.

    Khi chúng ta khởi tạo một đối tượng, chúng ta sẽ có một thể hiện:

    >>> object()                          # instantiation of class
    <object object at 0x7f9069b4e0b0>     # instance

    Tương tự, khi chúng ta định nghĩa một lớp rõ ràng với siêu dữ liệu mặc định type, chúng ta sẽ khởi tạo nó:

    >>> type('Object', (object,), {})     # instantiation of metaclass
    <class '__main__.Object'>             # instance
  • Nói cách khác, một lớp là một thể hiện của siêu dữ liệu:

    >>> isinstance(object, type)
    True
  • Đặt một cách thứ ba, siêu dữ liệu là một lớp học.

    >>> type(object) == type
    True
    >>> object.__class__
    <class 'type'>

Khi bạn viết một định nghĩa lớp và Python thực thi nó, nó sử dụng siêu dữ liệu để khởi tạo đối tượng lớp (lần lượt sẽ được sử dụng để khởi tạo các thể hiện của lớp đó).

Giống như chúng ta có thể sử dụng các định nghĩa lớp để thay đổi cách các thể hiện đối tượng tùy chỉnh hoạt động, chúng ta có thể sử dụng định nghĩa lớp siêu dữ liệu để thay đổi cách hành xử của một đối tượng lớp.

Chúng có thể được sử dụng để làm gì? Từ các tài liệu :

Các ứng dụng tiềm năng cho siêu dữ liệu là vô biên. Một số ý tưởng đã được khám phá bao gồm ghi nhật ký, kiểm tra giao diện, ủy quyền tự động, tạo thuộc tính tự động, proxy, khung và khóa / đồng bộ hóa tài nguyên tự động.

Tuy nhiên, người dùng thường tránh sử dụng siêu dữ liệu trừ khi thực sự cần thiết.

Bạn sử dụng siêu dữ liệu mỗi khi bạn tạo một lớp:

Khi bạn viết một định nghĩa lớp, ví dụ như thế này,

class Foo(object): 
    'demo'

Bạn khởi tạo một đối tượng lớp.

>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)

Nó giống như gọi chức năng typevới các đối số thích hợp và gán kết quả cho một biến của tên đó:

name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)

Lưu ý, một số thứ tự động được thêm vào __dict__, tức là không gian tên:

>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>, 
'__module__': '__main__', '__weakref__': <attribute '__weakref__' 
of 'Foo' objects>, '__doc__': 'demo'})

Siêu dữ liệu của đối tượng chúng ta đã tạo, trong cả hai trường hợp, là type.

(Một ghi chú bên lề về nội dung của lớp __dict__: __module__có bởi vì các lớp phải biết chúng được định nghĩa ở đâu __dict____weakref__ở đó bởi vì chúng ta không định nghĩa __slots__- nếu chúng ta xác định__slots__ chúng ta sẽ tiết kiệm một chút không gian trong các trường hợp, như chúng ta có thể không cho phép __dict____weakref__bằng cách loại trừ chúng. Ví dụ:

>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})

... nhưng tôi lạc đề.)

Chúng ta có thể mở rộng typegiống như bất kỳ định nghĩa lớp nào khác:

Đây là mặc định __repr__của các lớp:

>>> Foo
<class '__main__.Foo'>

Một trong những điều có giá trị nhất mà chúng ta có thể làm theo mặc định khi viết một đối tượng Python là cung cấp cho nó một mục tốt __repr__. Khi chúng tôi gọi, help(repr)chúng tôi biết rằng có một bài kiểm tra tốt cho một bài kiểm tra __repr__cũng đòi hỏi một bài kiểm tra cho sự bình đẳng - obj == eval(repr(obj)). Việc triển khai đơn giản __repr____eq__cho các thể hiện lớp của lớp loại của chúng tôi cung cấp cho chúng tôi một bản trình diễn có thể cải thiện mặc định __repr__của các lớp:

class Type(type):
    def __repr__(cls):
        """
        >>> Baz
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        >>> eval(repr(Baz))
        Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
        """
        metaname = type(cls).__name__
        name = cls.__name__
        parents = ', '.join(b.__name__ for b in cls.__bases__)
        if parents:
            parents += ','
        namespace = ', '.join(': '.join(
          (repr(k), repr(v) if not isinstance(v, type) else v.__name__))
               for k, v in cls.__dict__.items())
        return '{0}(\'{1}\', ({2}), {{{3}}})'.format(metaname, name, parents, namespace)
    def __eq__(cls, other):
        """
        >>> Baz == eval(repr(Baz))
        True            
        """
        return (cls.__name__, cls.__bases__, cls.__dict__) == (
                other.__name__, other.__bases__, other.__dict__)

Vì vậy, bây giờ khi chúng ta tạo một đối tượng với siêu dữ liệu này, tiếng __repr__vang trên dòng lệnh cung cấp một cảnh tượng xấu xí hơn nhiều so với mặc định:

>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})

Với một __repr__định nghĩa đẹp cho thể hiện của lớp, chúng ta có khả năng mạnh hơn để gỡ lỗi mã của chúng ta. Tuy nhiên, việc kiểm tra thêm với nhiều eval(repr(Class))khả năng là không thể (vì các chức năng sẽ không thể tránh khỏi các mặc định __repr__của chúng).

Một cách sử dụng dự kiến: __prepare__một không gian tên

Ví dụ, nếu chúng ta muốn biết các phương thức của một lớp được tạo theo thứ tự nào, chúng ta có thể cung cấp một lệnh được sắp xếp theo thứ tự như không gian tên của lớp. Chúng tôi sẽ làm điều này __prepare__để trả về không gian tên dict cho lớp nếu nó được triển khai trong Python 3 :

from collections import OrderedDict

class OrderedType(Type):
    @classmethod
    def __prepare__(metacls, name, bases, **kwargs):
        return OrderedDict()
    def __new__(cls, name, bases, namespace, **kwargs):
        result = Type.__new__(cls, name, bases, dict(namespace))
        result.members = tuple(namespace)
        return result

Và cách sử dụng:

class OrderedMethodsObject(object, metaclass=OrderedType):
    def method1(self): pass
    def method2(self): pass
    def method3(self): pass
    def method4(self): pass

Và bây giờ chúng ta có một bản ghi về thứ tự mà các phương thức này (và các thuộc tính lớp khác) đã được tạo:

>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')

Lưu ý, ví dụ này đã được điều chỉnh từ tài liệu - enum mới trong thư viện chuẩn thực hiện điều này.

Vì vậy, những gì chúng tôi đã làm là khởi tạo một siêu dữ liệu bằng cách tạo một lớp. Chúng ta cũng có thể đối xử với siêu dữ liệu như bất kỳ lớp nào khác. Nó có một thứ tự giải quyết phương thức:

>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)

Và nó có giá trị gần đúng repr(mà chúng ta không còn có thể tránh được trừ khi chúng ta có thể tìm cách thể hiện các chức năng của mình.):

>>> OrderedMethodsObject
OrderedType('OrderedMethodsObject', (object,), {'method1': <function OrderedMethodsObject.method1 at 0x0000000002DB01E0>, 'members': ('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4'), 'method3': <function OrderedMet
hodsObject.method3 at 0x0000000002DB02F0>, 'method2': <function OrderedMethodsObject.method2 at 0x0000000002DB0268>, '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'OrderedMethodsObject' objects>, '__doc__': None, '__d
ict__': <attribute '__dict__' of 'OrderedMethodsObject' objects>, 'method4': <function OrderedMethodsObject.method4 at 0x0000000002DB0378>})

78

Cập nhật Python 3

Có (tại thời điểm này) hai phương thức chính trong siêu dữ liệu:

  • __prepare__
  • __new__

__prepare__cho phép bạn cung cấp ánh xạ tùy chỉnh (như an OrderedDict) để được sử dụng làm không gian tên trong khi lớp đang được tạo. Bạn phải trả về một thể hiện của bất kỳ không gian tên nào bạn chọn. Nếu bạn không thực hiện __prepare__một bình thường dictđược sử dụng.

__new__ chịu trách nhiệm cho việc tạo / sửa đổi thực tế của lớp cuối cùng.

Một siêu dữ liệu xương trần, không làm gì thêm muốn:

class Meta(type):

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

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

Một ví dụ đơn giản:

Giả sử bạn muốn một số mã xác thực đơn giản chạy trên các thuộc tính của mình - giống như nó phải luôn là một inthoặc a str. Không có siêu dữ liệu, lớp của bạn sẽ trông giống như:

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

Như bạn có thể thấy, bạn phải lặp lại tên của thuộc tính hai lần. Điều này làm cho lỗi chính tả có thể cùng với các lỗi khó chịu.

Một siêu dữ liệu đơn giản có thể giải quyết vấn đề đó:

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

Đây là giao diện của siêu dữ liệu (không sử dụng __prepare__vì không cần thiết):

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)

Một mẫu chạy của:

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

sản xuất:

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')

Lưu ý : Ví dụ này đủ đơn giản, nó cũng có thể được thực hiện với một trình trang trí lớp, nhưng có lẽ một siêu dữ liệu thực tế sẽ làm được nhiều hơn thế.

Lớp 'ValidateType' để tham khảo:

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

Ồ, đây là một tính năng mới tuyệt vời mà tôi không biết đã tồn tại trong Python 3. Cảm ơn bạn vì ví dụ !!
Tiến sĩ Rich Lysakowski

Lưu ý rằng vì python 3.6, bạn có thể sử dụng __set_name__(cls, name)trong bộ mô tả ( ValidateType) để đặt tên trong bộ mô tả ( self.namevà trong trường hợp này cũng vậy self.attr). Điều này đã được thêm vào để không phải đi sâu vào siêu dữ liệu cho trường hợp sử dụng chung cụ thể này (xem PEP 487).
Lars

68

Vai trò của __call__()phương thức siêu dữ liệu ' khi tạo một thể hiện của lớp

Nếu bạn đã thực hiện lập trình Python trong hơn một vài tháng, cuối cùng bạn sẽ vấp phải mã giống như thế này:

# 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')

Điều thứ hai là có thể khi bạn thực hiện __call__()phương thức ma thuật trên lớp.

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

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

Các __call__()phương pháp được gọi khi một thể hiện của một lớp được sử dụng như một callable. Nhưng như chúng ta đã thấy từ các câu trả lời trước, bản thân một lớp là một thể hiện của siêu dữ liệu, vì vậy khi chúng ta sử dụng lớp này như một phương thức có thể gọi được (tức là khi chúng ta tạo một thể hiện của nó), chúng ta thực sự gọi __call__()phương thức của siêu dữ liệu đó . Tại thời điểm này, hầu hết các lập trình viên Python đều có một chút bối rối bởi vì họ đã được thông báo rằng khi tạo một thể hiện như thế này, instance = SomeClass()bạn đang gọi __init__()phương thức của nó . Một số người đã đào sâu hơn một chút biết rằng trước __init__()đó __new__(). Chà, hôm nay một lớp sự thật khác đang được tiết lộ, trước khi __new__()có siêu dữ liệu ' __call__().

Chúng ta hãy nghiên cứu chuỗi gọi phương thức từ cụ thể là phối cảnh tạo một thể hiện của một lớp.

Đây là một siêu dữ liệu ghi lại chính xác thời điểm trước khi một cá thể được tạo ra và thời điểm nó sắp trả lại nó.

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

Đây là một lớp sử dụng siêu dữ liệu đó

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__()."

Và bây giờ hãy tạo một ví dụ về 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.

Quan sát rằng mã ở trên không thực sự làm gì hơn là ghi nhật ký các tác vụ. Mỗi phương thức ủy thác công việc thực tế cho việc triển khai của cha mẹ, do đó giữ hành vi mặc định. Vì typeMeta_1lớp cha mẹ ( typelà siêu dữ liệu cha mẹ mặc định) và xem xét trình tự sắp xếp của đầu ra ở trên, nên bây giờ chúng ta có manh mối về việc triển khai giả của 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

Chúng ta có thể thấy rằng __call__()phương thức của siêu dữ liệu là phương thức được gọi đầu tiên. Sau đó, nó ủy nhiệm việc tạo cá thể cho __new__()phương thức của lớp và khởi tạo cho cá thể __init__(). Nó cũng là cái cuối cùng trả về thể hiện.

Từ những điều trên, có thể thấy rằng siêu dữ liệu ' __call__()cũng được trao cơ hội để quyết định liệu một cuộc gọi đến Class_1.__new__()hay Class_1.__init__()cuối cùng sẽ được thực hiện. Trong quá trình thực hiện, nó thực sự có thể trả về một đối tượng chưa bị chạm bởi một trong hai phương thức này. Lấy ví dụ cách tiếp cận này với mẫu singleton:

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__()."

Hãy quan sát những gì xảy ra khi liên tục cố gắng tạo một đối tượng kiểu 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

Đây là một bổ sung tốt cho "câu trả lời được chấp nhận" trước đó. Nó cung cấp các ví dụ cho các lập trình viên trung gian để nhai.
Tiến sĩ Rich Lysakowski

56

Siêu dữ liệu là một lớp cho biết cách tạo (một số) lớp khác.

Đây là một trường hợp mà tôi thấy metaclass là một giải pháp cho vấn đề của mình: tôi có một vấn đề thực sự phức tạp, có lẽ có thể được giải quyết theo cách khác, nhưng tôi đã chọn giải quyết nó bằng siêu dữ liệu. Do tính phức tạp, nó là một trong số ít các mô-đun tôi đã viết trong đó các nhận xét trong mô-đun vượt quá số lượng mã đã được viết. Đây là ...

#!/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()

43

Phiên bản tl; dr

Các type(obj)chức năng giúp bạn loại một đối tượng.

Các type()của một lớp là nó metaclass .

Để sử dụng siêu dữ liệu:

class Foo(object):
    __metaclass__ = MyMetaClass

typelà siêu dữ liệu của riêng nó. Lớp của một lớp là một siêu dữ liệu-- phần thân của một lớp là các đối số được truyền cho siêu dữ liệu được sử dụng để xây dựng lớp.

Ở đây bạn có thể đọc về cách sử dụng siêu dữ liệu để tùy chỉnh xây dựng lớp.


42

typethực sự là một metaclass- một lớp tạo ra các lớp khác. Hầu hết metaclasslà các lớp con của type. Lớp metaclassnhận newlớp làm đối số đầu tiên của nó và cung cấp quyền truy cập vào đối tượng lớp với các chi tiết như được đề cập dưới đây:

>>> 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:

Lưu ý rằng lớp học không được khởi tạo bất cứ lúc nào; hành động đơn giản của việc tạo lớp kích hoạt thực thi metaclass.


27

Các lớp Python là các đối tượng - như ví dụ - của lớp meta của chúng.

Siêu dữ liệu mặc định, được áp dụng khi bạn xác định các lớp là:

class foo:
    ...

lớp meta được sử dụng để áp dụng một số quy tắc cho toàn bộ các lớp. Ví dụ: giả sử bạn đang xây dựng ORM để truy cập cơ sở dữ liệu và bạn muốn các bản ghi từ mỗi bảng sẽ thuộc một lớp được ánh xạ tới bảng đó (dựa trên các trường, quy tắc kinh doanh, v.v.,), có thể sử dụng siêu dữ liệu là ví dụ, logic nhóm kết nối, được chia sẻ bởi tất cả các lớp bản ghi từ tất cả các bảng. Một cách sử dụng khác là logic để hỗ trợ các khóa ngoại, bao gồm nhiều lớp bản ghi.

khi bạn xác định siêu dữ liệu, bạn loại lớp con và có thể ghi đè các phương thức ma thuật sau để chèn logic của bạn.

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

dù sao đi nữa, hai cái đó là những cái móc được sử dụng phổ biến nhất. metaclassing là mạnh mẽ, và ở trên không có danh sách sử dụng cho metaclassing gần và đầy đủ.


21

Hàm type () có thể trả về kiểu của một đối tượng hoặc tạo một kiểu mới,

ví dụ: chúng ta có thể tạo một lớp Hi với hàm type () và không cần sử dụng cách này với lớp 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

Ngoài việc sử dụng kiểu () để tạo các lớp một cách linh hoạt, bạn có thể kiểm soát hành vi tạo của lớp và sử dụng siêu dữ liệu.

Theo mô hình đối tượng Python, lớp là đối tượng, vì vậy lớp phải là một thể hiện của một lớp khác. Theo mặc định, một lớp Python là thể hiện của lớp loại. Đó là, kiểu là siêu dữ liệu của hầu hết các lớp dựng sẵn và siêu dữ liệu của các lớp do người dùng định nghĩa.

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']

Phép thuật sẽ có hiệu lực khi chúng ta chuyển các đối số từ khóa trong siêu dữ liệu, nó chỉ ra trình thông dịch Python để tạo CustomList thông qua ListMetaclass. new (), tại thời điểm này, chúng ta có thể sửa đổi định nghĩa lớp, ví dụ, và thêm một phương thức mới và sau đó trả về định nghĩa đã sửa đổi.


11

Ngoài các câu trả lời được công bố, tôi có thể nói rằng một metaclassđịnh nghĩa hành vi cho một lớp. Vì vậy, bạn có thể thiết lập rõ ràng siêu dữ liệu của bạn. Bất cứ khi nào Python có được một từ khóa classthì nó bắt đầu tìm kiếm metaclass. Nếu không tìm thấy - loại siêu dữ liệu mặc định được sử dụng để tạo đối tượng của lớp. Sử dụng __metaclass__thuộc tính, bạn có thể thiết lập metaclasslớp của mình:

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

print(MyClass.__metaclass__)

Nó sẽ tạo ra kết quả như thế này:

class 'type'

Và, tất nhiên, bạn có thể tạo riêng của mình metaclassđể xác định hành vi của bất kỳ lớp nào được tạo bằng lớp của bạn.

Để làm điều đó, metaclasslớp loại mặc định của bạn phải được kế thừa vì đây là lớp chính 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__)

Đầu ra sẽ là:

class '__main__.MyMetaClass'
class 'type'

4

Trong lập trình hướng đối tượng, siêu dữ liệu là một lớp có các thể hiện là các lớp. Giống như một lớp bình thường định nghĩa hành vi của một số đối tượng nhất định, siêu dữ liệu định nghĩa hành vi của lớp nhất định và các thể hiện của chúng Thuật ngữ siêu dữ liệu đơn giản có nghĩa là một cái gì đó được sử dụng để tạo các lớp. Nói cách khác, đó là lớp học của một lớp. Siêu dữ liệu được sử dụng để tạo lớp, giống như đối tượng là một thể hiện của một lớp, một lớp là một thể hiện của siêu dữ liệu. Trong lớp trăn cũng được coi là đối tượng.


Thay vì đưa ra các định nghĩa bookish, sẽ tốt hơn nếu bạn thêm một số ví dụ. Dòng đầu tiên trong câu trả lời của bạn dường như đã được sao chép từ mục Metaclass của Wikipedia.
minh bạch

@verisimilitude Tôi cũng đang học, bạn có thể giúp tôi cải thiện câu trả lời này bằng cách cung cấp một số ví dụ thực tế từ kinh nghiệm của bạn không ??
Venu Gopal Tewari

2

Đây là một ví dụ khác về những gì nó có thể được sử dụng cho:

  • Bạn có thể sử dụng metaclassđể thay đổi chức năng của thể hiện của nó (lớp).
class MetaMemberControl(type):
    __slots__ = ()

    @classmethod
    def __prepare__(mcs, f_cls_name, f_cls_parents,  # f_cls means: future class
                    meta_args=None, meta_options=None):  # meta_args and meta_options is not necessarily needed, just so you know.
        f_cls_attr = dict()
        if not "do something or if you want to define your cool stuff of dict...":
            return dict(make_your_special_dict=None)
        else:
            return f_cls_attr

    def __new__(mcs, f_cls_name, f_cls_parents, f_cls_attr,
                meta_args=None, meta_options=None):

        original_getattr = f_cls_attr.get('__getattribute__')
        original_setattr = f_cls_attr.get('__setattr__')

        def init_getattr(self, item):
            if not item.startswith('_'):  # you can set break points at here
                alias_name = '_' + item
                if alias_name in f_cls_attr['__slots__']:
                    item = alias_name
            if original_getattr is not None:
                return original_getattr(self, item)
            else:
                return super(eval(f_cls_name), self).__getattribute__(item)

        def init_setattr(self, key, value):
            if not key.startswith('_') and ('_' + key) in f_cls_attr['__slots__']:
                raise AttributeError(f"you can't modify private members:_{key}")
            if original_setattr is not None:
                original_setattr(self, key, value)
            else:
                super(eval(f_cls_name), self).__setattr__(key, value)

        f_cls_attr['__getattribute__'] = init_getattr
        f_cls_attr['__setattr__'] = init_setattr

        cls = super().__new__(mcs, f_cls_name, f_cls_parents, f_cls_attr)
        return cls


class Human(metaclass=MetaMemberControl):
    __slots__ = ('_age', '_name')

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def __getattribute__(self, item):
        """
        is just for IDE recognize.
        """
        return super().__getattribute__(item)

    """ with MetaMemberControl then you don't have to write as following
    @property
    def name(self):
        return self._name

    @property
    def age(self):
        return self._age
    """


def test_demo():
    human = Human('Carson', 27)
    # human.age = 18  # you can't modify private members:_age  <-- this is defined by yourself.
    # human.k = 18  # 'Human' object has no attribute 'k'  <-- system error.
    age1 = human._age  # It's OK, although the IDE will show some warnings. (Access to a protected member _age of a class)

    age2 = human.age  # It's OK! see below:
    """
    if you do not define `__getattribute__` at the class of Human,
    the IDE will show you: Unresolved attribute reference 'age' for class 'Human'
    but it's ok on running since the MetaMemberControl will help you.
    """


if __name__ == '__main__':
    test_demo()

metaclassrất mạnh, có rất nhiều thứ (như ma thuật khỉ) bạn có thể làm với nó, nhưng hãy cẩn thận điều này chỉ có thể được biết đến với bạn.


2

Một lớp, trong Python, là một đối tượng và giống như bất kỳ đối tượng nào khác, nó là một thể hiện của "một cái gì đó". "Cái gì đó" là cái được gọi là Metaclass. Siêu dữ liệu này là một loại lớp đặc biệt tạo ra các đối tượng của lớp khác. Do đó, metaclass chịu trách nhiệm tạo các lớp mới. Điều này cho phép lập trình viên tùy chỉnh cách tạo các lớp.

Để tạo một siêu dữ liệu, việc ghi đè các phương thức mới () và init () thường được thực hiện. new () có thể được ghi đè để thay đổi cách tạo đối tượng, trong khi init () có thể được ghi đè để thay đổi cách khởi tạo đối tượng. Metaclass có thể được tạo bằng một số cách. Một trong những cách là sử dụng hàm type (). Hàm type (), khi được gọi với 3 tham số, tạo ra một siêu dữ liệu. Các tham số là: -

  1. Tên lớp
  2. Tuple có các lớp cơ sở được kế thừa bởi lớp
  3. Một từ điển có tất cả các phương thức lớp và các biến lớp

Một cách khác để tạo siêu dữ liệu bao gồm từ khóa 'metaclass'. Xác định siêu dữ liệu là một lớp đơn giản. Trong các tham số của lớp kế thừa, vượt qua metaclass = metaclass_name

Metaclass có thể được sử dụng cụ thể trong các tình huống sau: -

  1. khi một hiệu ứng cụ thể phải được áp dụng cho tất cả các lớp con
  2. Tự động thay đổi lớp (khi tạo) là bắt buộc
  3. Bởi các nhà phát triển API

2

Lưu ý rằng trong python 3.6, một phương pháp dunder mới __init_subclass__(cls, **kwargs)đã được giới thiệu để thay thế rất nhiều trường hợp sử dụng phổ biến cho metaclass. Được gọi khi một lớp con của lớp định nghĩa được tạo. Xem tài liệu trăn .


-3

Metaclass là một loại lớp xác định cách lớp sẽ hành xử như thế nào hoặc chúng ta có thể nói rằng chính lớp đó là một thể hiện của siêu dữ liệu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.