我正在尝试建立一个可以解析数学表达式的类(我知道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")
并获取合适的子类,并且调用函数而不是实例化一个类会增加混乱。
由于我反对建议,我欢迎批评我的推理是错误的,但是我的技术问题是:实际上我该怎么做?类构造函数如何返回不同的类?
答案 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.')