Câu hỏi Metaclasses trong Python là gì?


Metaclasses là gì và chúng ta sử dụng chúng như thế nào?


4584
2017-09-19 06:10


gốc




Các câu trả lời:


Một metaclass là lớp của một lớp. Giống như một lớp xác định cách một thể hiện của lớp hoạt động, một metaclass định nghĩa cách một lớp hoạt động. Một lớp là một thể hiện của một metaclass.

metaclass diagram

Trong Python, bạn có thể sử dụng các callables tùy ý cho metaclasses (như Jerub cho thấy), cách tiếp cận hữu ích hơn là thực sự biến nó thành một lớp thực tế. type là metaclass thông thường trong Python. Trong trường hợp bạn đang tự hỏi, vâng, type chính 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ì đó như type hoàn toàn bằng Python, nhưng Python cheats một chút. Để tạo metaclass của riêng bạn trong Python bạn thực sự chỉ muốn phân lớp type.

Một metaclass thường được sử dụng như một nhà máy lớp. Giống như bạn tạo một thể hiện của lớp bằng cách gọi lớp, Python tạo một lớp mới (khi nó thực hiện câu lệnh 'lớp') bằng cách gọi metaclass. Kết hợp với bình thường __init__ và __new__ các phương thức, metaclasses do đó cho phép bạn thực hiện 'những thứ phụ trội' khi tạo một lớp, như đăng ký lớp mới với một số đăng ký, hoặc thậm chí thay thế lớp với một cái gì đó khác hoàn toàn.

Khi mà class câu lệnh được thực thi, Python trước tiên thực hiện phần thân của class tuyên bố như một khối mã bình thường. Không gian tên kết quả (một dict) giữ các thuộc tính của class-to-be. Các metaclass được xác định bằng cách nhìn vào baseclasses của class-to-be (metaclasses được thừa hưởng), tại __metaclass__ thuộc tính của class-to-be (nếu có) hoặc __metaclass__ biến toàn cục. Metaclass sau đó được gọi với tên, căn cứ và thuộc tính của lớp để khởi tạo nó.

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

Dưới đây là một ví dụ tổng hợp về các bit và phần:

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__

2087
2017-09-19 07:01



"Bạn sẽ không thể tạo lại một cái gì đó giống như loại hoàn toàn bằng Python, nhưng Python cheats một chút" là không đúng sự thật. - ppperry
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b - ppperry


Các lớp làm đối tượng

Trước khi hiểu metaclasses, bạn cần phải nắm vững các lớp trong Python. Và Python có một ý tưởng rất đặc biệt về các lớp học, được mượn từ ngôn ngữ Smalltalk.

Trong hầu hết các ngôn ngữ, các lớp chỉ là các đ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 học nhiều hơn trong Python. Các lớp cũng là các đối tượng.

Vâng, đồ vật.

Ngay sau khi bạn sử dụng từ khóa class, Python thực thi nó và tạo một đối tượng. Hướng dẫn

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

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

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

Nhưng vẫn cò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 vào nó
  • bạn có thể truyền nó như một 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 ngay lập tức, 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 ra bởi một cái gì đó.

Khi bạn sử dụng class từ 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 đó một cách thủ công.

Nhớ hàm type? Chức năng cũ tốt cho phép bạn biết nhập một đối tượng là:

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

Tốt, type có khả năng hoàn toàn khác, nó cũng có thể tạo các lớp học khi đang bay. type có thể lấy mô tả của một lớp làm tham số, và trả lại một lớp.

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

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

type(name of the class,
     tuple of the parent class (for inheritance, can be empty),
     dictionary containing attributes names and values)

ví dụ.:

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

có thể được tạo theo cách 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àm biến để giữ tham chiếu lớp. Chúng có thể khác nhau, nhưng không có lý do gì để làm phức tạp.

type chấ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 sang:

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

Và được sử dụng như một lớp 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ể kế thừa 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 các phương thức vào lớp của bạn. Chỉ cần xác định một hàm với chữ ký thích hợp và gán nó làm một 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 nhiều phương thức 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 chúng ta đang đi đâu: 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 tự động.

