Có thể ghép chuỗi metaclasses không?Metaclass Mixin hoặc Chaining?
Tôi có lớp Model
sử dụng __metaclass__=ModelBase
để xử lý dict không gian tên của nó. Tôi sẽ kế thừa từ nó và "liên kết" một metaclass khác nên nó sẽ không tô bóng bản gốc.
phương pháp đầu tiên là phải phân lớp class MyModelBase(ModelBase)
:
MyModel(Model):
__metaclass__ = MyModelBase # inherits from `ModelBase`
Nhưng là nó có thể chỉ để chuỗi họ như mixins, mà không subclassing rõ ràng? Một cái gì đó như
class MyModel(Model):
__metaclass__ = (MyMixin, super(Model).__metaclass__)
... hoặc thậm chí tốt hơn: tạo ra một mixin rằng sẽ sử dụng __metaclass__
từ cha mẹ trực tiếp của các lớp có sử dụng nó:
class MyModel(Model):
__metaclass__ = MyMetaMixin, # Automagically uses `Model.__metaclass__`
Lý do: Đối với linh hoạt hơn trong việc mở rộng các ứng dụng hiện có, tôi muốn tạo cơ chế toàn cầu để gắn vào quá trình Model
, Form
, ... định nghĩa trong Django để nó có thể được thay đổi khi chạy.
Một cơ chế chung sẽ tốt hơn nhiều so với triển khai nhiều metaclasses với mixback gọi lại.
Với sự giúp đỡ của bạn, cuối cùng tôi đã tìm ra giải pháp: metaclass MetaProxy
.
Ý tưởng là: tạo ra một metaclass mà gọi một callback để thay đổi không gian tên của lớp được tạo ra, sau đó, với sự giúp đỡ của __new__
, đột biến thành một metaclass của một trong những cha mẹ
#!/usr/bin/env python
#-*- coding: utf-8 -*-
# Magical metaclass
class MetaProxy(type):
""" Decorate the class being created & preserve __metaclass__ of the parent
It executes two callbacks: before & after creation of a class,
that allows you to decorate them.
Between two callbacks, it tries to locate any `__metaclass__`
in the parents (sorted in MRO).
If found — with the help of `__new__` method it
mutates to the found base metaclass.
If not found — it just instantiates the given class.
"""
@classmethod
def pre_new(cls, name, bases, attrs):
""" Decorate a class before creation """
return (name, bases, attrs)
@classmethod
def post_new(cls, newclass):
""" Decorate a class after creation """
return newclass
@classmethod
def _mrobases(cls, bases):
""" Expand tuple of base-classes ``bases`` in MRO """
mrobases = []
for base in bases:
if base is not None: # We don't like `None` :)
mrobases.extend(base.mro())
return mrobases
@classmethod
def _find_parent_metaclass(cls, mrobases):
""" Find any __metaclass__ callable in ``mrobases`` """
for base in mrobases:
if hasattr(base, '__metaclass__'):
metacls = base.__metaclass__
if metacls and not issubclass(metacls, cls): # don't call self again
return metacls#(name, bases, attrs)
# Not found: use `type`
return lambda name,bases,attrs: type.__new__(type, name, bases, attrs)
def __new__(cls, name, bases, attrs):
mrobases = cls._mrobases(bases)
name, bases, attrs = cls.pre_new(name, bases, attrs) # Decorate, pre-creation
newclass = cls._find_parent_metaclass(mrobases)(name, bases, attrs)
return cls.post_new(newclass) # Decorate, post-creation
# Testing
if __name__ == '__main__':
# Original classes. We won't touch them
class ModelMeta(type):
def __new__(cls, name, bases, attrs):
attrs['parentmeta'] = name
return super(ModelMeta, cls).__new__(cls, name, bases, attrs)
class Model(object):
__metaclass__ = ModelMeta
# Try to subclass me but don't forget about `ModelMeta`
# Decorator metaclass
class MyMeta(MetaProxy):
""" Decorate a class
Being a subclass of `MetaProxyDecorator`,
it will call base metaclasses after decorating
"""
@classmethod
def pre_new(cls, name, bases, attrs):
""" Set `washere` to classname """
attrs['washere'] = name
return super(MyMeta, cls).pre_new(name, bases, attrs)
@classmethod
def post_new(cls, newclass):
""" Append '!' to `.washere` """
newclass.washere += '!'
return super(MyMeta, cls).post_new(newclass)
# Here goes the inheritance...
class MyModel(Model):
__metaclass__ = MyMeta
a=1
class MyNewModel(MyModel):
__metaclass__ = MyMeta # Still have to declare it: __metaclass__ do not inherit
a=2
class MyNewNewModel(MyNewModel):
# Will use the original ModelMeta
a=3
class A(object):
__metaclass__ = MyMeta # No __metaclass__ in parents: just instantiate
a=4
class B(A):
pass # MyMeta is not called until specified explicitly
# Make sure we did everything right
assert MyModel.a == 1
assert MyNewModel.a == 2
assert MyNewNewModel.a == 3
assert A.a == 4
# Make sure callback() worked
assert hasattr(MyModel, 'washere')
assert hasattr(MyNewModel, 'washere')
assert hasattr(MyNewNewModel, 'washere') # inherited
assert hasattr(A, 'washere')
assert MyModel.washere == 'MyModel!'
assert MyNewModel.washere == 'MyNewModel!'
assert MyNewNewModel.washere == 'MyNewModel!' # inherited, so unchanged
assert A.washere == 'A!'
Trong Python 3.4, điều này dường như không khẳng định chính xác; dòng 113 thất bại ('MyModel' không có thuộc tính' washere') – Joost