类构造函数可以返回子类吗?

时间:2019-04-25 05:31:45

标签: python python-3.x class

我正在尝试建立一个可以解析数学表达式的类(我知道SymPy,我已经尝试过了,但是它不适合我的用途)。

根据表达式输入,我需要返回一个不同的类。例如,我有这个:

class MyNum(MyTerm):
    def __init__(self, n):
        self.num = n

    def latex(self):
        return str(self.num)


class MyDivision(MyTerm):
    def __init__(self, n, d):
        self.numerator = n
        self.denominator = d

    def latex(self):
        return '\\frac {{ {} }} {{ {} }}'.format(self.numerator, self.denominator)


def parseTerm(term):
    matches = re.match(r'^[0-9]+$', term)
    if matches is not None:
        return MyNum(term)

    matches = re.match(r'^([0-9]+)/([0-9]+)$', term)
    if matches is not None:
        return MyDivision(matches[1], matches[2])

因此,我们有一个工厂函数parseTerm,它将返回适当的类。 parseTerm("2")将给出MyNum,而parseTerm("2/3")将给出MyDivision

我基于另一个问题的建议建立了这个建议,该问题建议类永远不要返回不同的函数。但是我想得越多,我就越不满意。每个类将实现完全相同的面向外部的方法,剩下的问题是实现细节。在我看来,用户应该调用num = MyTerm("2")并获取合适的子类,并且调用函数而不是实例化一个类会增加混乱。

由于我反对建议,我欢迎批评我的推理是错误的,但是我的技术问题是:实际上我该怎么做?类构造函数如何返回不同的类?

4 个答案:

答案 0 :(得分:2)

__new__可以实现,但是增加的功能并不值得。

天真:

class MyTerm:
    def __new__(cls, *args):
        return parseTerm(*args)

使用RecursionError将失败,因为在ParseTerm中创建子类对象将调用... MyTerm.__new__

好吧,让我们只委托object作为子类:

class MyTerm:
    def __new__(cls, *args):
        if cls is MyTerm:
            return parseTerm(*args)
        return object.__new__(cls)

它适用于"2",而不适用于"4/2",因为MyDivision.__init__被调用了两次:

  • 第一次在parseTerm内部使用参数“ 4”,“ 2”
  • MyTerm.__new__之后,Python机器第二次使用原始参数“ 4/2”

因此,您将必须允许MyDivision.__init__接受并忽略该呼叫:

class MyDivision(MyTerm):
    def __init__(self, n, d = None):
        print(self, n, d)
        if d is not None:
            self.numerator = n
            self.denominator = d

但这将允许MyDivision("4") ...因此,进行了一次新测试:

class MyDivision(MyTerm):
    def __init__(self, n, d = None):
        print(self, n, d)
        if d is not None:
            self.numerator = n
            self.denominator = d
        if not hasattr(self, denominator):
            raise TypeError("Missing required argument")

恕我直言,这不值得...

答案 1 :(得分:1)

您应该只调用一个函数,该函数执行确定应返回哪个类所需的逻辑,然后返回该子类。在这种情况下,num = parseTerm("2")是有意义的。将所有相似之处放在父类的init函数中,然后在子类的init中执行以下操作。

class MyDivision(MyTerm):
    def __init__(self, n, d):
        super(MyDivision, self).__init__()
        self.numerator = n
        self.denominator = d

super()将执行父类的init函数。

此外,我不认为在这里使用函数会引起混淆。如果命名足够好,就不会造成混乱。

term = create_new_term("2")

答案 2 :(得分:0)

我想说一种更干净的方法是定义一个Parser类,该类在其构造函数中使用该术语,并具有一个parseTerm函数,该函数返回要调用的类实例。

class Parser:

    def __init__(self, term):
        self.term = term

    def parseTerm(self):

        matches = re.match(r'^[0-9]+$', self.term)
        if matches is not None:
            return MyNum(self.term)

        matches = re.match(r'^([0-9]+)/([0-9]+)$', self.term)
        if matches is not None:
            return MyDivision(matches[1], matches[2])

然后,调用正确的MyTerm类将很简单:

terms = ["2", "2/3"]
for term in terms:
    obj = Parser(term).parseTerm()
    print(obj.latex())

输出为:

2
\frac { 2 } { 3 }

我们可以看到,将所有内容都委派给Parser后,它变得更加简单。我们可以为更多MyX类添加条件到parseTerm并获取对象。

答案 3 :(得分:0)

我会排除可能的匹配项,而改用命名匹配组:

valid_terms = [{'regex': r'^(?P<n>[0-9]+)$', 'class': MyNum},
               {'regex': r'^(?P<n>[0-9]+)/(?P<d>[0-9]+)$', 'class': MyDivision}]

def parseTerm(term):
    for valid_term in valid_terms:
        match = re.match(valid_term['regex'], term)
        if match is not None:
            return valid_term['class'](**match.groupdict())

    raise ValueError(f'{term} is not a valid term.')