【问题标题】:Can a class constructor return a child class?类构造函数可以返回子类吗?
【发布时间】:2019-04-25 05:31:45
【问题描述】:

我正在尝试构建一个解析数学表达式的类(我知道 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") 并获取合适的子类似乎是合适的,并且调用函数而不是实例化类会增加混乱。

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

【问题讨论】:

  • 我会说创建一个对象并获得其他东西会造成混乱。调用函数/方法不会。即使有可能,我也不会那样做。
  • 你能分享MyTerm的定义吗
  • 当前的代码很好,你唯一可以通过复杂化获得的就是更多的错误 - 最终客户端代码仍然看起来像一个函数调用导致一些类实例。
  • 我想说只实现一个 Parser 类,它负责解析一个术语并返回正确的 MyTerm 子类就足够了。检查我的回答是否有意义@wyatt

标签: python python-3.x class


【解决方案1】:

__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”
  • Python 机器在 MyTerm.__new__ 之后第二次使用原始参数“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")

恕我直言,这不值得……

【讨论】:

  • 忽视建议的危险。你是对的,我错过了很多这将带来的复杂性。谢谢。
  • 将添加Parser 类,正如我在回答帮助中提到的那样@wyatt @serge-ballesta
  • @DeveshKumarSingh:我不这么认为。一个类的构造函数应该构建该类的对象,句号。任何采取不同行动的尝试都将以可怕的黑客攻击而告终。
  • 啊,所以不是放置parseTerm,而是返回__init__ 中的类对象将适用于Parser 类?
【解决方案2】:

您应该只调用一个函数来执行您确定应该返回哪个类所需的逻辑,然后返回该子类。在这种情况下,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")

【讨论】:

    【解决方案3】:

    我想说一个更简洁的方法是定义一个 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,变得更加简单。我们可以为 parseTerm 添加更多MyX 类的条件并获取对象。

    【讨论】:

      【解决方案4】:

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

      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.')
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-29
        • 2011-05-06
        • 2010-09-20
        • 1970-01-01
        相关资源
        最近更新 更多