#python #python-3.x #class
#python #python-3.x #класс
Вопрос:
Я пытаюсь создать класс, который будет анализировать математические выражения (я знаю о 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")
и получал тот дочерний класс, который подходит, а вызов функции вместо создания экземпляра класса добавляет путаницы.
Я приветствую критику за то, что мои рассуждения неверны, поскольку я иду против советов, но мой технический вопрос таков: как мне на самом деле это сделать? Как конструктор класса возвращает другой класс?
Комментарии:
1. Я бы сказал, что создание объекта и получение чего-то другого создало бы путаницу. Вызов функции / метода этого не делает. Даже если бы это было возможно, я бы никогда так не поступил.
2. Можете ли вы поделиться определением
MyTerm
3. Текущий код в порядке, единственное, что вы можете получить, усложнив, это больше ошибок — в итоге клиентский код все равно будет выглядеть как вызов функции, приводящий к некоторому экземпляру класса.
4. Я бы сказал, что достаточно просто реализовать
Parser
класс, который заботится о разборе термина и возвращает правильныйMyTerm
подкласс. Проверьте, имеет ли смысл мой ответ @wyatt
Ответ №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")
ИМХО, оно того не стоит…
Комментарии:
1. Опасность игнорирования советов. Вы правы, я пропустил большую часть сложности, которую это повлечет за собой. Спасибо.
2. Поможет ли каким-либо образом добавление
Parser
класса, о котором я упоминал в своем ответе @wyatt @serge-ballesta3. @DeveshKumarSingh: Я так не думаю. Ожидается, что конструктор класса будет создавать объекты этого класса, точка. Любая попытка действовать по-другому закончится ужасным взломом со всеми оговорками.
4. Ааа, так вместо того, чтобы помещать
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()
выполнит функцию инициализации родительского класса.
Кроме того, я не думаю, что использование функции здесь сбивает с толку. Если вы назовете его достаточно хорошо, это не вызовет путаницы.
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.')