ใน Python metaclasses คืออะไรและเราใช้มันเพื่ออะไร
ใน Python metaclasses คืออะไรและเราใช้มันเพื่ออะไร
คำตอบ:
เมตาคลาสเป็นคลาสของคลาส คลาสจะกำหนดพฤติกรรมของอินสแตนซ์ของคลาส (เช่นวัตถุ) ในขณะที่ metaclass กำหนดว่าคลาสจะทำงานอย่างไร คลาสเป็นตัวอย่างของ metaclass
ในขณะที่อยู่ใน Python คุณสามารถใช้ callables ตามอำเภอใจสำหรับ metaclasses (เช่นJerubแสดง) วิธีที่ดีกว่าคือทำให้คลาสจริง type
เป็น metaclass ปกติใน Python type
เป็นคลาสและเป็นประเภทของตัวเอง คุณจะไม่สามารถสร้างบางสิ่งบางอย่างเช่นtype
Python ใหม่ได้ แต่ Python จะโกงนิดหน่อย เพื่อสร้าง metaclass ของคุณเองในหลามจริงๆเพียงแค่คุณต้องการที่จะ type
subclass
เมตาคลาสมักใช้เป็นคลาสโรงงาน เมื่อคุณสร้างวัตถุโดยการเรียกคลาส Python สร้างคลาสใหม่ (เมื่อดำเนินการคำสั่ง 'คลาส') โดยการเรียกเมตาคลาส เมื่อรวมกับวิธีปกติ__init__
และ__new__
เมธอด metaclasses จึงอนุญาตให้คุณทำ 'สิ่งพิเศษ' เมื่อสร้างคลาสเช่นการลงทะเบียนคลาสใหม่ด้วยรีจิสทรีหรือแทนที่คลาสด้วยสิ่งอื่นทั้งหมด
เมื่อclass
คำสั่งถูกดำเนินการ Python จะเรียกใช้งานเนื้อหาของclass
คำสั่งก่อนว่าเป็นบล็อกปกติของรหัส เนมสเปซที่เป็นผลลัพธ์ (dict) เก็บคุณสมบัติของ class-to-be metaclass ถูกกำหนดโดยการดูที่ baseclasses ของ class-to-be (metaclasses ถูกสืบทอด), ที่__metaclass__
แอ็ตทริบิวต์ของ class-to-be (ถ้ามี) หรือ__metaclass__
ตัวแปรโกลบอล เมตาคลาสจะถูกเรียกด้วยชื่อเบสและคุณลักษณะของคลาสเพื่อสร้างอินสแตนซ์
อย่างไรก็ตาม metaclasses จะกำหนดประเภทของชั้นเรียนไม่ใช่เฉพาะโรงงานเท่านั้นดังนั้นคุณสามารถทำอะไรกับมันได้มากขึ้น ตัวอย่างเช่นคุณสามารถกำหนดวิธีการปกติใน metaclass เมตาคลาสของเมธอดเหล่านี้เป็นเหมือนคลาสเมธอดที่สามารถเรียกใช้บนคลาสได้โดยไม่มีอินสแตนซ์ แต่ก็ไม่เหมือนคลาสเมธอดที่ไม่สามารถเรียกใช้บนอินสแตนซ์ของคลาสได้ type.__subclasses__()
เป็นตัวอย่างของวิธีการในtype
metaclass นอกจากนี้คุณยังสามารถกำหนดปกติ 'วิเศษ' วิธีการเช่น__add__
, __iter__
และ__getattr__
เพื่อให้การดำเนินการหรือการเปลี่ยนแปลงวิธีการที่จะทำงานระดับ
นี่คือตัวอย่างโดยรวมของบิตและชิ้นส่วน:
def make_hook(f):
"""Decorator to turn 'foo' method into '__foo__'"""
f.is_hook = 1
return f
class MyType(type):
def __new__(mcls, name, bases, attrs):
if name.startswith('None'):
return None
# Go over attributes and see if they should be renamed.
newattrs = {}
for attrname, attrvalue in attrs.iteritems():
if getattr(attrvalue, 'is_hook', 0):
newattrs['__%s__' % attrname] = attrvalue
else:
newattrs[attrname] = attrvalue
return super(MyType, mcls).__new__(mcls, name, bases, newattrs)
def __init__(self, name, bases, attrs):
super(MyType, self).__init__(name, bases, attrs)
# classregistry.register(self, self.interfaces)
print "Would register class %s now." % self
def __add__(self, other):
class AutoClass(self, other):
pass
return AutoClass
# Alternatively, to autogenerate the classname as well as the class:
# return type(self.__name__ + other.__name__, (self, other), {})
def unregister(self):
# classregistry.unregister(self)
print "Would unregister class %s now." % self
class MyObject:
__metaclass__ = MyType
class NoneSample(MyObject):
pass
# Will print "NoneType None"
print type(NoneSample), repr(NoneSample)
class Example(MyObject):
def __init__(self, value):
self.value = value
@make_hook
def add(self, other):
return self.__class__(self.value + other.value)
# Will unregister the class
Example.unregister()
inst = Example(10)
# Will fail with an AttributeError
#inst.unregister()
print inst + inst
class Sibling(MyObject):
pass
ExampleSibling = Example + Sibling
# ExampleSibling is now a subclass of both Example and Sibling (with no
# content of its own) although it will believe it's called 'AutoClass'
print ExampleSibling
print ExampleSibling.__mro__
__metaclass__
ไม่รองรับ Python 3 ในการใช้ Python 3 class MyObject(metaclass=MyType)
ดูpython.org/dev/peps/pep-3115และคำตอบด้านล่าง
ก่อนที่จะทำความเข้าใจกับ metaclasses คุณจะต้องเรียนรู้หลักใน Python และ Python มีความคิดที่แปลกประหลาดมากเกี่ยวกับคลาสที่ยืมมาจากภาษา Smalltalk
ในภาษาส่วนใหญ่ชั้นเรียนเป็นเพียงส่วนหนึ่งของรหัสที่อธิบายถึงวิธีการสร้างวัตถุ นั่นเป็นความจริงใน Python เช่นกัน:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>
แต่คลาสมีมากกว่านั้นใน Python คลาสเป็นวัตถุด้วย
ใช่วัตถุ
ทันทีที่คุณใช้คำสำคัญclass
Python จะเรียกใช้งานมันและสร้าง OBJECT การเรียนการสอน
>>> class ObjectCreator(object):
... pass
...
สร้างในหน่วยความจำวัตถุที่มีชื่อ "ObjectCreator"
วัตถุนี้ (ชั้น) เป็นตัวเองความสามารถในการสร้างวัตถุ (อินสแตนซ์) และนี่คือเหตุผลที่มันเป็นชั้นเรียน
แต่ก็ยังเป็นวัตถุดังนั้น:
เช่น:
>>> 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>
เนื่องจากคลาสเป็นวัตถุคุณจึงสามารถสร้างมันได้ทันทีเหมือนกับวัตถุใด ๆ
ก่อนอื่นคุณสามารถสร้างคลาสในฟังก์ชันโดยใช้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>
แต่มันไม่ได้เป็นแบบไดนามิกเพราะคุณยังต้องเขียนทั้งชั้นด้วยตัวเอง
เนื่องจากคลาสเป็นวัตถุจึงต้องถูกสร้างโดยบางสิ่ง
เมื่อคุณใช้class
คำสำคัญ Python จะสร้างวัตถุนี้โดยอัตโนมัติ แต่เช่นเดียวกับสิ่งของส่วนใหญ่ใน Python มันทำให้คุณสามารถทำมันเองได้
จำฟังก์ชั่นได้type
หรือไม่ ฟังก์ชั่นเก่าที่ดีที่ช่วยให้คุณรู้ว่าวัตถุชนิดใด:
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>
มันtype
มีความสามารถที่แตกต่างกันโดยสิ้นเชิงมันสามารถสร้างคลาสได้ทันที type
สามารถนำคำอธิบายของคลาสเป็นพารามิเตอร์และส่งคืนคลาสได้
(ฉันรู้ว่ามันงี่เง่าที่ฟังก์ชั่นเดียวกันสามารถมีการใช้งานสองแบบที่แตกต่างกันโดยสิ้นเชิงตามพารามิเตอร์ที่คุณส่งไปมันเป็นปัญหาเนื่องจากความเข้ากันได้ย้อนหลังใน Python)
type
ทำงานด้วยวิธีนี้:
type(name, bases, attrs)
ที่ไหน:
name
: ชื่อของคลาสbases
: tuple ของคลาส parent (สำหรับการสืบทอดสามารถว่างได้)attrs
: พจนานุกรมที่มีชื่อและค่าของแอตทริบิวต์เช่น:
>>> class MyShinyClass(object):
... pass
สามารถสร้างได้ด้วยตนเองด้วยวิธีนี้:
>>> 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>
คุณจะสังเกตเห็นว่าเราใช้ "MyShinyClass" เป็นชื่อของคลาสและเป็นตัวแปรเพื่อเก็บการอ้างอิงคลาส พวกเขาอาจแตกต่างกัน แต่ไม่มีเหตุผลที่จะทำให้สิ่งต่าง ๆ ซับซ้อน
type
ยอมรับพจนานุกรมเพื่อกำหนดคุณสมบัติของชั้นเรียน ดังนั้น:
>>> class Foo(object):
... bar = True
สามารถแปลเป็น:
>>> Foo = type('Foo', (), {'bar':True})
และใช้เป็นคลาสปกติ:
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True
และแน่นอนคุณสามารถสืบทอดจากมันได้ดังนั้น:
>>> class FooChild(Foo):
... pass
อยากจะเป็น:
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar is inherited from Foo
True
ในที่สุดคุณจะต้องเพิ่มวิธีการในชั้นเรียนของคุณ เพียงกำหนดฟังก์ชั่นที่มีลายเซ็นที่ถูกต้องและกำหนดเป็นแอททริบิวต์
>>> 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
และคุณสามารถเพิ่มวิธีการมากขึ้นหลังจากที่คุณสร้างคลาสแบบไดนามิกเช่นเดียวกับการเพิ่มวิธีการไปยังวัตถุคลาสที่สร้างขึ้นตามปกติ
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True
คุณเห็นว่าเรากำลังจะไปไหน: ใน Python คลาสเป็นวัตถุและคุณสามารถสร้างคลาสได้ทันทีแบบไดนามิก
นี่คือสิ่งที่ Python ทำเมื่อคุณใช้คำหลักclass
และทำได้โดยใช้ metaclass
Metaclasses เป็น 'สิ่ง' ที่สร้างคลาส
คุณกำหนดคลาสเพื่อสร้างวัตถุใช่ไหม
แต่เราเรียนรู้ว่าคลาส Python เป็นวัตถุ
metaclasses เป็นสิ่งที่สร้างวัตถุเหล่านี้ พวกมันคือคลาสของคลาสคุณสามารถนึกภาพได้ด้วยวิธีนี้:
MyClass = MetaClass()
my_object = MyClass()
คุณเคยเห็นที่type
ให้คุณทำสิ่งนี้:
MyClass = type('MyClass', (), {})
เป็นเพราะฟังก์ชั่นtype
นั้นเป็นเมตาคลาสจริงๆ type
เป็น metaclass Python ใช้เพื่อสร้างคลาสทั้งหมดที่อยู่เบื้องหลัง
ตอนนี้คุณสงสัยว่าทำไมมันถึงเขียนเป็นตัวพิมพ์เล็กType
ใช่ไหม?
ฉันเดาว่ามันเป็นเรื่องของความสอดคล้องกับstr
คลาสที่สร้างวัตถุสตริงและint
คลาสที่สร้างวัตถุจำนวนเต็ม type
เป็นเพียงคลาสที่สร้างวัตถุคลาส
คุณจะเห็นว่าโดยการตรวจสอบ__class__
คุณลักษณะ
ทุกอย่างและฉันหมายถึงทุกสิ่งเป็นวัตถุใน Python นั่นรวมถึง ints, สตริง, ฟังก์ชั่นและชั้นเรียน พวกเขาทั้งหมดเป็นวัตถุ และทั้งหมดถูกสร้างขึ้นจากชั้นเรียน:
>>> 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'>
ตอนนี้สิ่งที่เป็น__class__
ของใด ๆ__class__
?
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
ดังนั้น metaclass เป็นเพียงสิ่งที่สร้างวัตถุคลาส
คุณสามารถเรียกมันว่า 'โรงงานคลาส' หากคุณต้องการ
type
เป็น metaclass ที่ใช้ใน Python แต่แน่นอนว่าคุณสามารถสร้าง metaclass ของคุณเองได้
__metaclass__
แอตทริบิวต์ใน Python 2 คุณสามารถเพิ่ม__metaclass__
แอตทริบิวต์เมื่อคุณเขียนคลาส (ดูหัวข้อถัดไปสำหรับไวยากรณ์ Python 3):
class Foo(object):
__metaclass__ = something...
[...]
ถ้าคุณทำเช่นนั้นงูใหญ่จะใช้ metaclass Foo
เพื่อสร้างชั้นเรียน
ระวังมันยุ่งยาก
คุณเขียนclass Foo(object)
ก่อน แต่คลาสอ็อบเจ็กต์Foo
ยังไม่ได้สร้างในหน่วยความจำ
Python จะค้นหา__metaclass__
คำจำกัดความของคลาส Foo
หากพบก็จะใช้มันเพื่อสร้างชั้นวัตถุ ถ้าไม่มีก็จะใช้
type
ในการสร้างชั้นเรียน
อ่านหลาย ๆ ครั้ง
เมื่อคุณทำ:
class Foo(Bar):
pass
Python ทำสิ่งต่อไปนี้:
มี__metaclass__
แอตทริบิวต์Foo
หรือไม่
ถ้าใช่สร้างวัตถุในหน่วยความจำชั้นเรียน (ฉันกล่าวว่าวัตถุชั้น, อยู่กับฉันที่นี่) ที่มีชื่อโดยใช้สิ่งที่อยู่ในFoo
__metaclass__
ถ้า Python หาไม่เจอ__metaclass__
มันจะมองหา__metaclass__
ที่ MODULE level และพยายามทำแบบเดียวกัน (แต่สำหรับคลาสที่ไม่ได้รับสิ่งใดโดยเฉพาะคลาสแบบเก่า)
จากนั้นหากไม่พบสิ่งใด__metaclass__
เลยมันจะใช้เมตาคลาสBar
ของตัวเอง (ซึ่งเป็นพาเรนต์แรก) (ซึ่งอาจเป็นค่าเริ่มต้นtype
) เพื่อสร้างคลาสวัตถุ
ระวังที่นี่ว่า__metaclass__
จะไม่ได้รับการถ่ายทอดแอตทริบิวต์, metaclass ของผู้ปกครอง ( Bar.__class__
) จะเป็น หากBar
ใช้__metaclass__
แอตทริบิวต์ที่สร้างBar
ด้วยtype()
(และไม่type.__new__()
) คลาสย่อยจะไม่รับการทำงานนั้น
ตอนนี้คำถามใหญ่ก็คือคุณจะใส่__metaclass__
อะไรลงไป
คำตอบคือ: สิ่งที่สามารถสร้างคลาสได้
และสามารถสร้างชั้นเรียนได้อย่างไร type
หรือสิ่งใดก็ตามที่ subclasses หรือใช้มัน
ไวยากรณ์ในการตั้งค่า metaclass มีการเปลี่ยนแปลงใน Python 3:
class Foo(object, metaclass=something):
...
เช่น__metaclass__
ไม่มีการใช้งานแอตทริบิวต์อีกต่อไปเพื่อสนับสนุนอาร์กิวเมนต์ของคำหลักในรายการของคลาสพื้นฐาน
พฤติกรรมของ metaclasses แต่อยู่ส่วนใหญ่เหมือนกัน
สิ่งหนึ่งที่เพิ่มให้กับ metaclasses ใน python 3 ก็คือคุณสามารถส่งแอตทริบิวต์เป็นอาร์กิวเมนต์คำหลักไปยัง metaclass ได้เช่น:
class Foo(object, metaclass=something, kwarg1=value1, kwarg2=value2):
...
อ่านหัวข้อด้านล่างเพื่อดูว่าหลามจัดการอย่างไร
วัตถุประสงค์หลักของ metaclass คือการเปลี่ยนคลาสโดยอัตโนมัติเมื่อสร้างขึ้น
คุณมักจะทำสิ่งนี้สำหรับ API ที่คุณต้องการสร้างคลาสที่ตรงกับบริบทปัจจุบัน
ลองนึกภาพตัวอย่างโง่ ๆ ที่คุณตัดสินใจว่าคลาสทั้งหมดในโมดูลของคุณควรมีคุณลักษณะของพวกเขาเขียนเป็นตัวพิมพ์ใหญ่ มีหลายวิธีในการทำเช่นนี้ แต่วิธีหนึ่งคือการตั้งค่า__metaclass__
ที่ระดับโมดูล
ด้วยวิธีนี้คลาสทั้งหมดของโมดูลนี้จะถูกสร้างขึ้นโดยใช้เมตาคลาสนี้และเราต้องบอกเมทาคลาสให้เปลี่ยนแอตทริบิวต์ทั้งหมดเป็นตัวพิมพ์ใหญ่
โชคดีที่__metaclass__
สามารถเรียกได้ว่าจริง ๆ แล้วมันไม่จำเป็นต้องเป็นคลาสที่เป็นทางการ (ฉันรู้ว่าบางสิ่งที่มี 'คลาส' ในชื่อไม่จำเป็นต้องเป็นคลาสรูปที่ไป ... แต่มันมีประโยชน์)
ดังนั้นเราจะเริ่มต้นด้วยตัวอย่างง่ายๆโดยใช้ฟังก์ชั่น
# 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'
ตรวจสอบกัน:
>>> hasattr(Foo, 'bar')
False
>>> hasattr(Foo, 'BAR')
True
>>> Foo.BAR
'bip'
ทีนี้ลองทำแบบเดียวกัน แต่ใช้คลาสจริงสำหรับ 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_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)
ลองเขียนข้างบนอีกครั้ง แต่ด้วยชื่อตัวแปรที่สั้นและสมจริงกว่าตอนนี้เรารู้แล้วว่าพวกมันหมายถึงอะไร:
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)
cls
คุณอาจสังเกตเห็นอาร์กิวเมนต์พิเศษ ไม่มีอะไรพิเศษเกี่ยวกับมัน: __new__
รับคลาสที่กำหนดไว้เสมอเป็นพารามิเตอร์แรก เช่นเดียวกับที่คุณมีself
สำหรับวิธีการทั่วไปที่ได้รับอินสแตนซ์เป็นพารามิเตอร์แรกหรือคลาสที่กำหนดสำหรับวิธีการเรียน
แต่นี่ไม่ใช่ OOP ที่เหมาะสม พวกเราเรียกร้องโดยตรงและเราจะไม่เอาชนะหรือโทรของผู้ปกครองtype
__new__
มาทำสิ่งนั้นแทน:
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)
เราสามารถทำให้มันสะอาดยิ่งขึ้นโดยใช้super
ซึ่งจะทำให้การรับมรดกง่ายขึ้น (เพราะใช่คุณสามารถมี metaclasses สืบทอดจาก metaclasses สืบทอดจากประเภท):
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)
โอ้และในงูหลาม 3 ถ้าคุณโทรด้วยอาร์กิวเมนต์คำหลักเช่นนี้:
class Foo(object, metaclass=MyMetaclass, kwarg1=value1):
...
มันแปลสิ่งนี้ใน metaclass เพื่อใช้งาน:
class MyMetaclass(type):
def __new__(cls, clsname, bases, dct, kwargs1=default):
...
แค่นั้นแหละ. ไม่มีอะไรเพิ่มเติมเกี่ยวกับ metaclasses
เหตุผลที่อยู่เบื้องหลังความซับซ้อนของรหัสที่ใช้ metaclasses ไม่ใช่เพราะ metaclasses เป็นเพราะคุณมักจะใช้ metaclasses ในการทำสิ่งที่บิดเบี้ยวโดยอาศัยวิปัสสนาจัดการวิปัสสนา vars เช่น__dict__
ฯลฯ
จริง ๆ แล้ว metaclasses มีประโยชน์อย่างยิ่งในการทำเวทย์มนตร์ดำและดังนั้นสิ่งที่ซับซ้อน แต่ด้วยตัวเองมันง่าย:
เนื่องจาก__metaclass__
สามารถยอมรับ callable ใด ๆ ทำไมคุณจะใช้คลาสเนื่องจากเห็นได้ชัดว่าซับซ้อนมากขึ้น
มีสาเหตุหลายประการ:
UpperAttrMetaclass(type)
คุณจะรู้ว่าสิ่งที่จะทำตาม__new__
, และ__init__
__call__
ซึ่งจะช่วยให้คุณทำสิ่งต่าง ๆ แม้ว่าโดยปกติคุณสามารถทำได้ทั้งหมดใน__new__
บางคนก็ใช้งานได้สะดวก__init__
กว่าตอนนี้คำถามใหญ่ ทำไมคุณถึงใช้คุณสมบัติที่ทำให้เกิดข้อผิดพลาดที่คลุมเครือ
ปกติแล้วคุณจะไม่:
Metaclasses เป็นเวทย์มนตร์ที่ผู้ใช้ 99% ไม่ควรกังวล หากคุณสงสัยว่าคุณต้องการพวกเขาหรือไม่คุณไม่ (คนที่ต้องการให้พวกเขารู้จริง ๆ ว่าพวกเขาต้องการพวกเขาและไม่ต้องการคำอธิบายเกี่ยวกับสาเหตุ)
Python Guru Tim Peters
กรณีการใช้งานหลักสำหรับ metaclass กำลังสร้าง API ตัวอย่างทั่วไปของเรื่องนี้คือ Django ORM อนุญาตให้คุณกำหนดบางอย่างเช่นนี้:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
แต่ถ้าคุณทำสิ่งนี้:
person = Person(name='bob', age='35')
print(person.age)
มันจะไม่ส่งคืนIntegerField
วัตถุ มันจะส่งคืนint
และยังสามารถนำโดยตรงจากฐานข้อมูล
สิ่งนี้เป็นไปได้เพราะmodels.Model
กำหนด__metaclass__
และใช้เวทย์มนตร์ที่จะเปลี่ยนคำสั่งที่Person
คุณเพิ่งกำหนดด้วยคำสั่งง่ายๆให้กลายเป็นเบ็ดซับซ้อนไปยังเขตข้อมูลฐานข้อมูล
Django ทำให้บางอย่างดูซับซ้อนโดยเปิดเผย API ง่าย ๆ และใช้ metaclasses สร้างรหัสจาก API นี้เพื่อทำงานจริงที่อยู่เบื้องหลัง
ก่อนอื่นคุณจะรู้ว่าคลาสเป็นวัตถุที่สามารถสร้างอินสแตนซ์ได้
ในความเป็นจริงแล้วคลาสเป็นกรณีของตัวเอง ของ metaclasses
>>> class Foo(object): pass
>>> id(Foo)
142630324
ทุกอย่างเป็นวัตถุใน Python และเป็นอินสแตนซ์ของคลาสหรืออินสแตนซ์ของ metaclasses
type
ยกเว้น
type
เป็นจริง metaclass ของตัวเอง นี่ไม่ใช่สิ่งที่คุณสามารถทำซ้ำใน Python บริสุทธิ์และทำได้โดยการโกงเล็กน้อยในระดับการใช้งาน
ประการที่สอง metaclasses มีความซับซ้อน คุณอาจไม่ต้องการใช้มันสำหรับการเปลี่ยนคลาสอย่างง่าย ๆ คุณสามารถเปลี่ยนคลาสได้โดยใช้สองเทคนิคที่แตกต่างกัน:
99% ของเวลาที่คุณต้องการเปลี่ยนคลาสคุณจะดีขึ้นเมื่อใช้สิ่งเหล่านี้
แต่ 98% ของเวลาคุณไม่จำเป็นต้องเปลี่ยนชั้นเรียนเลย
models.Model
นั้นไม่ได้ใช้__metaclass__
แต่เป็นการclass Model(metaclass=ModelBase):
อ้างอิงModelBase
คลาสที่ทำเวทเวทมนตร์ดังกล่าวข้างต้น โพสต์ที่ยอดเยี่ยม! นี่คือแหล่งที่มาของ Django: github.com/django/django/blob/master/django/db/models/ …
__metaclass__
แอตทริบิวต์จะไม่ถูกสืบทอด metaclass ของ parent ( Bar.__class__
) จะเป็น หากBar
ใช้__metaclass__
คุณลักษณะที่สร้างขึ้นBar
ด้วยtype()
(และไม่ใช่type.__new__()
) คลาสย่อยจะไม่รับพฤติกรรมนั้น >> - คุณ / ใครสักคนช่วยอธิบายเนื้อเรื่องนี้ให้ลึกขึ้นหน่อยได้ไหม?
Now you wonder why the heck is it written in lowercase, and not Type?
- ดีเพราะมีการใช้งานใน C - เป็นเหตุผลเดียวกันที่ defaultdict เป็นตัวพิมพ์เล็กในขณะที่ OrderedDict (ใน python 2) เป็น CamelCase ปกติ
หมายเหตุคำตอบนี้มีไว้สำหรับ Python 2.x ตามที่เขียนในปี 2008 metaclasses จะแตกต่างกันเล็กน้อยใน 3.x
Metaclasses เป็นซอสลับที่ทำให้ 'คลาส' ทำงาน metaclass เริ่มต้นสำหรับวัตถุสไตล์ใหม่ที่เรียกว่า 'type'
class type(object)
| type(object) -> the object's type
| type(name, bases, dict) -> a new type
Metaclasses ใช้เวลา 3 args ' name ', ' ฐาน ' และ ' dict '
นี่คือที่ลับเริ่มต้น ค้นหาตำแหน่งฐานและ dict ที่มาจากการกำหนดคลาสตัวอย่าง
class ThisIsTheName(Bases, Are, Here):
All_the_code_here
def doesIs(create, a):
dict
ให้กำหนด metaclass ที่จะแสดงให้เห็นว่า ' class: ' เรียกมันอย่างไร
def test_metaclass(name, bases, dict):
print 'The Class Name is', name
print 'The Class Bases are', bases
print 'The dict has', len(dict), 'elems, the keys are', dict.keys()
return "yellow"
class TestName(object, None, int, 1):
__metaclass__ = test_metaclass
foo = 1
def baz(self, arr):
pass
print 'TestName = ', repr(TestName)
# output =>
The Class Name is TestName
The Class Bases are (<type 'object'>, None, <type 'int'>, 1)
The dict has 4 elems, the keys are ['baz', '__module__', 'foo', '__metaclass__']
TestName = 'yellow'
และตอนนี้ตัวอย่างที่หมายถึงบางสิ่งบางอย่างสิ่งนี้จะทำให้ตัวแปรในรายการ "คุณสมบัติ" ตั้งค่าในชั้นเรียนโดยอัตโนมัติและตั้งค่าเป็นไม่มี
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
หมายเหตุว่าพฤติกรรมมายากลที่Initialised
กำไรโดยมี metaclass init_attributes
ไม่ได้ส่งผ่านไปยัง subclass Initialised
ของ
นี่คือตัวอย่างที่ชัดเจนยิ่งขึ้นแสดงให้เห็นว่าคุณสามารถ subclass 'type' เพื่อสร้าง metaclass ที่ดำเนินการได้อย่างไรเมื่อสร้างคลาส มันค่อนข้างยุ่งยาก:
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
คนอื่น ๆ อธิบายว่า metaclasses ทำงานอย่างไรและเข้ากันได้อย่างไรกับระบบประเภท Python นี่คือตัวอย่างของสิ่งที่พวกเขาสามารถใช้สำหรับ ในกรอบการทดสอบที่ฉันเขียนฉันต้องการติดตามลำดับที่มีการกำหนดชั้นเรียนเพื่อที่ฉันจะได้สร้างอินสแตนซ์ของพวกเขาในลำดับนี้ในภายหลัง ฉันพบว่าทำได้ง่ายที่สุดโดยใช้ 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
อะไรก็ตามที่เป็นคลาสย่อยจากMyType
นั้นจะได้รับแอตทริบิวต์ class _order
ที่บันทึกลำดับที่กำหนดคลาส
__init__(self)
กล่าวว่าtype(self)._order = MyBase.counter; MyBase.counter += 1
?
หนึ่งการใช้งานสำหรับ metaclasses คือการเพิ่มคุณสมบัติและวิธีการใหม่ให้กับอินสแตนซ์โดยอัตโนมัติ
ตัวอย่างเช่นถ้าคุณดูที่รุ่น Djangoนิยามของพวกเขาจะดูสับสนเล็กน้อย ดูเหมือนว่าคุณจะกำหนดคุณสมบัติคลาสเท่านั้น:
class Person(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
อย่างไรก็ตามในขณะใช้งานจริงวัตถุบุคคลนั้นก็จะเต็มไปด้วยวิธีการที่มีประโยชน์ทุกประเภท ดูแหล่งที่มาของ metaclassery ที่น่าอัศจรรย์
ฉันคิดว่าคำแนะนำ ONLamp สำหรับการเขียนโปรแกรม metaclass นั้นเขียนได้ดีและให้คำแนะนำที่ดีกับหัวข้อแม้ว่าจะมีอายุหลายปีแล้วก็ตาม
http://www.onlamp.com/pub/a/python/2003/04/17/metaclasses.html (เก็บถาวรที่https://web.archive.org/web/20080206005253/http://www.onlamp com / pub / a / python / 2003/04/17 / metaclasses.html )
กล่าวโดยย่อ: คลาสเป็นพิมพ์เขียวสำหรับการสร้างอินสแตนซ์ metaclass เป็นพิมพ์เขียวสำหรับการสร้างคลาส จะเห็นได้ง่ายว่าในคลาส Python จะต้องเป็นออบเจ็กต์ชั้นหนึ่งด้วยเพื่อเปิดใช้งานลักษณะการทำงานนี้
ผมไม่เคยเขียนหนึ่งตัวเอง แต่ฉันคิดว่าหนึ่งของการใช้ที่อร่อยที่สุดของ metaclasses สามารถมองเห็นได้ในกรอบ Django คลาสโมเดลใช้เมธอดคลาสเพื่อเปิดใช้งานสไตล์การประกาศของการเขียนโมเดลใหม่หรือคลาสคลาส ในขณะที่ metaclass กำลังสร้างคลาสสมาชิกทุกคนจะมีความเป็นไปได้ในการปรับแต่งคลาสเอง
สิ่งที่เหลือพูดคือ: ถ้าคุณไม่รู้ว่า metaclasses คืออะไรความน่าจะเป็นที่คุณไม่ต้องการคือ 99%
metaclasses คืออะไร? คุณใช้มันเพื่ออะไร
TLDR: metaclass instantiates และกำหนดพฤติกรรมสำหรับคลาสเช่นเดียวกับคลาส instantiates และกำหนดพฤติกรรมสำหรับอินสแตนซ์
pseudocode:
>>> Class(...)
instance
ข้างต้นควรดูคุ้นเคย แล้วมันClass
มาจากไหน? มันเป็นตัวอย่างของ metaclass (เช่น pseudocode):
>>> Metaclass(...)
Class
ในโค้ดจริงเราสามารถส่ง metaclass เริ่มต้นtype
ได้ทุกสิ่งที่เราต้องการในการยกตัวอย่างคลาสและเราได้รับคลาส:
>>> type('Foo', (object,), {}) # requires a name, bases, and a namespace
<class '__main__.Foo'>
คลาสคืออินสแตนซ์เนื่องจาก metaclass เป็นคลาส
เมื่อเรายกตัวอย่างวัตถุเราจะได้รับอินสแตนซ์:
>>> object() # instantiation of class
<object object at 0x7f9069b4e0b0> # instance
เมื่อเรากำหนดคลาสอย่างชัดเจนด้วย metaclass เริ่มต้นtype
เราจะยกตัวอย่าง:
>>> type('Object', (object,), {}) # instantiation of metaclass
<class '__main__.Object'> # instance
อีกทางหนึ่งคลาสเป็นตัวอย่างของ metaclass:
>>> isinstance(object, type)
True
วิธีที่สามเมตาคลาสคือคลาสของคลาส
>>> type(object) == type
True
>>> object.__class__
<class 'type'>
เมื่อคุณเขียนคำจำกัดความของคลาสแล้ว Python จะเรียกใช้มันจะใช้ metaclass เพื่อสร้างอินสแตนซ์ของออบเจ็กต์คลาส (ซึ่งจะใช้เพื่อสร้างอินสแตนซ์ของคลาสนั้น)
เช่นเดียวกับที่เราสามารถใช้คำจำกัดความของคลาสเพื่อเปลี่ยนวิธีการทำงานของอินสแตนซ์ของวัตถุที่กำหนดเองเราสามารถใช้คำจำกัดความของคลาส metaclass เพื่อเปลี่ยนวิธีการทำงานของวัตถุคลาส
พวกเขาจะใช้อะไรได้บ้าง? จากเอกสาร :
การใช้ศักยภาพสำหรับ metaclasses นั้นไร้ขอบเขต แนวคิดบางอย่างที่สำรวจรวมถึงการบันทึกการตรวจสอบอินเตอร์เฟสการมอบหมายอัตโนมัติการสร้างคุณสมบัติอัตโนมัติพร็อกซีเฟรมเวิร์กและการล็อค / การซิงโครไนซ์ทรัพยากรอัตโนมัติ
อย่างไรก็ตามโดยทั่วไปผู้ใช้ควรหลีกเลี่ยงการใช้ metaclasses เว้นแต่จำเป็นจริงๆ
เมื่อคุณเขียนคำจำกัดความของคลาสเช่นเช่นนี้
class Foo(object):
'demo'
คุณยกตัวอย่างวัตถุคลาส
>>> Foo
<class '__main__.Foo'>
>>> isinstance(Foo, type), isinstance(Foo, object)
(True, True)
เหมือนกับการเรียกใช้ฟังก์ชันที่type
มีอาร์กิวเมนต์ที่เหมาะสมและกำหนดผลลัพธ์ให้กับตัวแปรของชื่อนั้น:
name = 'Foo'
bases = (object,)
namespace = {'__doc__': 'demo'}
Foo = type(name, bases, namespace)
โปรดทราบบางสิ่งจะถูกเพิ่มเข้า__dict__
ในเนมสเปซโดยอัตโนมัติ:
>>> Foo.__dict__
dict_proxy({'__dict__': <attribute '__dict__' of 'Foo' objects>,
'__module__': '__main__', '__weakref__': <attribute '__weakref__'
of 'Foo' objects>, '__doc__': 'demo'})
metaclasstype
ของวัตถุที่เราสร้างขึ้นในทั้งสองกรณีคือ
(หมายเหตุด้านข้างเกี่ยวกับเนื้อหาของคลาส__dict__
: __module__
มีเพราะคลาสจะต้องทราบว่าถูกกำหนดไว้ที่ใด __dict__
และ__weakref__
อยู่ที่นั่นเพราะเราไม่ได้กำหนด__slots__
- ถ้าเรากำหนด__slots__
เราจะประหยัดพื้นที่ในบิตในกรณีเช่น เราสามารถไม่อนุญาต__dict__
และ__weakref__
ยกเว้นพวกเขาตัวอย่างเช่น:
>>> Baz = type('Bar', (object,), {'__doc__': 'demo', '__slots__': ()})
>>> Baz.__dict__
mappingproxy({'__doc__': 'demo', '__slots__': (), '__module__': '__main__'})
... แต่ฉันพูดนอกเรื่อง)
type
ความหมายเหมือนกับคลาสอื่น ๆ :นี่คือค่าเริ่มต้น__repr__
ของคลาส:
>>> Foo
<class '__main__.Foo'>
__repr__
หนึ่งในสิ่งที่มีคุณค่ามากที่สุดที่เราสามารถทำได้โดยเริ่มต้นในการเขียนวัตถุงูใหญ่คือการให้ด้วยดี เมื่อเราเรียกว่าhelp(repr)
เราได้เรียนรู้ว่ามีการทดสอบที่ดีสำหรับการ__repr__
ที่ยังต้องมีการทดสอบเพื่อความเท่าเทียมกัน obj == eval(repr(obj))
- การใช้งานอย่างง่ายดังต่อไปนี้__repr__
และ__eq__
สำหรับอินสแตนซ์ของคลาสประเภทคลาสของเราทำให้เรามีการสาธิตที่อาจปรับปรุงให้ดีขึ้นเกี่ยวกับค่าเริ่มต้น__repr__
ของคลาส:
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__)
ดังนั้นเมื่อเราสร้างวัตถุด้วย metaclass นี้__repr__
เสียงสะท้อนบนบรรทัดคำสั่งจะให้ภาพที่น่าเกลียดน้อยกว่าค่าเริ่มต้น:
>>> class Bar(object): pass
>>> Baz = Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
>>> Baz
Type('Baz', (Foo, Bar,), {'__module__': '__main__', '__doc__': None})
ด้วยการ__repr__
กำหนดที่ดีสำหรับอินสแตนซ์ของคลาสเรามีความสามารถที่แข็งแกร่งในการดีบักโค้ดของเรา อย่างไรก็ตามการตรวจสอบเพิ่มเติมด้วยeval(repr(Class))
ไม่น่าเป็นไปได้มาก (เนื่องจากฟังก์ชั่นจะค่อนข้างเป็นไปไม่ได้ที่จะประเมินจากค่าเริ่มต้น__repr__
)
__prepare__
เนมสเปซตัวอย่างเช่นหากเราต้องการทราบว่าวิธีการใดของคลาสที่สร้างขึ้นเราสามารถจัดเตรียม dict ที่สั่งซื้อเป็นเนมสเปซของคลาส เราจะทำเช่นนี้__prepare__
ซึ่งจะคืนค่าเนมสเปซสำหรับคลาสถ้ามันถูกใช้ใน 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
และการใช้งาน:
class OrderedMethodsObject(object, metaclass=OrderedType):
def method1(self): pass
def method2(self): pass
def method3(self): pass
def method4(self): pass
และตอนนี้เรามีบันทึกลำดับที่สร้างวิธีการเหล่านี้ (และคุณสมบัติคลาสอื่น ๆ ):
>>> OrderedMethodsObject.members
('__module__', '__qualname__', 'method1', 'method2', 'method3', 'method4')
หมายเหตุตัวอย่างนี้ดัดแปลงจากเอกสารประกอบ - enumใหม่ในไลบรารีมาตรฐานทำสิ่งนี้
ดังนั้นสิ่งที่เราทำคือยกตัวอย่าง metaclass โดยการสร้างคลาส เราสามารถปฏิบัติต่อเมตาคลาสได้เหมือนชั้นเรียนอื่น ๆ มันมีวิธีการแก้ปัญหาการสั่งซื้อ:
>>> inspect.getmro(OrderedType)
(<class '__main__.OrderedType'>, <class '__main__.Type'>, <class 'type'>, <class 'object'>)
และมันมีค่าประมาณที่ถูกต้องrepr
(ซึ่งเราไม่สามารถประเมินได้อีกต่อไปหากเราไม่สามารถหาวิธีที่จะเป็นตัวแทนของฟังก์ชันของเราได้):
>>> 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>})
Python 3 อัพเดท
มีวิธีการสำคัญสองวิธี (ในจุดนี้) ใน metaclass:
__prepare__
และ__new__
__prepare__
ช่วยให้คุณสามารถกำหนดแผนที่ที่กำหนดเอง (เช่นOrderedDict
) เพื่อใช้เป็นเนมสเปซในขณะที่กำลังสร้างคลาส คุณต้องส่งคืนอินสแตนซ์ของเนมสเปซที่คุณเลือก หากคุณไม่ได้ใช้งาน__prepare__
ปกติdict
จะใช้
__new__
รับผิดชอบในการสร้าง / ดัดแปลงชั้นเรียนจริง
metaclass ที่ไม่ต้องทำอะไรเพิ่มเติมต้องการ:
class Meta(type):
def __prepare__(metaclass, cls, bases):
return dict()
def __new__(metacls, cls, bases, clsdict):
return super().__new__(metacls, cls, bases, clsdict)
ตัวอย่างง่ายๆ:
สมมติว่าคุณต้องการบางรหัสตรวจสอบง่ายที่จะทำงานบนแอตทริบิวต์ของคุณ - เหมือนว่ามันจะต้องเป็นหรือint
str
คลาสของคุณจะมีลักษณะดังนี้:
class Person:
weight = ValidateType('weight', int)
age = ValidateType('age', int)
name = ValidateType('name', str)
อย่างที่คุณเห็นคุณต้องทำซ้ำชื่อของแอตทริบิวต์สองครั้ง สิ่งนี้ทำให้พิมพ์ผิดไปได้พร้อมกับข้อบกพร่องที่น่ารำคาญ
metaclass ธรรมดาสามารถจัดการกับปัญหานั้นได้:
class Person(metaclass=Validator):
weight = ValidateType(int)
age = ValidateType(int)
name = ValidateType(str)
นี่คือลักษณะที่ metaclass จะดูเหมือน (ไม่ได้ใช้__prepare__
เนื่องจากไม่จำเป็น):
class Validator(type):
def __new__(metacls, cls, bases, clsdict):
# search clsdict looking for ValidateType descriptors
for name, attr in clsdict.items():
if isinstance(attr, ValidateType):
attr.name = name
attr.attr = '_' + name
# create final class and return it
return super().__new__(metacls, cls, bases, clsdict)
ตัวอย่างการรันของ:
p = Person()
p.weight = 9
print(p.weight)
p.weight = '9'
ผลิต:
9
Traceback (most recent call last):
File "simple_meta.py", line 36, in <module>
p.weight = '9'
File "simple_meta.py", line 24, in __set__
(self.name, self.type, value))
TypeError: weight must be of type(s) <class 'int'> (got '9')
หมายเหตุ : ตัวอย่างนี้ง่ายพอที่จะทำได้ด้วยเครื่องมือตกแต่งชั้นเรียน แต่สันนิษฐานว่า metaclass จริงจะทำอะไรได้มากกว่า
คลาส 'ValidateType' สำหรับการอ้างอิง:
class ValidateType:
def __init__(self, type):
self.name = None # will be set by metaclass
self.attr = None # will be set by metaclass
self.type = type
def __get__(self, inst, cls):
if inst is None:
return self
else:
return inst.__dict__[self.attr]
def __set__(self, inst, value):
if not isinstance(value, self.type):
raise TypeError('%s must be of type(s) %s (got %r)' %
(self.name, self.type, value))
else:
inst.__dict__[self.attr] = value
__set_name__(cls, name)
ใน descriptor ( ValidateType
) เพื่อตั้งชื่อใน descriptor ( self.name
และในกรณีนี้ด้วยself.attr
) สิ่งนี้ถูกเพิ่มเข้ามาโดยไม่จำเป็นต้องดำน้ำใน metaclasses สำหรับกรณีการใช้งานทั่วไปที่เฉพาะเจาะจงนี้ (ดู PEP 487)
__call__()
คลาส' เมธอดเมื่อสร้างอินสแตนซ์ของคลาสหากคุณเคยเขียนโปรแกรม Python มานานกว่าสองสามเดือนคุณจะสะดุดกับรหัสที่มีลักษณะดังนี้:
# define a class
class SomeClass(object):
# ...
# some definition here ...
# ...
# create an instance of it
instance = SomeClass()
# then call the object as if it's a function
result = instance('foo', 'bar')
หลังเป็นไปได้เมื่อคุณใช้__call__()
วิธีการเวทมนต์ในชั้นเรียน
class SomeClass(object):
# ...
# some definition here ...
# ...
def __call__(self, foo, bar):
return bar + foo
__call__()
วิธีการจะเรียกเมื่ออินสแตนซ์ของชั้นใช้เป็น callable แต่ดังที่เราเห็นจากคำตอบก่อนหน้าคลาสนั้นเป็นอินสแตนซ์ของ metaclass ดังนั้นเมื่อเราใช้คลาสเป็น callable (เช่นเมื่อเราสร้างตัวอย่างของคลาส) เราเรียก__call__()
เมธอดคลาสของ metaclass ณ จุดนี้โปรแกรมเมอร์ Python ส่วนใหญ่สับสนเล็กน้อยเพราะได้รับแจ้งว่าเมื่อสร้างอินสแตนซ์เช่นนี้instance = SomeClass()
คุณกำลังเรียก__init__()
ใช้เมธอด บางคนที่เคยขุดบิตลึกรู้ว่าก่อนที่จะมี__init__()
__new__()
ทีนี้วันนี้มีการเปิดเผยความจริงอีกชั้นก่อนที่จะ__new__()
มี metaclass '__call__()
ลองศึกษาวิธีการโทรโซ่จากมุมมองของการสร้างตัวอย่างของชั้นเรียนโดยเฉพาะ
นี่คือ metaclass ที่บันทึกช่วงเวลาก่อนที่อินสแตนซ์จะถูกสร้างขึ้นและช่วงเวลาที่จะส่งคืน
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
นี่คือคลาสที่ใช้ 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__()."
และตอนนี้เรามาสร้างตัวอย่างของ 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.
สังเกตว่ารหัสข้างต้นไม่ได้ทำอะไรมากกว่าการบันทึกงาน แต่ละวิธีมอบหมายงานที่แท้จริงให้กับการนำไปใช้งานของผู้ปกครองจึงทำให้พฤติกรรมเริ่มต้น เนื่องจากtype
เป็นMeta_1
ของผู้ปกครองชั้นเรียน ( type
เป็น metaclass ผู้ปกครองเริ่มต้น) และเมื่อพิจารณาจากลำดับการสั่งซื้อของการส่งออกดังกล่าวข้างต้นขณะนี้เรามีเงื่อนงำเป็นสิ่งที่จะดำเนินการหลอกของtype.__call__()
:
class type:
def __call__(cls, *args, **kwarg):
# ... maybe a few things done to cls here
# then we call __new__() on the class to create an instance
instance = cls.__new__(cls, *args, **kwargs)
# ... maybe a few things done to the instance here
# then we initialize the instance with its __init__() method
instance.__init__(*args, **kwargs)
# ... maybe a few more things done to instance here
# then we return it
return instance
เราจะเห็นได้ว่าเมตาคลาสของ__call__()
เมธอดนั้นเป็นวิธีที่เรียกว่าอันดับแรก จากนั้นก็สร้างได้รับมอบหมายจากอินสแตนซ์ในชั้นเรียนของวิธีการและการเริ่มต้นที่จะอินสแตนซ์ของ__new__()
__init__()
นอกจากนี้ยังเป็นสิ่งที่ส่งคืนอินสแตนซ์ในที่สุด
จากด้านบนมันเกิดขึ้นที่ metaclass ' __call__()
ยังได้รับโอกาสในการตัดสินใจว่าจะเรียกClass_1.__new__()
หรือไม่Class_1.__init__()
ก็จะทำในที่สุด ในระหว่างการดำเนินการมันสามารถคืนวัตถุที่ไม่ได้สัมผัสโดยวิธีใดวิธีหนึ่งเหล่านี้ ยกตัวอย่างวิธีการนี้ให้กับรูปแบบซิงเกิล:
class Meta_2(type):
singletons = {}
def __call__(cls, *args, **kwargs):
if cls in Meta_2.singletons:
# we return the only instance and skip a call to __new__()
# and __init__()
print ("{} singleton returning from Meta_2.__call__(), "
"skipping creation of new instance.".format(cls))
return Meta_2.singletons[cls]
# else if the singleton isn't present we proceed as usual
print "Meta_2.__call__() before creating an instance."
instance = super(Meta_2, cls).__call__(*args, **kwargs)
Meta_2.singletons[cls] = instance
print "Meta_2.__call__() returning new instance."
return instance
class Class_2(object):
__metaclass__ = Meta_2
def __new__(cls, *args, **kwargs):
print "Class_2.__new__() before creating instance."
instance = super(Class_2, cls).__new__(cls)
print "Class_2.__new__() returning instance."
return instance
def __init__(self, *args, **kwargs):
print "entering Class_2.__init__() for initialization."
super(Class_2, self).__init__()
print "exiting Class_2.__init__()."
ลองสังเกตสิ่งที่เกิดขึ้นเมื่อพยายามสร้างวัตถุชนิดซ้ำ ๆ Class_2
a = Class_2()
# Meta_2.__call__() before creating an instance.
# Class_2.__new__() before creating instance.
# Class_2.__new__() returning instance.
# entering Class_2.__init__() for initialization.
# exiting Class_2.__init__().
# Meta_2.__call__() returning new instance.
b = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.
c = Class_2()
# <class '__main__.Class_2'> singleton returning from Meta_2.__call__(), skipping creation of new instance.
a is b is c # True
เมตาคลาสเป็นคลาสที่บอกวิธีสร้างคลาสอื่น (บางอัน)
นี่เป็นกรณีที่ฉันเห็น metaclass เป็นวิธีแก้ปัญหาของฉัน: ฉันมีปัญหาที่ซับซ้อนจริง ๆ ซึ่งอาจได้รับการแก้ไขแตกต่างกัน แต่ฉันเลือกที่จะแก้มันโดยใช้ metaclass เนื่องจากความซับซ้อนจึงเป็นหนึ่งในไม่กี่โมดูลที่ฉันเขียนซึ่งความคิดเห็นในโมดูลเกินจำนวนรหัสที่ถูกเขียน ที่นี่มันคือ ...
#!/usr/bin/env python
# Copyright (C) 2013-2014 Craig Phillips. All rights reserved.
# This requires some explaining. The point of this metaclass excercise is to
# create a static abstract class that is in one way or another, dormant until
# queried. I experimented with creating a singlton on import, but that did
# not quite behave how I wanted it to. See now here, we are creating a class
# called GsyncOptions, that on import, will do nothing except state that its
# class creator is GsyncOptionsType. This means, docopt doesn't parse any
# of the help document, nor does it start processing command line options.
# So importing this module becomes really efficient. The complicated bit
# comes from requiring the GsyncOptions class to be static. By that, I mean
# any property on it, may or may not exist, since they are not statically
# defined; so I can't simply just define the class with a whole bunch of
# properties that are @property @staticmethods.
#
# So here's how it works:
#
# Executing 'from libgsync.options import GsyncOptions' does nothing more
# than load up this module, define the Type and the Class and import them
# into the callers namespace. Simple.
#
# Invoking 'GsyncOptions.debug' for the first time, or any other property
# causes the __metaclass__ __getattr__ method to be called, since the class
# is not instantiated as a class instance yet. The __getattr__ method on
# the type then initialises the class (GsyncOptions) via the __initialiseClass
# method. This is the first and only time the class will actually have its
# dictionary statically populated. The docopt module is invoked to parse the
# usage document and generate command line options from it. These are then
# paired with their defaults and what's in sys.argv. After all that, we
# setup some dynamic properties that could not be defined by their name in
# the usage, before everything is then transplanted onto the actual class
# object (or static class GsyncOptions).
#
# Another piece of magic, is to allow command line options to be set in
# in their native form and be translated into argparse style properties.
#
# Finally, the GsyncListOptions class is actually where the options are
# stored. This only acts as a mechanism for storing options as lists, to
# allow aggregation of duplicate options or options that can be specified
# multiple times. The __getattr__ call hides this by default, returning the
# last item in a property's list. However, if the entire list is required,
# calling the 'list()' method on the GsyncOptions class, returns a reference
# to the GsyncListOptions class, which contains all of the same properties
# but as lists and without the duplication of having them as both lists and
# static singlton values.
#
# So this actually means that GsyncOptions is actually a static proxy class...
#
# ...And all this is neatly hidden within a closure for safe keeping.
def GetGsyncOptionsType():
class GsyncListOptions(object):
__initialised = False
class GsyncOptionsType(type):
def __initialiseClass(cls):
if GsyncListOptions._GsyncListOptions__initialised: return
from docopt import docopt
from libgsync.options import doc
from libgsync import __version__
options = docopt(
doc.__doc__ % __version__,
version = __version__,
options_first = True
)
paths = options.pop('<path>', None)
setattr(cls, "destination_path", paths.pop() if paths else None)
setattr(cls, "source_paths", paths)
setattr(cls, "options", options)
for k, v in options.iteritems():
setattr(cls, k, v)
GsyncListOptions._GsyncListOptions__initialised = True
def list(cls):
return GsyncListOptions
def __getattr__(cls, name):
cls.__initialiseClass()
return getattr(GsyncListOptions, name)[-1]
def __setattr__(cls, name, value):
# Substitut option names: --an-option-name for an_option_name
import re
name = re.sub(r'^__', "", re.sub(r'-', "_", name))
listvalue = []
# Ensure value is converted to a list type for GsyncListOptions
if isinstance(value, list):
if value:
listvalue = [] + value
else:
listvalue = [ None ]
else:
listvalue = [ value ]
type.__setattr__(GsyncListOptions, name, listvalue)
# Cleanup this module to prevent tinkering.
import sys
module = sys.modules[__name__]
del module.__dict__['GetGsyncOptionsType']
return GsyncOptionsType
# Our singlton abstract proxy class.
class GsyncOptions(object):
__metaclass__ = GetGsyncOptionsType()
type(obj)
ฟังก์ชั่นที่คุณได้รับชนิดของวัตถุ
type()
ของชั้นเป็นของmetaclass
วิธีใช้ metaclass:
class Foo(object):
__metaclass__ = MyMetaClass
type
เป็น metaclass ของตัวเอง คลาสของคลาสเป็นเมตาคลาส - ร่างกายของคลาสเป็นอาร์กิวเมนต์ที่ส่งผ่านไปยังเมตาคลาสที่ใช้ในการสร้างคลาส
ที่นี่คุณสามารถอ่านเกี่ยวกับวิธีใช้ metaclasses เพื่อปรับแต่งโครงสร้างของคลาส
type
เป็นจริงmetaclass
- คลาสที่สร้างคลาสอื่น ส่วนใหญ่เป็นคลาสย่อยของmetaclass
รับชั้นเป็นอาร์กิวเมนต์ครั้งแรกและให้สามารถเข้าถึงวัตถุชั้นโดยมีรายละเอียดดังกล่าวข้างล่าง:type
metaclass
new
>>> class MetaClass(type):
... def __init__(cls, name, bases, attrs):
... print ('class name: %s' %name )
... print ('Defining class %s' %cls)
... print('Bases %s: ' %bases)
... print('Attributes')
... for (name, value) in attrs.items():
... print ('%s :%r' %(name, value))
...
>>> class NewClass(object, metaclass=MetaClass):
... get_choch='dairy'
...
class name: NewClass
Bases <class 'object'>:
Defining class <class 'NewClass'>
get_choch :'dairy'
__module__ :'builtins'
__qualname__ :'NewClass'
Note:
ขอให้สังเกตว่าชั้นไม่ได้ยกตัวอย่างในเวลาใดก็ได้ metaclass
การกระทำที่เรียบง่ายของการสร้างชั้นเรียกการดำเนินการของ
คลาส Python เป็นวัตถุของเมตาดาต้า
เมตาคลาสเริ่มต้นซึ่งจะถูกนำไปใช้เมื่อคุณกำหนดคลาสเป็น:
class foo:
...
เมตาคลาสใช้เพื่อใช้กฎบางอย่างกับชุดคลาสทั้งหมด ตัวอย่างเช่นสมมติว่าคุณกำลังสร้าง ORM เพื่อเข้าถึงฐานข้อมูลและคุณต้องการให้ระเบียนจากแต่ละตารางเป็นคลาสที่แมปกับตารางนั้น (โดยยึดตามเขตข้อมูลกฎธุรกิจ ฯลฯ ) การใช้ metaclass ที่เป็นไปได้ ตัวอย่างเช่นตรรกะกลุ่มการเชื่อมต่อซึ่งใช้ร่วมกันโดยทุกคลาสของการบันทึกจากตารางทั้งหมด การใช้งานอื่นคือตรรกะเพื่อสนับสนุนคีย์ต่างประเทศซึ่งเกี่ยวข้องกับการบันทึกหลายคลาส
เมื่อคุณกำหนด metaclass คุณเป็นประเภท subclass และสามารถแทนที่วิธีเวทย์มนตร์ต่อไปนี้เพื่อแทรกตรรกะของคุณ
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
อย่างไรก็ตามทั้งสองเป็นตะขอที่ใช้กันมากที่สุด metaclassing นั้นทรงพลังและเหนือสิ่งอื่นใดคือรายการของการใช้งาน metaclassing อย่างละเอียด
ฟังก์ชั่น type () สามารถส่งคืนชนิดของวัตถุหรือสร้างชนิดใหม่
ตัวอย่างเช่นเราสามารถสร้างคลาส Hi ด้วยฟังก์ชัน type () และไม่จำเป็นต้องใช้วิธีนี้กับคลาส Hi (วัตถุ):
def func(self, name='mike'):
print('Hi, %s.' % name)
Hi = type('Hi', (object,), dict(hi=func))
h = Hi()
h.hi()
Hi, mike.
type(Hi)
type
type(h)
__main__.Hi
นอกเหนือจากการใช้ type () เพื่อสร้างคลาสแบบไดนามิกคุณสามารถควบคุมพฤติกรรมการสร้างคลาสและใช้เมตาคลาสได้
ตามโมเดลวัตถุ Python คลาสเป็นวัตถุดังนั้นคลาสจะต้องเป็นอินสแตนซ์ของคลาสอื่นที่แน่นอน โดยค่าเริ่มต้นคลาส Python เป็นอินสแตนซ์ของคลาสชนิด นั่นคือ type คือ metaclass ของคลาสที่มีอยู่แล้วภายในและ metaclass ของคลาสที่ผู้ใช้กำหนดเอง
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
class CustomList(list, metaclass=ListMetaclass):
pass
lst = CustomList()
lst.add('custom_list_1')
lst.add('custom_list_2')
lst
['custom_list_1', 'custom_list_2']
Magic จะมีผลเมื่อเราส่งอาร์กิวเมนต์ของคีย์เวิร์ดใน metaclass ซึ่งระบุว่า Python interpreter เพื่อสร้าง CustomList ผ่าน ListMetaclass ใหม่ () ณ จุดนี้เราสามารถแก้ไขคำจำกัดความของคลาสตัวอย่างเช่นและเพิ่มวิธีการใหม่แล้วส่งกลับคำจำกัดความที่แก้ไขแล้ว
นอกเหนือจากคำตอบที่เผยแพร่ฉันสามารถพูดได้ว่าการmetaclass
กำหนดพฤติกรรมสำหรับชั้นเรียน ดังนั้นคุณสามารถกำหนด metaclass ของคุณได้อย่างชัดเจน เมื่อใดก็ตามที่ได้รับหลามคำหลักแล้วก็เริ่มค้นหาclass
metaclass
หากไม่พบ - ประเภท metaclass เริ่มต้นจะใช้ในการสร้างวัตถุของชั้นเรียน การใช้__metaclass__
คุณสมบัตินี้คุณสามารถกำหนดmetaclass
คลาสของคุณได้:
class MyClass:
__metaclass__ = type
# write here other method
# write here one more method
print(MyClass.__metaclass__)
มันจะสร้างผลลัพธ์เช่นนี้:
class 'type'
และแน่นอนคุณสามารถสร้างของคุณเองmetaclass
เพื่อกำหนดพฤติกรรมของคลาสใด ๆ ที่สร้างขึ้นโดยใช้คลาสของคุณ
สำหรับการทำเช่นนั้นmetaclass
คลาสประเภทเริ่มต้นของคุณจะต้องสืบทอดเนื่องจากนี่คือหลักmetaclass
:
class MyMetaClass(type):
__metaclass__ = type
# you can write here any behaviour you want
class MyTestClass:
__metaclass__ = MyMetaClass
Obj = MyTestClass()
print(Obj.__metaclass__)
print(MyMetaClass.__metaclass__)
ผลลัพธ์จะเป็น:
class '__main__.MyMetaClass'
class 'type'
ในการเขียนโปรแกรมเชิงวัตถุเมตาคลาสคือคลาสที่อินสแตนซ์เป็นคลาส เช่นเดียวกับคลาสทั่วไปที่กำหนดพฤติกรรมของวัตถุบางอย่าง metaclass จะกำหนดพฤติกรรมของคลาสบางอย่างและอินสแตนซ์ของพวกเขาคำ metaclass นั้นหมายถึงสิ่งที่ใช้สร้างคลาส มันคือคลาสของคลาส เมตาคลาสใช้ในการสร้างคลาสดังนั้นเช่นเดียวกับวัตถุที่เป็นอินสแตนซ์ของคลาสคลาสนั้นเป็นอินสแตนซ์ของเมตาคลาส ในชั้นเรียนของงูหลามก็ถือว่าเป็นวัตถุ
นี่คืออีกตัวอย่างของสิ่งที่สามารถใช้สำหรับ:
metaclass
เพื่อเปลี่ยนการทำงานของอินสแตนซ์ (คลาส)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()
metaclass
มีประสิทธิภาพและมีหลายสิ่งหลายอย่าง (เช่นมายากลลิง) คุณสามารถทำอะไรกับมัน แต่ต้องระวังเรื่องนี้อาจจะเป็นที่รู้จักกับคุณเท่านั้น
คลาสใน Python เป็นวัตถุและเหมือนกับวัตถุอื่นมันเป็นตัวอย่างของ "บางสิ่ง" "อะไรบางอย่าง" นี่คือสิ่งที่เรียกว่าเป็น Metaclass เมตาคลาสนี้เป็นคลาสพิเศษชนิดที่สร้างวัตถุของคลาสอื่น ดังนั้น metaclass มีหน้าที่สร้างคลาสใหม่ สิ่งนี้ทำให้โปรแกรมเมอร์สามารถกำหนดวิธีการสร้างคลาสได้เอง
หากต้องการสร้าง metaclass การแทนที่เมธอดnew () และinit () มักจะทำ ใหม่ () สามารถถูกแทนที่เพื่อเปลี่ยนวิธีการสร้างวัตถุในขณะที่init () สามารถแทนที่เพื่อเปลี่ยนวิธีการเริ่มต้นวัตถุ Metaclass สามารถสร้างได้หลายวิธี หนึ่งในวิธีคือการใช้ฟังก์ชั่น type () type () ฟังก์ชั่นเมื่อเรียกใช้ด้วยพารามิเตอร์ 3 ตัวจะสร้าง metaclass พารามิเตอร์คือ: -
อีกวิธีในการสร้าง metaclass ประกอบด้วยคำหลัก 'metaclass' กำหนด metaclass เป็นคลาสที่ง่าย ในพารามิเตอร์ของคลาสที่สืบทอดผ่าน metaclass = metaclass_name
Metaclass สามารถใช้งานได้เฉพาะในสถานการณ์ต่อไปนี้: -
Metaclass เป็นประเภทของคลาสที่กำหนดว่าคลาสจะมีลักษณะอย่างไรหรือเราสามารถพูดได้ว่าคลาสนั้นเป็นตัวอย่างของ metaclass
class A(type):pass<NEWLINE>class B(type,metaclass=A):pass<NEWLINE>b.__class__ = b