Đây là những gì Python làm khi bạn sử dụng từ khóa class, và nó làm như vậy bằng cách sử dụng một metaclass.

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

Metaclasses là 'thứ' tạo ra các lớp.

Bạn định nghĩa các lớp để tạo các đối tượng, đúng 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, metaclasses là những gì tạo ra các đối tượng này. Họ là những lớp học 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 điều đó type cho phép bạn làm điều gì đó như thế này:

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

Đó là vì chức năng type trên thực tế là một metaclass. type là metaclass Python sử dụng để tạo tất cả các lớp đằng sau hậu trường.

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

Vâng, tôi đoán đó là vấn đề nhất quán với str, lớp học tạo ra chuỗi đối tượng và intlớp tạo ra các đối tượng số nguyên. type Là chỉ 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.

Tất cả mọi thứ, và tôi có nghĩa là tất cả mọi thứ, là một đối tượng trong Python. Bao gồm ints, chuỗi, hàm và lớp. Tất cả chúng đều là đồ vật. Và tất cả chúng đều có đượ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__ của 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, một metaclass chỉ là thứ tạo ra các đối tượng lớp.

Bạn có thể gọi nó là 'nhà máy lớp' nếu bạn muốn.

type là Python được sử dụng trong metaclass, nhưng tất nhiên, bạn có thể tạo riêng metaclass.

Các __metaclass__ thuộc tính

Bạn có thể thêm __metaclass__ khi bạn viết một lớp:

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

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

Cẩn thận, nó phức tạp.

Bạn viết class Foo(object) đầu tiên, nhưng đối tượng lớp Foo không được tạo trong bộ nhớ.

Python sẽ tìm kiế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 nhiều lần.

Khi bạn làm:

class Foo(Bar):
    pass

Python làm như sau:

Có cái nào không __metaclass__ thuộc tính trong Foo?

Nếu có, 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 Foo bằ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 một __metaclass__ ở cấp MODULE, và cố gắng làm tương tự (nhưng chỉ cho các lớp không kế thừa bất kỳ thứ gì, về cơ bản các lớp kiểu cũ).

Sau đó, nếu nó không thể tìm thấy bất kỳ __metaclass__ ở tất cả, nó sẽ sử dụng Bar's (cha mẹ đầu tiên) của riêng metaclass (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 thừa kế, metaclass của cha mẹ (Bar.__class__) sẽ là. Nếu Bar đã sử dụng một __metaclass__ thuộc tính đã tạo Bar với 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à, bạn có thể đặt cái gì vào __metaclass__ ?

Câu trả lời là: 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 kỳ thứ gì phân loại hoặc sử dụng nó.

Metaclasses tùy chỉnh

Mục đích chính của metaclass là tự động thay đổi lớp học, khi nó được tạo ra.

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 khớp với Ngữ 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ữ hoa. Có nhiều cách để làm điều này, nhưng một cách là để thiết lập __metaclass__ ở cấp mô-đun.

