Сразу после игры с ним еще несколько я думаю, что у меня есть. Когда я впервые задал вопрос, который я не знал о operator overloading.
Итак, что происходит в этой сессии 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, если вы хотите посмотреть.)
Итак, как Jerub говорил, Symbol.__add__()
имеет decorator под названием _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
С небольшим рефакторинга его можно легко расширить, чтобы обрабатывать все basic arithmetic:
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__()
instead of __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
...
и вуаля. В любом случае, вы можете придумать всевозможные другие вещи для реализации, такие как приоритет оператора, оценку с заменой, расширенное упрощение, дифференциацию и т. Д., Но я думаю, что это довольно круто, что основы настолько просты.
Никто никогда не давал удовлетворительного ответа на этот вопрос, и мне действительно интересно. – Omnifarious