Câu trả lời:
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ự. type
là siêu dữ liệu thông thường trong Python. type
bả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ư type
hoà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__
và __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 class
câu lệnh được thực thi, trước tiên Python thực thi phần thân của class
câ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 type
siê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__
và __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__
__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.
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 đó:
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>
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 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 đó 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, type
có 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. type
có 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ớpbases
: 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ínhví 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ứ.
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 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 class
và nó làm như vậy bằng cách sử dụng siêu dữ liệu.
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 type
cho 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 type
trên thực tế là một siêu dữ liệu. type
là 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à int
lớp tạo các đối tượng nguyên. type
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.
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.
__metaclass__
thuộc tínhTrong 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 Foo
chư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 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 __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 Bar
siê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 Bar
bằ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? type
hoặc bất cứ thứ gì mà phân lớp hoặc sử dụng nó.
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.
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ó self
cá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 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 đ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:
Vì __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:
UpperAttrMetaclass(type)
, bạn biết những gì sẽ theo dõi__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 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__
.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 Person
bạ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.
Đầ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
.
type
thự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.
models.Model
nó không sử dụng __metaclass__
mà chỉ class Model(metaclass=ModelBase):
để tham khảo một ModelBase
lớ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ẻ
__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 Bar
bằ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?
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
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 Initialised
thu được bằng cách có siêu dữ liệu init_attributes
khô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
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 MyType
sau đó có một thuộc tính lớp _order
ghi lại thứ tự mà các lớp được định nghĩa.
__init__(self)
nói type(self)._order = MyBase.counter; MyBase.counter += 1
?
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.
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%.
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'>
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.
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 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'})
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__
và __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__
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 đề.)
type
giố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__
và __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).
__prepare__
một không gian tênVí 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>})
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__
và__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 int
hoặ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
__set_name__(cls, name)
trong bộ mô tả ( ValidateType
) để đặt tên trong bộ mô tả ( self.name
và 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).
__call__()
phương thức siêu dữ liệu ' khi tạo một thể hiện của lớpNế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ì type
là Meta_1
lớp cha mẹ ( type
là 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
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()
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
type
là 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.
type
thực sự là một metaclass
- một lớp tạo ra các lớp khác. Hầu hết metaclass
là các lớp con của type
. Lớp metaclass
nhận new
lớ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
.
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 đủ.
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.
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 class
thì 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 metaclass
lớ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 đó, metaclass
lớ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'
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.
Đây là một ví dụ khác về những gì nó có thể được sử dụng cho:
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()
Nó metaclass
rấ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.
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à: -
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: -
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 .
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.
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b