将超类实例转换为子类实例

11
  • 我有一个外部库,无法修改。这个库有一个函数,genA(),用于返回类A的实例。
  • 我在自己的代码中定义了类B,作为类A的子类。
  • 我想在我的项目中使用类B的实例,但是这个实例应该由genA()生成。

有没有标准且简单的方法来实现这一点呢?


# I cannnot tweak these code

def genA():
    a = A
    return(a)

class A:
    def __init__():
        self.a = 1

# ---

# code in my side

class B(A):
    def __init__():
        self.b = 2


a = genA()
# like a copy-constructor, doesn't work
# b = B(a)

# I want to get this
b.a # => 1
b.b # => 2

这里是等价的C++代码:
#include <iostream>

// library side code
class A {
public:
  int a;
  // ... many members

  A() { a = 1; }
};

void fa(A a) {
  std::cout << a.a << std::endl;
}

A genA() { A a; return a; }

// ///
// my code

class B : public A {
public:
  int b;
  B() : A() { init(); }
  B(A& a) : A(a) { init(); }
  void init() { b = 2; }
};

void fb(B b) {
  std::cout << b.b << std::endl;
}


int main(void) {
  A a = genA();
  B b(a);

  fa(b); // => 1
  fb(b); // => 2
}

这是可以通过极其笨拙的方法实现的,但我不建议这样做。你不能将A对象放入某种包装器中,而不是尝试更改它的类吗? - user2357112
谢谢,实际上我需要将对象传递给库侧函数和我的函数,所以从某种意义上说,对象应该是类A的实例。但现在我明白了这种困难(令我惊讶!),我会采取另一种方法。也许定义一个类B,它有一个类A实例作为其成员。当我需要将它传递给库侧函数时,调用 a = genA(); B b; b.a_instance = a; lib_fun(b.a_instance)。您认为这有意义吗? - kohske
我不确定它会运行得有多好,但似乎是可行的。 - user2357112
3个回答

11

不应该使用__new__,但它仅适用于新式类:

class A(object):
    def __init__(self):
        self.a = 10

class B(A):
    def __new__(cls, a):
        a.__class__ = cls
        return a

    def __init__(self, a):
        self.b = 20

a = A()
b = B(a)

print type(b), b.a, b.b   # <class '__main__.B'> 10 20

但是正如我所说的,不要这样做,你应该在这种情况下使用聚合而不是子类化。如果你希望让B具有与A相同的接口,你可以编写带有__getattr__的透明代理:

class B(object):
    def __init__(self, a):
        self.__a = a
        self.b = 20

    def __getattr__(self, attr):
        return getattr(self.__a, attr)

    def __setattr__(self, attr, val):
        if attr == '_B__a':
            object.__setattr__(self, attr, val)

        return setattr(self.__a, attr, val)

1
谢谢,如果类A有很多成员,我需要为所有成员编写setter/getter吗? - kohske
1
@kohske,不需要。每次您尝试获取 b.a 时,Python 都找不到 a 属性,因此它会请求 __getattr__ 帮助,并将 "a" 作为 attr 参数传递,因此 __getattr__ 处理了 A 类的所有属性。 - myaut
1
谢谢,这很有用。我可以问一下为什么不应该使用__new__吗? - kohske
1
@kohske:重新定义__class__虽然可行,但是这是一种非常不寻常的技术。它会让其他开发人员感到困惑,并可能导致意想不到的后果(例如,在多重继承中,您可能还需要覆盖MRO)。此外,它也不符合我所知道的任何设计模式。 - myaut
@myaut,应该是这样的吧: def __setattr__(self, attr, val): if attr == '_B__a': object.__setattr__(self, attr, val); else: return setattr(self.__a, attr, val); 否则Python不会在self.__a内查找名为_B__a的变量? - Nick Crews

2

我不理解你的代码。在我的看法中,它是不正确的。首先,在A和B类的__init__中都没有self。其次,在你的B类中,你没有调用A的构造函数。第三,genA函数没有返回任何对象,只是引用了A类。请检查修改后的代码:

def genA():
    a = A() #<-- need ()
    return(a)

class A:
    def __init__(self):  # <-- self missing
        self.a = 1

# ---

# code in my side

class B(A):
    def __init__(self, a=None):
        super().__init__()  #<-- initialize base class

        if isinstance(a, A): #<-- if a is instance of base class, do copying
            self.a = a.a

        self.b = 2


a = genA()
a.a = 5
b = B(a)

# this works
print(b.a) # => 5
print(b.b) # => 2   

谢谢,如果A类有很多成员,我需要为所有成员编写self.a = a.a吗? - kohske
简短地回答是肯定的。长话短说,你可以使用循环迭代变量。但我认为这超出了该问题的范围。 - Marcin
非常感谢。使用迭代很容易,但我只想知道是否有一种简单和标准的方法。非常感谢。 - kohske

0
似乎没有标准的方法来做到这一点,但有几种方法。如果您不想处理每个单独的属性,我建议使用以下方法:
class A(object):
    # Whatever

class B(A):
    def __init__(self, a):
        super(B, self).__init__()
        for attr in dir(a):
            setattr(self, attr, getattr(a, attr))
        # Any other specific attributes of class B can be set here

# Now, B can be instantiated this way:
a = A()
b = B(a)

如果您不想访问父类的“私有”属性,可以添加

if not attr.startswith('__'):

for 循环中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接