"根据__init__的参数改变对象的基础"

2024-09-30 14:37:13 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图在Python中创建一个元类,在创建过程中根据创建实例时给定的参数动态更改类型的基类。在

简而言之,我有一个层次结构,C --> B --> A但我想做的是,如果某些东西被传递给C进行构造,我要动态地交换A的其他实现。在

因为C是这个库的用户实现的,所以我不想强迫他们写一些初学者不懂的东西,所以我的计划是在B内部实现魔法,它的存在只是为了将A转移到适当的实现中。在

根据我对metaclasses^{}的理解,我得到了:

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.__call__的参数来转移B.__new__内的碱基,但当然它们根本不会按这个顺序被调用,所以这不是一个选项。在

我想把__new__全部删除,然后在__call__内完成,但问题是,到那时对象已经存在了,所以更改基太晚了。在

关于类和元类,我缺少什么?有办法吗?在


Tags: selfnewobjectfooinitdeftypecall
3条回答

更新:一种可能的替代方法是使用类修饰符来扮演当前的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。。。在


如果我理解正确的话,从一开始使用工厂函数并创建一个新的type并使用适当的基来创建一个新的type会更容易。。。在

^{pr2}$

相当于:

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)

我相信我已经成功地实现了您所要求的元类。我不确定这是否是最好的设计,但它是有效的。C的每个概念实例实际上都是C的“专门化”实例,它派生自B的一个专门化,它派生自一个专门化的A类(这些A类不需要以任何方式关联)。给定的C专用化的所有实例都将具有相同的类型,但与具有不同专门化的实例的类型不同。继承的工作方式相同,专门化定义了单独的并行类树。在

我的代码是:

首先,我们需要定义A类的专门化。这可以根据您的需要进行,但是在我的测试中,我使用了列表理解来在num类变量中构建一组具有不同名称和不同值的类。在

As = [type('A_{}'.format(i), (object,), {"num":i}) for i in range(10)]

接下来,我们有一个“dummy”未专门化的A类,它实际上只是一个元类可以挂接的地方。A的元类AMeta执行我在上面定义的列表中指定的{}类的查找。如果您使用不同的方法来定义专用的A类,请更改AMeta._get_specialization以找到它们。如果您愿意,甚至可以在这里按需创建A的新专门化。在

^{pr2}$

现在,我们来看看类B及其元类{}。这就是我们的子类的实际专业化发生的地方。元类的__call__方法基于selector参数,使用_get_specialization方法来构建类的专用版本。_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

使用此设置,用户可以定义从B继承的任意数量的C类。在幕后,他们将真正定义从B和{}的各种专门化继承的一整套专门化类。在

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返回的实例视为其“factory”类的实例。在

下面是我目前能收集到的最接近的信息:

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)

它可以这样使用:

^{pr2}$

它与您的设置在以下几个方面有所不同:

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

  2. B不从A继承,因此当C从B继承时,它也不会从A继承;而是从正确交换的实现基继承。

相关问题 更多 >