根据__init__的参数更改对象的基数

时间:2014-04-29 20:33:08

标签: python metaclass

我试图在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__内完成所有操作,但问题是那时对象已经存在,所以改变基础为时已晚

我对类和元类缺少什么?有没有办法做到这一点?

4 个答案:

答案 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类。在幕后,他们真正定义了一系列继承自BA的各种专业的专业化课程。

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

如果您希望非专业化的BC类型伪装成其专业化的超类,则可以将以下函数添加到BMeta

def __subclasscheck__(cls, subclass):
    return issubclass(subclass, tuple(cls._specializations.values()))

def __instancecheck__(cls, instance):
    return isinstance(instance, tuple(cls._specializations.values()))

这些将说服内置的isinstanceissubclass函数将从BC返回的实例视为&#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

它在某些方面与您的设置不同:

  1. 必须使用makeIt classmethod而不是直接使用C(blah)来实例化C类。这是为了避免无限循环。如果在__new__中使用B来处理委派,但是神奇创建的具有切换基础的新类必须从原始C继承,那么新类将继承{{1}并试图在内部创建一个将再次参与魔术。这也可以通过使用B.__new__并向动态创建的类添加“secret”属性并检查它以跳过魔法来规避。

  2. 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()