Ладно, поиграв с этим еще немного, я думаю, что понял ... когда я впервые задал вопрос, я не знал о перегрузке оператора .
Итак, что происходит в этой сессии Python?
>>> from sympy import *
>>> x = Symbol(x)
>>> x + x
2*x
Оказывается, нет ничего особенного в том, как интерпретатор оценивает выражение; важно то, что питон переводит
x + x
в
x.__add__(x)
и Symbol наследуется от базового класса, который определяет __add__(self, other)
для возврата Add(self, other)
. (Эти классы можно найти в sympy.core.symbol, sympy.core.basic и sympy.core.add, если вы хотите посмотреть.)
Так, как говорил Иерув, Symbol.__add__()
имеет декоратор , называемый _sympifyit
, который в основном преобразует второй аргумент функции в выражение sympy перед вычислением функции, в процессе, возвращающем функцию, называемую __sympifyit_wrapper
это то, что я видел раньше.
Использование объектов для определения операций - довольно изящная концепция; Определяя свои собственные операторы и строковые представления, вы можете довольно легко реализовать тривиальную систему символической алгебры:
symbolic.py -
class Symbol(object):
def __init__(self, name):
self.name = name
def __add__(self, other):
return Add(self, other)
def __repr__(self):
return self.name
class Add(object):
def __init__(self, left, right):
self.left = left
self.right = right
def __repr__(self):
return self.left + '+' + self.right
Теперь мы можем сделать:
>>> from symbolic import *
>>> x = Symbol('x')
>>> x+x
x+x
С помощью небольшого рефакторинга он может быть легко расширен для обработки базовой арифметики :
class Basic(object):
def __add__(self, other):
return Add(self, other)
def __radd__(self, other): # if other hasn't implemented __add__() for Symbols
return Add(other, self)
def __mul__(self, other):
return Mul(self, other)
def __rmul__(self, other):
return Mul(other, self)
# ...
class Symbol(Basic):
def __init__(self, name):
self.name = name
def __repr__(self):
return self.name
class Operator(Basic):
def __init__(self, symbol, left, right):
self.symbol = symbol
self.left = left
self.right = right
def __repr__(self):
return '{0}{1}{2}'.format(self.left, self.symbol, self.right)
class Add(Operator):
def __init__(self, left, right):
self.left = left
self.right = right
Operator.__init__(self, '+', left, right)
class Mul(Operator):
def __init__(self, left, right):
self.left = left
self.right = right
Operator.__init__(self, '*', left, right)
# ...
Немного изменив настройку, мы можем получить то же поведение, что и в начале сессии sympy. Мы изменим Add
, чтобы он возвращал экземпляр Mul
, если его аргументы равны. Это немного сложнее, так как мы дошли до до создания экземпляра; мы должны использовать __new__()
вместо __init__()
:
class Add(Operator):
def __new__(cls, left, right):
if left == right:
return Mul(2, left)
return Operator.__new__(cls)
...
Не забудьте реализовать оператор равенства для символов:
class Symbol(Basic):
...
def __eq__(self, other):
if type(self) == type(other):
return repr(self) == repr(other)
else:
return False
...
И вуаля. В любом случае, вы можете подумать о других вещах, которые нужно реализовать, таких как приоритет операторов, оценка с заменой, расширенное упрощение, дифференцирование и т. Д., Но я думаю, что это очень круто, что основы настолько просты.