Tôi có một ý tưởng sơ bộ về các lớp meta. Chúng là các lớp trong đó các đối tượng lớp được dựa trên (vì các lớp là các đối tượng trong Python). Nhưng ai đó có thể giải thích (với mã) như thế nào đi về việc tạo ra một.Làm thế nào để tạo một metaclass?
Trả lời
Có (vào thời điểm này) hai phương pháp quan trọng trong một metaclass:
__prepare__
, và__new__
__prepare__
cho phép bạn cung cấp một bản đồ tùy chỉnh (chẳng hạn như một OrderedDict
) được sử dụng làm không gian tên trong khi lớp đang được tạo. Bạn phải trả về một cá thể của bất kỳ không gian tên nào bạn chọn. Nếu bạn không triển khai __prepare__
thì sử dụng thông thường dict
.
__new__
chịu trách nhiệm tạo/sửa đổi thực tế lớp học cuối cùng.
Một trần xương, làm-gì-thêm metaclass sẽ trông như thế:
class Meta(type):
def __prepare__(metaclass, cls, bases):
return dict()
def __new__(metacls, cls, bases, clsdict):
return super().__new__(metacls, cls, bases, clsdict)
Một ví dụ đơn giản:
Giả sử bạn muốn một số mã xác nhận đơn giản để chạy trên các thuộc tính của bạn - như nó phải luôn là int
hoặc str
. Nếu không có metaclass, lớp học của bạn sẽ trông giống như sau:
class Person:
weight = ValidateType('weight', int)
age = ValidateType('age', int)
name = ValidateType('name', str)
Như bạn 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 kích thích.
Một metaclass đơn giản có thể giải quyết vấn đề đó:
class Person(metaclass=Validator):
weight = ValidateType(int)
age = ValidateType(int)
name = ValidateType(str)
Đây là những gì metaclass sẽ trông như thế (không sử dụng __prepare__
vì nó không cần thiết):
class Validator(type):
def __new__(metacls, cls, bases, clsdict):
# search clsdict looking for ValidateType descriptors
for name, attr in clsdict.items():
if isinstance(attr, ValidateType):
attr.name = name
attr.attr = '_' + name
# create final class and return it
return super().__new__(metacls, cls, bases, clsdict)
Một mẫu chạy 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')
Ghi chú
Ví dụ này là đủ đơn giản, nó có thể cũng đã được thực hiện với một trang trí lớp học, nhưng có lẽ một metaclass thực tế sẽ làm nhiều hơn nữa.
Trong Python 2.x, phương pháp __prepare__
không tồn tại, và lớp speficies metaclass của mình bằng cách đưa vào một biến lớp __metaclass__ = ...
, như thế này:
class Person(object):
__metaclass__ = ValidateType
Các 'ValidateType' lớp để 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
tôi đã viết một ví dụ hoàn toàn bình luận về một metaclass. Đó là trong Python 2.7. Tôi đang chia sẻ nó ở đây và hy vọng rằng nó có thể giúp bạn hiểu thêm về các phương pháp __new__
, __init__
, __call__
, __dict__
và khái niệm bị ràng buộc/không bị ràng buộc bằng Python, cũng như việc sử dụng metaclasses.
Vấn đề với một metaclass, tôi cảm thấy, là nó có quá nhiều những nơi mà bạn có thể làm những điều tương tự, hoặc tương tự nhưng với một số khác biệt nhỏ . Vì vậy, ý kiến của tôi và các trường hợp kiểm tra chủ yếu nhấn mạnh nơi để viết những gì, những gì đi đến nơi tại các điểm nhất định và những gì có thể truy cập cho một đối tượng nhất định.
Ví dụ này cố gắng xây dựng một nhà máy lớp học trong khi vẫn duy trì các định nghĩa lớp được định dạng tốt.
from pprint import pprint
from types import DictType
class FactoryMeta(type):
""" Factory Metaclass """
# @ Anything "static" (bounded to the classes rather than the instances)
# goes in here. Or use "@classmethod" decorator to bound it to meta.
# @ Note that these members won't be visible to instances, you have to
# manually add them to the instances in metaclass' __call__ if you wish
# to access them through a instance directly (see below).
extra = "default extra"
count = 0
def clsVar(cls):
print "Class member 'var': " + str(cls.var)
@classmethod
def metaVar(meta):
print "Metaclass member 'var': " + str(meta.var)
def __new__(meta, name, bases, dict):
# @ Metaclass' __new__ serves as a bi-functional slot capable for
# initiating the classes as well as alternating the meta.
# @ Suggestion is putting majority of the class initialization code
# in __init__, as you can directly reference to cls there; saving
# here for anything you want to dynamically added to the meta (such
# as shared variables or lazily GC'd temps).
# @ Any changes here to dict will be visible to the new class and their
# future instances, but won't affect the metaclass. While changes
# directly through meta will be visible to all (unless you override
# it later).
dict['new_elem'] = "effective"
meta.var = "Change made to %s by metaclass' __new__" % str(meta)
meta.count += 1
print "================================================================"
print " Metaclass's __new__ (creates class objects)"
print "----------------------------------------------------------------"
print "Bounded to object: " + str(meta)
print "Bounded object's __dict__: "
pprint(DictType(meta.__dict__), depth = 1)
print "----------------------------------------------------------------"
print "Parameter 'name': " + str(name)
print "Parameter 'bases': " + str(bases)
print "Parameter 'dict': "
pprint(dict, depth = 1)
print "\n"
return super(FactoryMeta, meta).__new__(meta, name, bases, dict)
def __init__(cls, name, bases, dict):
# @ Metaclass' __init__ is the standard slot for class initialization.
# Classes' common variables should mainly goes in here.
# @ Any changes here to dict won't actually affect anything. While
# changes directly through cls will be visible to the created class
# and its future instances. Metaclass remains untouched.
dict['init_elem'] = "defective"
cls.var = "Change made to %s by metaclass' __init__" % str(cls)
print "================================================================"
print " Metaclass's __init__ (initiates class objects)"
print "----------------------------------------------------------------"
print "Bounded to object: " + str(cls)
print "Bounded object's __dict__: "
pprint(DictType(cls.__dict__), depth = 1)
print "----------------------------------------------------------------"
print "Parameter 'name': " + str(name)
print "Parameter 'bases': " + str(bases)
print "Parameter 'dict': "
pprint(dict, depth = 1)
print "\n"
return super(FactoryMeta, cls).__init__(name, bases, dict)
def __call__(cls, *args):
# @ Metaclass' __call__ gets called when a class name is used as a
# callable function to create an instance. It is called before the
# class' __new__.
# @ Instance's initialization code can be put in here, although it
# is bounded to "cls" rather than instance's "self". This provides
# a slot similar to the class' __new__, where cls' members can be
# altered and get copied to the instances.
# @ Any changes here through cls will be visible to the class and its
# instances. Metaclass remains unchanged.
cls.var = "Change made to %s by metaclass' __call__" % str(cls)
# @ "Static" methods defined in the meta which cannot be seen through
# instances by default can be manually assigned with an access point
# here. This is a way to create shared methods between different
# instances of the same metaclass.
cls.metaVar = FactoryMeta.metaVar
print "================================================================"
print " Metaclass's __call__ (initiates instance objects)"
print "----------------------------------------------------------------"
print "Bounded to object: " + str(cls)
print "Bounded object's __dict__: "
pprint(DictType(cls.__dict__), depth = 1)
print "\n"
return super(FactoryMeta, cls).__call__(*args)
class Factory(object):
""" Factory Class """
# @ Anything declared here goes into the "dict" argument in the metaclass'
# __new__ and __init__ methods. This provides a chance to pre-set the
# member variables desired by the two methods, before they get run.
# @ This also overrides the default values declared in the meta.
__metaclass__ = FactoryMeta
extra = "overridng extra"
def selfVar(self):
print "Instance member 'var': " + str(self.var)
@classmethod
def classFactory(cls, name, bases, dict):
# @ With a factory method embedded, the Factory class can act like a
# "class incubator" for generating other new classes.
# @ The dict parameter here will later be passed to the metaclass'
# __new__ and __init__, so it is the right place for setting up
# member variables desired by these two methods.
dict['class_id'] = cls.__metaclass__.count # An ID starts from 0.
# @ Note that this dict is for the *factory product classes*. Using
# metaclass as callable is another way of writing class definition,
# with the flexibility of employing dynamically generated members
# in this dict.
# @ Class' member methods can be added dynamically by using the exec
# keyword on dict.
exec(cls.extra, dict)
exec(dict['another_func'], dict)
return cls.__metaclass__(name + ("_%02d" % dict['class_id']), bases, dict)
def __new__(cls, function):
# @ Class' __new__ "creates" the instances.
# @ This won't affect the metaclass. But it does alter the class' member
# as it is bounded to cls.
cls.extra = function
print "================================================================"
print " Class' __new__ (\"creates\" instance objects)"
print "----------------------------------------------------------------"
print "Bounded to object: " + str(cls)
print "Bounded object's __dict__: "
pprint(DictType(cls.__dict__), depth = 1)
print "----------------------------------------------------------------"
print "Parameter 'function': \n" + str(function)
print "\n"
return super(Factory, cls).__new__(cls)
def __init__(self, function, *args, **kwargs):
# @ Class' __init__ initializes the instances.
# @ Changes through self here (normally) won't affect the class or the
# metaclass; they are only visible locally to the instances.
# @ However, here you have another chance to make "static" things
# visible to the instances, "locally".
self.classFactory = self.__class__.classFactory
print "================================================================"
print " Class' __init__ (initiates instance objects)"
print "----------------------------------------------------------------"
print "Bounded to object: " + str(self)
print "Bounded object's __dict__: "
pprint(DictType(self.__dict__), depth = 1)
print "----------------------------------------------------------------"
print "Parameter 'function': \n" + str(function)
print "\n"
return super(Factory, self).__init__(*args, **kwargs)
# @ The metaclass' __new__ and __init__ will be run at this point, where the
# (manual) class definition hitting its end.
# @ Note that if you have already defined everything well in a metaclass, the
# class definition can go dummy with simply a class name and a "pass".
# @ Moreover, if you use class factories extensively, your only use of a
# manually defined class would be to define the incubator class.
Kết quả trông như thế này (điều chỉnh cho trình diễn tốt hơn):
================================================================
Metaclass's __new__ (creates class objects)
----------------------------------------------------------------
Bounded to object: <class '__main__.FactoryMeta'>
Bounded object's __dict__:
{ ...,
'clsVar': <function clsVar at 0x00000000029BC828>,
'count': 1,
'extra': 'default extra',
'metaVar': <classmethod object at 0x00000000029B4B28>,
'var': "Change made to <class '__main__.FactoryMeta'> by metaclass' __new__"}
----------------------------------------------------------------
Parameter 'name': Factory
Parameter 'bases': (<type 'object'>,)
Parameter 'dict':
{ ...,
'classFactory': <classmethod object at 0x00000000029B4DC8>,
'extra': 'overridng extra',
'new_elem': 'effective',
'selfVar': <function selfVar at 0x00000000029BC6D8>}
================================================================
Metaclass's __init__ (initiates class objects)
----------------------------------------------------------------
Bounded to object: <class '__main__.Factory'>
Bounded object's __dict__:
{ ...,
'classFactory': <classmethod object at 0x00000000029B4DC8>,
'extra': 'overridng extra',
'new_elem': 'effective',
'selfVar': <function selfVar at 0x00000000029BC6D8>,
'var': "Change made to <class '__main__.Factory'> by metaclass' __init__"}
----------------------------------------------------------------
Parameter 'name': Factory
Parameter 'bases': (<type 'object'>,)
Parameter 'dict':
{ ...,
'classFactory': <classmethod object at 0x00000000029B4DC8>,
'extra': 'overridng extra',
'init_elem': 'defective',
'new_elem': 'effective',
'selfVar': <function selfVar at 0x00000000029BC6D8>}
Trình tự gọi là metaclass' __new__
sau đó __init__
của nó. __call__
sẽ không được gọi vào lúc này.
Và nếu chúng ta tạo ra một thể hiện,
func1 = (
"def printElems(self):\n"
" print \"Member new_elem: \" + self.new_elem\n"
" print \"Member init_elem: \" + self.init_elem\n"
)
factory = Factory(func1)
Đầu ra là:
================================================================
Metaclass's __call__ (initiates instance objects)
----------------------------------------------------------------
Bounded to object: <class '__main__.Factory'>
Bounded object's __dict__:
{ ...,
'classFactory': <classmethod object at 0x00000000029B4DC8>,
'extra': 'overridng extra',
'metaVar': <bound method type.metaVar of <class '__main__.FactoryMeta'>>,
'new_elem': 'effective',
'selfVar': <function selfVar at 0x00000000029BC6D8>,
'var': "Change made to <class '__main__.Factory'> by metaclass' __call__"}
================================================================
Class' __new__ ("creates" instance objects)
----------------------------------------------------------------
Bounded to object: <class '__main__.Factory'>
Bounded object's __dict__:
{ ...,
'classFactory': <classmethod object at 0x00000000029B4DC8>,
'extra': 'def printElems(self):\n print "Member new_elem: " + self.new_elem\n print "Member init_elem: " + self.init_elem\n',
'metaVar': <bound method type.metaVar of <class '__main__.FactoryMeta'>>,
'new_elem': 'effective',
'selfVar': <function selfVar at 0x00000000029BC6D8>,
'var': "Change made to <class '__main__.Factory'> by metaclass' __call__"}
----------------------------------------------------------------
Parameter 'function':
def printElems(self):
print "Member new_elem: " + self.new_elem
print "Member init_elem: " + self.init_elem
================================================================
Class' __init__ (initiates instance objects)
----------------------------------------------------------------
Bounded to object: <__main__.Factory object at 0x00000000029BB7B8>
Bounded object's __dict__:
{'classFactory': <bound method FactoryMeta.classFactory of <class '__main__.Factory'>>}
----------------------------------------------------------------
Parameter 'function':
def printElems(self):
print "Member new_elem: " + self.new_elem
print "Member init_elem: " + self.init_elem
Các metaclass' __call__
được gọi đầu tiên, sau đó lớp __new__
và __init__
.
So sánh các thành viên được in của từng đối tượng, bạn có thể khám phá thời gian và địa điểm họ đã thêm hoặc thay đổi, giống như tôi đã nhận xét trong mã.
Tôi cũng chạy trường hợp kiểm tra sau:
factory.clsVar() # Will raise exception
Factory.clsVar()
factory.metaVar()
factory.selfVar()
func2 = (
"@classmethod\n"
"def printClassID(cls):\n"
" print \"Class ID: %02d\" % cls.class_id\n"
)
ProductClass1 = factory.classFactory("ProductClass", (object,), { 'another_func': func2 })
product = ProductClass1()
product.printClassID()
product.printElems() # Will raise exception
ProductClass2 = Factory.classFactory("ProductClass", (Factory,), { 'another_func': "pass" })
ProductClass2.printClassID() # Will raise exception
ProductClass3 = ProductClass2.classFactory("ProductClass", (object,), { 'another_func': func2 })
nào bạn có thể chạy một mình để xem cách nó hoạt động.
Lưu ý rằng tôi cố ý để tên của các lớp được tạo động được tạo khác với tên biến mà chúng được gán cho. Điều này là để hiển thị tên nào thực sự có hiệu lực.
Một lưu ý khác là tôi đặt "tĩnh" trong dấu ngoặc kép, mà tôi đề cập đến khái niệm như trong C++ chứ không phải là trình trang trí Python. Theo truyền thống tôi là một lập trình viên C++, vì vậy tôi vẫn muốn suy nghĩ theo cách của mình.
- 1. Làm thế nào để thay đổi metaclass của lớp
- 2. Làm thế nào để thêm phương thức sử dụng metaclass
- 3. Làm thế nào để đánh chặn việc tạo lớp và thêm thuộc tính bằng cách sử dụng một metaclass?
- 4. Làm thế nào để xác định một metaclass Python với Boost.Python?
- 5. Làm thế nào để xác định metaclass cho một lớp học kéo dài từ SQLAlchemy cơ sở khai báo
- 6. Thừa kế của metaclass
- 7. Supertalk superclass vs metaclass?
- 8. Ai gọi metaclass
- 9. Metaclass Mixin hoặc Chaining?
- 10. Làm thế nào để tạo một Outlook Plugin bằng Delphi?
- 11. Làm thế nào để tạo một bot IM với C#
- 12. Làm thế nào để tạo một UITextfield đa dòng?
- 13. JsTree làm thế nào để tạo ra một nút gốc
- 14. Làm thế nào để tạo một ConstantInt trong LLVM?
- 15. Làm thế nào để tạo một popup nhỏ trong iOS?
- 16. Làm thế nào để tạo ra một Gemfile?
- 17. Làm thế nào để tạo ra một động Bootstrap Multiselect
- 18. Làm thế nào để tạo một AttributeSet trong Android?
- 19. Làm thế nào để tạo chỉ một bảng với SQLAlchemy?
- 20. Làm thế nào để tạo một va_list trên GCC?
- 21. Làm thế nào để tạo một bản sao lớp String?
- 22. Làm thế nào để tạo một mảng bit trong Python?
- 23. Làm thế nào để tạo một thư mục iOS?
- 24. Làm thế nào để tạo một phar thực thi?
- 25. Làm thế nào để tạo một Drawable từ byte []? (Android)
- 26. Làm thế nào để tạo một LinkedList tròn
- 27. Làm thế nào để tạo một F # PCL với Xamarin.Studio?
- 28. Làm thế nào để khởi tạo một NSWindowController trong Swift?
- 29. Clojure: Làm thế nào để tạo ra một 'trie'?
- 30. Làm thế nào để tạo ra một icosahedron chia nhỏ?
Để xem chi tiết tại sao * của metaclasses, xem [câu trả lời này] (http://stackoverflow.com/a/6581949/208880). –
@PeterMortensen: Không. Câu hỏi đó đang tìm kiếm một nguồn tài nguyên ngoài trang web. –