工厂函数与子类

0 投票
2 回答
1131 浏览
提问于 2025-04-17 10:37

我有以下的类结构:

class A(object):
    def __init__(self, filename, x):
        self.x = x
        # initialize A from filename

    # rest of A's methods

class B(A):
    def __init__(self, filename):
        super(B, self).__init__(filename, 10)

    # rest of B's methods

这两个类在它们的 __init__ 方法中都需要传入一个文件名。然后,这个文件的内容会用来初始化对象中的一些数据。

为了方便测试,我希望能够直接通过传递数据来创建 AB 的实例,而不是让对象从文件中读取数据。我想了一个办法,打算用两个工厂函数,每个函数用 classmethod 来实现,但我在 B 中的版本一直没法正常工作。以下是我目前的代码:

class A(object):
    def __init__(self, x):
        self.x = x

    @classmethod
    def construct(cls, filename, x):
        a = cls(x)
        # initialize a from filename
        return a

    @classmethod
    def test_construct(cls, data, x):
        a = cls(x)
        # initialize a from data
        return a

class B(A):
    def __init__(self):
        super(B, self).__init__(10)

    @classmethod
    def construct(cls, filename):
        # should construct B from filename

    @classmethod
    def test_construct(cls, data):
        # should construct B from data

2 个回答

1

__init__ 里处理数据。

你可以把这个变成可选的,在你的程序中使用 filename,在测试中使用 data

class A(object):
    def __init__(self, x, filename=None, data=None):
        if not any((filename, data)):
            raise TypeError('either filename or data needs to be provided')
        if all((filename, data)):
            raise TypeError("both filename and data can't be provided")

        self.x = x
        if filename:
            with open(filename, 'r') as f:
                data = f.read()     # just an example

编辑:无论如何,如果你想使用特殊的构造方法,这就是我会做的:

class A(object):
    def __init__(self, data, x):
        self.x = x
        self.data = data

    @classmethod
    def construct(cls, filename, x):
        with open(filename, 'r') as f:
            data = f.read()
        return cls(data, x)

class B(A):
    def __init__(self, data):
        super(B, self).__init__(data, 10)

    @classmethod
    def construct(cls, filename):
        with open(filename, 'r') as f:
            data = f.read()
        # modify data as you wish
        return cls(data)

在你的程序中调用 construct,在测试中调用 __init__

2

在B类的方法中,如果想要调用它的父类的方法,可以直接使用 super(),就像在调用基类的实例一样:

class B(A):
    def __init__(self):
        super(B, self).__init__(10)

    @classmethod
    def construct(cls, filename):
        # should construct B from filename
        return super(B, cls).construct(filename, 10)

编辑:正如你在评论中提到的,这里会出现一个问题,因为你在基类的构造函数中添加了一个参数。你应该避免在基类和子类之间的方法签名不兼容:一个 B 的实例 就是 一个 A 的实例,所以它应该能够接受你对 A 实例的任何方法调用。

一种解决办法是:

class B(A):
    def __init__(self, x=10):
        # if you're paranoid insert `assert x==10` here
        super(B, self).__init__(x)

    @classmethod
    def construct(cls, filename):
        # should construct B from filename
        return super(B, cls).construct(filename, 10)

这样就又能正常工作了,但你需要确保自己不要把 'x' 直接传给 B 的实例。更好的做法可能是完全去掉 __init__ 中的 x,看起来你可以通过将它设置为类属性,或者在构造后单独设置来实现。

撰写回答