Bằng cách này, tất cả các lớp của mô-đun này sẽ được tạo bằng cách sử dụng metaclass này, và chúng ta chỉ cần nói cho metaclass 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ỳ cuộc gọi nào, nó không cần phải là lớp học 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, đi con số ... nhưng nó rất 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_attr):
    """
      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_attr = {}
    for name, val in future_class_attr.items():
        if not name.startswith('__'):
            uppercase_attr[name.upper()] = val
        else:
            uppercase_attr[name] = val

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

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

print(hasattr(Foo, 'bar'))
# Out: False
print(hasattr(Foo, 'BAR'))
# Out: True

f = Foo()
print(f.BAR)
# Out: 'bip'

Bây giờ, chúng ta hãy làm chính xác như nhau, nhưng sử dụng một lớp thực sự cho một metaclass:

# 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_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type(future_class_name, future_class_parents, uppercase_attr)

Nhưng điều này không thực sự là OOP. Chúng tôi gọi type trực tiếp và chúng tôi không ghi đè hoặc gọi cho phụ huynh __new__. Hãy làm nó:

class UpperAttrMetaclass(type):

    def __new__(upperattr_metaclass, future_class_name,
                future_class_parents, future_class_attr):

        uppercase_attr = {}
        for name, val in future_class_attr.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        # reuse the type.__new__ method
        # this is basic OOP, nothing magic in there
        return type.__new__(upperattr_metaclass, future_class_name,
                            future_class_parents, uppercase_attr)

Bạn có thể đã nhận thấy đối số bổ sung upperattr_metaclass. Có không có gì đặc biệt về nó: __new__ luôn nhận được lớp mà nó được định nghĩa trong, như tham số đầu tiên. Cũng giống như bạn có self cho các phương thức thông thường nhận được cá thể như tham số đầu tiên, hoặc lớp xác định cho các phương thức lớp.

Tất nhiên, những cái tên tôi sử dụng ở đây rất dài vì mục đích rõ ràng, nhưng cho self, tất cả các đối số đều có tên thông thường. Vì vậy, một sản xuất thực sự metaclass sẽ trông như thế này:

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return type.__new__(cls, clsname, bases, uppercase_attr)

Chúng tôi có thể làm cho nó thậm chí còn sạch hơn bằng cách sử dụng super, điều này sẽ làm giảm tính kế thừa (vì có, bạn có thể có metaclasses, kế thừa từ metaclasses, kế thừa từ kiểu):

class UpperAttrMetaclass(type):

    def __new__(cls, clsname, bases, dct):

        uppercase_attr = {}
        for name, val in dct.items():
            if not name.startswith('__'):
                uppercase_attr[name.upper()] = val
            else:
                uppercase_attr[name] = val

        return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

Đó là nó. Thực sự không có gì khác về metaclasses.

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

Thật vậy, metaclasses đặc biệt hữu ích để làm phép màu đen, và do đó những thứ phức tạp. Nhưng một mình, chúng rất đơn giản:

  • đánh chặn việc tạo lớp
  • sửa đổi lớp
  • trả lại lớp đã sửa đổi

Tại sao bạn sử dụng các lớp metaclasses 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 lại sử dụng một lớp học 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ẽ làm theo
  • Bạn có thể sử dụng OOP. Metaclass có thể kế thừa từ metaclass, ghi đè lên các phương thức cha mẹ. Metaclasses thậm chí có thể sử dụng metaclasses.
  • Các lớp con của một lớp sẽ là các cá thể của metaclass của nó nếu bạn đã chỉ định một lớp metaclass, nhưng không phải với một hàm metaclass.
  • Bạn có thể cấu trúc mã của mình tốt hơn. Bạn không bao giờ sử dụng metaclasses cho một cái gì đó như 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 pháp 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ể móc vào __new__, __init__ và __call__. Điều này sẽ cho phép bạn làm những thứ khác nhau. Ngay cả khi thông thường bạn có thể làm tất cả trong __new__, một số người chỉ cảm thấy thoải mái hơn khi sử dụng __init__.
  • Chúng được gọi là metaclasses, chết tiệt! Nó phải có nghĩa gì đó!

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

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

Vâng, thông thường bạn không:

Metaclasses là ma thuật sâu sắc hơn   99% người dùng không bao giờ nên lo lắng.   Nếu bạn tự hỏi bạn có cần chúng 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 chúng, và không cần   giải thích về lý do tại sao).

Python Guru Tim Peters

Trường hợp sử dụng chính cho một metaclass là tạo ra một API. Một ví dụ điển hình của điều này là ORM Django.

Nó cho phép bạn xác định 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:

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

Nó sẽ không trả lại IntegerField vật. Nó sẽ trả về một intvà thậm chí có thể lấy 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 Person bạn chỉ định nghĩa với các câu lệnh đơn giản vào một móc phức tạp đến một 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 lộ ra một API đơn giản và sử dụng metaclasses, tạo lại mã từ API này để thực hiện công việc thực đằ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 cá thể.

Trên thực tế, các lớp là bản thân các cá thể. Của metaclasses.

>>> 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à trường hợp của các lớp hoặc các trường hợp của metaclasses.

Ngoại trừ type.

type thực sự là metaclass của chính nó. Đây không phải là thứ bạn có thể sinh sản bằng Python thuần túy và được thực hiện bằng cách gian lận một chút tại triển khai cấp độ.

Thứ hai, metaclasses rất phức tạp. Bạn có thể không muốn sử dụng chúng cho 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 chúng.

Nhưng 98% thời gian, bạn không cần phải thay đổi lớp học.


5763
2017-09-19 06:26



Có vẻ như ở Django models.Model nó không sử dụng __metaclass__ mà đúng hơn là class Model(metaclass=ModelBase): để tham chiếu ModelBase lớp mà sau đó thực hiện phép thuật metaclass 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/… - Max Goodridge
<< Hãy cẩn thận ở đây rằng __metaclass__ thuộc tính sẽ không được thừa kế, metaclass của cha mẹ (Bar.__class__) sẽ là. Nếu Bar đã sử dụng một __metaclass__ thuộc tính đã tạo Bar với 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 đó giải thích sâu hơn một chút về đoạn này không? - petrux
@MaxGoodridge Đó là cú pháp Python 3 cho metaclasses. Xem Mô hình dữ liệu Python 3.6 VS Mô hình dữ liệu Python 2.7 - TBBle
Đó là câu trả lời của cộng đồng wiki (vì vậy, những người đã nhận xét về sửa đổi / cải tiến có thể xem xét chỉnh sửa nhận xét của họ vào câu trả lời, nếu họ chắc chắn rằng họ là chính xác). - Shule
Phần nào của câu trả lời này là về python2 và về pythono3? - styrofoam fly


Lưu ý, câu trả lời này là cho Python 2.x như nó đã được viết vào năm 2008, metaclasses hơi khác nhau trong 3.x, xem các ý kiến.

Metaclasses là loại nước sốt bí mật tạo nên tác phẩm 'lớp'. Giá trị mặc định của một đối tượng kiểu mới được gọi là 'type'.

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

Metaclasses 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ừ đâu trong định nghĩa lớp mẫu này.

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

Cho phép xác định một metaclass sẽ chứng minh như thế nào 'lớp học:'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 là một cái 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à đặ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 mà 'Initalised' đạt được bằng cách có metaclass init_attributes không được chuyển vào một lớp con của Initalised.

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 'type' để tạo một metaclass thực hiện một hành động khi lớp được tạo ra. Điều này khá phức tạp:

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

312
2017-09-19 06:45





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

Ví dụ, nếu bạn nhìn vào Mô hình Django, định nghĩa của họ có vẻ hơi khó hiểu. Có vẻ như bạn chỉ đang xác định 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, khi chạy các đối tượng Person được lấp đầy với tất cả các loại phương thức hữu ích. Xem nguồn cho một số metaclassery tuyệt vời.


125
2018-06-21 16:30



Không phải là việc sử dụng các lớp meta thêm thuộc tính và phương thức mới vào lớp học và không phải là một ví dụ? Theo như tôi hiểu nó, lớp meta thay đổi chính lớp đó và kết quả là các cá thể có thể được xây dựng khác nhau bởi lớp được thay đổi. Có thể là một chút gây hiểu 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 cá thể có thể đạt được bằng sự kế thừa thông thường. Các tham chiếu đến mã Django là một ví dụ là tốt, mặc dù. - trixn


Những người khác đã giải thích cách metaclasses hoạt động và cách chúng phù hợp với hệ thống kiểu Python. Đây là một ví dụ về những gì họ có thể được sử dụng cho. Trong một khuôn khổ thử nghiệm tôi đã viết, tôi muốn theo dõi thứ tự mà trong đó 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 tìm thấy nó dễ nhất để làm điều này bằng cách sử dụng một metaclass.

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 kỳ thứ gì là lớp con của MyType sau đó nhận thuộc tính lớp _order ghi lại thứ tự các lớp được định nghĩa.


117
2017-09-19 06:32





Tôi nghĩ rằng việc giới thiệu ONLamp để lập trình metaclass cũng được viết và đưa ra một giới thiệu thực sự tốt cho chủ đề mặc dù đã được một vài tuổi rồi.

http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html

Tóm lại: Một lớp là một kế hoạch chi tiết cho việc tạo ra một thể hiện, một metaclass là một kế hoạch chi tiết cho việc tạo ra một lớp. Có thể dễ dàng thấy rằng trong các lớp Python cũng cần phải là các đối tượng hạng nhất để kích hoạt hành vi này.

Tôi chưa bao giờ viết một bản thân mình, nhưng tôi nghĩ một trong những cách sử dụng metaclasses đẹp nhất có thể được thấy trong Khung Django. Các lớp mô hình sử dụng một phương pháp metaclass để 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 metaclass đang tạo lớp, tất cả các thành viên đều có khả năng tùy biến lớp đó.

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


86
2017-08-10 23:28





Metaclasses là gì? Bạn sử dụng chúng để làm gì?

TLDR: Một metaclass instantiates và định nghĩa hành vi cho một lớp giống như một lớp instantiates và định nghĩa hành vi cho một cá thể.

Mã giả:

>>> Class(...)
instance

Hình trên trông khá quen thuộc. Vâng, ở đâu Class đến từ? Đó là một thể hiện của một metaclass (cũng là mã giả):

>>> Metaclass(...)
Class

Trong mã thực, chúng ta có thể vượt qua metaclass 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

  • Một lớp là một thể hiện như một metaclass 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 cá thể:

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

    Tương tự như vậy, khi chúng ta định nghĩa một lớp rõ ràng với metaclass mặc định, type, chúng tôi 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 một metaclass:

    >>> isinstance(object, type)
    True
    
  • Đặt một cách thứ ba, một metaclass là lớp của lớp.

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

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

Cũng giống như chúng ta có thể sử dụng các định nghĩa lớp để thay đổi cách các cá thể đối tượng tùy chỉnh hoạt động, chúng ta có thể sử dụng định nghĩa lớp metaclass để thay đổi cách một đối tượng lớp hoạt động.

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

Việc sử dụng tiềm năng cho metaclasses là vô biên. Một số ý tưởng đã được khám phá bao gồm đăng nhập, kiểm tra giao diện, ủy nhiệm tự động, tạo thuộc tính tự động, proxy, khung công tác và khóa / đồng bộ tài nguyên tự động.

Tuy nhiên, nó thường được khuyến khích cho người dùng tránh sử dụng metaclasses trừ khi hoàn toàn cần thiết.

Bạn sử dụng một metaclass mỗi khi bạn tạo một lớp:

Khi bạn viế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 hàm type vớ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'})

Các metaclass của đối tượng chúng tôi đã tạo, trong cả hai trường hợp, là type.

(Một lưu ý phụ về nội dung của lớp học __dict__: __module__ là vì các lớp phải biết chúng được định nghĩa ở đâu và __dict__ và __weakref__ có bởi vì chúng tôi không xác định __slots__ - nếu chúng ta định nghĩa __slots__ chúng tôi sẽ tiết kiệm một chút không gian trong các trường hợp, vì chúng tôi không thể cho phép __dict__ và __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 tôi có thể mở rộng type giống như bất kỳ định nghĩa lớp nào khác:

Đây là mặc định __repr__ các lớp học:

>>> 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 __repr__. Khi chúng ta gọi help(repr) chúng ta biết rằng có một thử nghiệm tốt cho một __repr__ cũng đòi hỏi một bài kiểm tra bình đẳng - obj == eval(repr(obj)). Việc triển khai đơn giản sau đây của __repr__ và __eq__ cho các phiên bả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 về mặc định __repr__ các lớp học:

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 ra một đối tượng với metaclass này, __repr__ lặp lại trên dòng lệnh cung cấp một cảnh 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 tốt đẹp __repr__ được định nghĩa cho cá thể lớp, chúng tôi có khả năng gỡ lỗi mã của chúng tôi mạnh hơn. Tuy nhiên, kiểm tra nhiều hơn nữa với eval(repr(Class)) không chắc (vì các hàm sẽ không thể đánh giá từ mặc định của chúng __repr__'S).

Mức 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 thứ tự các phương thức của lớp được tạo ra, chúng ta có thể cung cấp một lệnh dict được đặt làm không gian tên của lớp. Chúng tôi sẽ làm điều này với __prepare__ cái nào trả về dict không gian tên cho lớp nếu nó được thực hiện 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à trong đó các phương thức này (và các thuộc tính lớp khác) đã được tạo ra:

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

Lưu ý, ví dụ này đã được điều chỉnh từ tài liệu - cái mới enum 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 metaclass bằng cách tạo ra một lớp học. Chúng ta cũng có thể đối xử với metaclass như bất kỳ lớp nào khác. Nó có thứ tự độ phân giải phương thức:

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

Và nó có khoảng chính xác repr (mà chúng tôi không còn có thể đánh giá trừ khi chúng tôi có thể tìm cách đại diện cho các chức năng của chúng tôi.):

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

74
2018-03-01 19:48





Cập nhật Python 3

Có (tại thời điểm này) hai phương pháp chính trong một metaclass:

  • __prepare__
  • __new__

__prepare__ cho phép bạn cung cấp ánh xạ tùy chỉnh (chẳng hạn như 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 cá thể của bất kỳ không gian tên nào bạn chọn. Nếu bạn không triển khai __prepare__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 metaclass trần, do-không-gì-thêm-thêm sẽ thích:

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 nhận đơn giản để chạy trên các thuộc tính của bạn - như nó phải luôn là một int hoặc một str. Không có metaclass, lớp của bạn sẽ trông giống như sau:

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

Như bạn 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 kích thích.

Một metaclass đơ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à những gì metaclass sẽ giống như (không sử dụng __prepare__ vì nó 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:

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

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

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

55
2017-10-13 09:21



Trong ví dụ metaclass tôi nhận được NameError: name 'ValidateType' is not defined. Bất kỳ đề xuất làm thế nào để khắc phục tốt nhất điều này? Tôi đang sử dụng python 2 - Nickpick


Một metaclass là một lớp cho biết làm thế nào (một số) lớp khác nên được tạo ra.

Đây là trường hợp tôi thấy metaclass là một giải pháp cho vấn đề của tôi: Tôi đã có một vấn đề thực sự phức tạp, mà có lẽ có thể đã được giải quyết khác nhau, nhưng tôi đã chọn để giải quyết nó bằng cách sử dụng một metaclass. Bởi vì sự phức tạp, nó là một trong số ít các mô-đun tôi đã viết, nơi các bình luận trong mô-đun vượt quá số lượng mã đã được viết. Đây rồi ...

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

41
2017-08-09 18:49





Vai trò của một metaclass's __call__() phương thức khi tạo một cá thể 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ư sau:

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

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

Hãy nghiên cứu chuỗi cuộc gọi phương thức từ cụ thể quan điểm tạo ra một thể hiện của một lớp.

Đây là một metaclass 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ả về 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 metaclass đó

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 thể hiện 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.

Đoạn mã trên không thực sự làm bất cứ điều gì khác ngoài việc ghi lại tác vụ và sau đó ủy nhiệm công việc thực tế cho phụ huynh (tức là giữ hành vi mặc định). Vì vậy type đang được Meta_1's lớp cha mẹ, chúng ta có thể tưởng tượng rằng đây sẽ là việc thực hiện 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 metaclass's __call__() là phương thức được gọi đầu tiên. Sau đó, ủy quyền tạo ra cá thể cho lớp __new__()phương pháp và khởi tạo cho thể hiện __init__(). Nó cũng là cái cuối cùng trả về cá thể.

Từ phía trên nó bắt nguồn của metaclass __call__() cũng có cơ hội để quyết định có hay không gọi đến Class_1.__new__() hoặc là 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 mà không bị xúc động bởi một trong những phương thức này. Lấy ví dụ phương pháp này cho mẫu đơn:

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 thuộc loại 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

37
2017-12-27 02:21





type thực sự là một metaclass - một lớp học tạo ra các lớp khác. Phần lớn metaclass là các lớp con của type. Các metaclass nhận được new lớp là đố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 bên dưới:

>>> 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 không được khởi tạo bất kỳ lúc nào; hành động đơn giản của việc tạo lớp thực thi được kích hoạt của metaclass.


27
2017-07-13 07:58