我试图在Python中创建一个动态更改类型基类的元类,在创建期间根据创建实例时给出的参数进行更改。
简而言之,我有一个层次结构,C --> B --> A
但我想要做的是动态交换A
以便A
的其他实现,如果某些事情传递给{{} 1}}用于施工。
因为C是这个库的用户实现的,我不想强迫他们写任何初学者不会理解的东西,所以我的计划是让魔法在{{1}内部发生},仅出于将C
转移到适当的实现而退出的目的。
根据我对metaclasses和__new__
的理解,我得到了:
B
我的计划是根据A
的论据将class A(object):
pass
class Aimpl1(object):
def foo(self):
print "FOO"
class Aimpl2(object):
def foo(self):
print "BAR"
class AImplChooser(type):
def __call__(self, *args, **kwargs):
print "In call"
return super(AImplChooser,self).__call__(*args,**kwargs)
def __new__(cls, clsname, bases, dct):
print "Creating: " + clsname + ", " + ','.join([str(x) for x in bases])
return super(AImplChooser,cls).__new__(cls, clsname, bases, dct)
class B(A):
__metaclass__ = AImplChooser
def __init__(self, arg1, arg, arg3):
pass
class C(B):
def __init__(self, arg1, arg2=0, arg3=[]):
super(C, self).__init__(arg1, arg2, arg3)
c=C('')
print type(c)
print dir(type(c))
print c.__class__.__bases__
c.foo()
内的基地转移,但当然根本不会按顺序调用它们,所以这样就可以了不是一种选择。
我想完全放弃B.__new__
并在B.__call__
内完成所有操作,但问题是那时对象已经存在,所以改变基础为时已晚
我对类和元类缺少什么?有没有办法做到这一点?
答案 0 :(得分:3)
我相信我已经设法实现了你要求的元类。我不确定这是否是最好的设计,但它确实有效。 C
的每个名义实例实际上都是"专业化的实例" C
的{{1}}派生自B
的特化,派生自专门的A
类(A
类不需要以任何方式相关)。给定C
特化的所有实例将具有彼此相同的类型,但具有与具有不同特化的实例不同的类型。继承的工作方式相同,特殊化定义了单独的并行类树。
这是我的代码:
首先,我们需要定义A
类的特化。这可以随心所欲地完成,但是对于我的测试,我使用列表推导来在num
类变量中构建一组具有不同名称和不同值的类。
As = [type('A_{}'.format(i), (object,), {"num":i}) for i in range(10)]
接下来,我们有了#34;虚拟" unspecialized A
类,它实际上只是一个元类挂钩的地方。 A
的元类AMeta
会查找上面定义的列表中的专用A
类。如果您使用其他方法来定义专门的A
类,请更改AMeta._get_specialization
以便能够找到它们。如果您愿意,甚至可以在此处按需创建A
的新专业化。
class AMeta(type):
def _get_specialization(cls, selector):
return As[selector]
class A(object, metaclass=AMeta): # I'm using Python 3 metaclass declarations
pass # nothing defined in A is ever used, it is a pure dummy
现在,我们来到课程B
及其元类BMeta
。这是我们的子类的实际专业化发生的地方。元类的__call__
方法使用_get_specialization
方法基于selector
参数构建类的专用版本。 _get_specialization
缓存其结果,因此在继承树的给定级别,每个特化只有一个类。
如果需要,可以稍微调整一下(使用多个参数来计算selector
或其他),并且您可能希望将选择器传递给类构造函数,具体取决于它实际上是什么。元类的当前实现只允许单继承(一个基类),但它可能会扩展为在您不需要的情况下支持多重继承。
请注意,虽然此处B
类为空,但您可以为其提供将出现在每个专门化中的方法和类变量(作为浅层副本)。
class BMeta(AMeta):
def __new__(meta, name, bases, dct):
cls = super(BMeta, meta).__new__(meta, name, bases, dct)
cls._specializations = {}
return cls
def _get_specialization(cls, selector):
if selector not in cls._specializations:
name = "{}_{}".format(cls.__name__, selector)
bases = (cls.__bases__[0]._get_specialization(selector),)
dct = cls.__dict__.copy()
specialization = type(name, bases, dct) # not a BMeta!
cls._specializations[selector] = specialization
return cls._specializations[selector]
def __call__(cls, selector, *args, **kwargs):
cls = cls._get_specialization(selector)
return type.__call__(cls, *args, **kwargs) # selector could be passed on here
class B(A, metaclass=BMeta):
pass
通过此设置,您的用户可以定义从C
继承的任意数量的B
类。在幕后,他们真正定义了一系列继承自B
和A
的各种专业的专业化课程。
class C(B):
def print_num(self):
return self.num
值得注意的是,C并不是真正用作常规课程。 C
实际上是一个工厂,它创建各种相关类的实例,而不是自身的实例。
>>> C(1)
<__main__.C_1 object at 0x00000000030231D0>
>>> C(2)
<__main__.C_2 object at 0x00000000037101D0>
>>> C(1).print_num()
1
>>> C(2).print_num()
2
>>> type(C(1)) == type(C(2))
False
>>> type(C(1)) == type(C(1))
True
>>> isinstance(C(1), type(B(1)))
True
但是,这可能是一种不明显的行为:
>>> isinstance(C(1), C)
False
如果您希望非专业化的B
和C
类型伪装成其专业化的超类,则可以将以下函数添加到BMeta
:
def __subclasscheck__(cls, subclass):
return issubclass(subclass, tuple(cls._specializations.values()))
def __instancecheck__(cls, instance):
return isinstance(instance, tuple(cls._specializations.values()))
这些将说服内置的isinstance
和issubclass
函数将从B
和C
返回的实例视为&#34; factory&#的实例34;类。
答案 1 :(得分:2)
更新:可能的替代方法是使用类装饰器来执行当前B
角色:
(这仍需要一些工作)。
class A1(object):
def foo(self):
print 'foo'
class A2(object):
def foo(self):
print 'bar'
from functools import wraps
def auto_impl_A(cls):
@wraps(cls)
def f(val, *args, **kwargs):
base = {1: A1, 2: A2}.get(val, object)
return type(cls.__name__, (cls, base,), dict(cls.__dict__))(*args, **kwargs)
return f
@auto_impl_A
class MyC(object):
pass
所以用户装饰他们的类而不是继承,并正常写C
,但它的基础将是一个合适的A
......
原始的proprosal:如果我理解正确,那么从一开始就更容易使用工厂功能并创建一个具有合适基础的新type
...
class A1(object): pass
class A2(object): pass
class ANOther(object): pass
def make_my_C_obj(someval, *args, **kwargs):
base = {1: A1, 2: A2}.get(someval, ANOther)
return type('C', (base,), {})(*args, **kwargs)
for i in xrange(3):
print i, type(make_my_C_obj(i)).mro()
0 [<class '__main__.C'>, <class '__main__.ANOther'>, <type 'object'>]
1 [<class '__main__.C'>, <class '__main__.A1'>, <type 'object'>]
2 [<class '__main__.C'>, <class '__main__.A2'>, <type 'object'>]
这相当于:
class Aimpl1(object):
def foo(self):
print "FOO"
class Aimpl2(object):
def foo(self):
print "BAR"
def C_factory(base):
class C(base):
pass
return C
for b in (Aimpl1, Aimpl2):
c=C_factory(b)()
c.foo()
print type(c)
答案 2 :(得分:2)
这是我目前最接近的事情:
class A(object):
pass
class Aimpl1(object):
def foo(self):
print "FOO"
class Aimpl2(object):
def foo(self):
print "BAR"
class B(object):
@classmethod
def makeIt(cls, whichA):
if whichA == 1:
impl = Aimpl1
elif whichA == 2:
impl = Aimpl2
else:
impl = A
print "Instantiating", impl, "from", cls
TmpC = type(b'TmpC', (cls,impl), dict(cls.__dict__))
return TmpC(whichA)
def __init__(self, whichA):
pass
class C(B):
def __init__(self, whichA):
super(C, self).__init__(whichA)
可以这样使用:
>>> c = C.makeIt(1)
Instantiating <class '__main__.Aimpl1'> from <class '__main__.C'>
>>> c.__class__.__mro__
(<class '__main__.TmpC'>,
<class '__main__.C'>,
<class '__main__.B'>,
<class '__main__.Aimpl1'>,
<type 'object'>)
>>> c.foo()
FOO
>>> c = C.makeIt(2)
Instantiating <class '__main__.Aimpl2'> from <class '__main__.C'>
>>> c.__class__.__mro__
(<class '__main__.TmpC'>,
<class '__main__.C'>,
<class '__main__.B'>,
<class '__main__.Aimpl2'>,
<type 'object'>)
>>> c.foo()
BAR
它在某些方面与您的设置不同:
必须使用makeIt
classmethod而不是直接使用C(blah)
来实例化C类。这是为了避免无限循环。如果在__new__
中使用B
来处理委派,但是神奇创建的具有切换基础的新类必须从原始C
继承,那么新类将继承{{1}并试图在内部创建一个将再次参与魔术。这也可以通过使用B.__new__
并向动态创建的类添加“secret”属性并检查它以跳过魔法来规避。
B不从A继承,因此当C继承自B时,它也不会从A继承;相反,它可以从正确交换的实现基础继承。
答案 3 :(得分:1)
您可以使用包装器执行此操作:
class Bwrapper(object):
def __init__(self, impl):
self._a = Aimpl2() if impl == 2 else Aimpl1()
def foo(self):
return self._a.foo()