Bien, después de jugar con eso creo que lo tengo ... cuando le pregunté por primera vez la pregunta que no sabía sobre operator overloading.
Entonces, ¿qué está pasando en esta sesión de python?
>>> from sympy import *
>>> x = Symbol(x)
>>> x + x
2*x
Resulta que no hay nada especial acerca de cómo el intérprete evalúa la expresión; lo importante es que Python se traduce
x + x
en
x.__add__(x)
y símbolo hereda de la clase base, que define __add__(self, other)
para volver Add(self, other)
. (Estas clases se encuentran en sympy.core.symbol, sympy.core.basic y sympy.core.add si quieres echar un vistazo.)
Así como Jerobaal estaba diciendo, Symbol.__add__()
tiene una llamada decorator_sympifyit
cuales básicamente convierte el segundo argumento de una función en una expresión sympy antes de evaluar la función, en el proceso devuelve una función llamada __sympifyit_wrapper
que es lo que vi antes.
Usar objetos para definir operaciones es un concepto bastante elegante; mediante la definición de sus propios operadores y representaciones de cadena se puede implementar un sistema de álgebra simbólica trivial con bastante facilidad:
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
Ahora podemos hacer:
>>> from symbolic import *
>>> x = Symbol('x')
>>> x+x
x+x
Con un poco de la refactorización se puede extender fácilmente para manejar todos 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)
# ...
Con solo un poco más de ajuste podemos obtener el mismo comportamiento que la sesión sympy desde el principio ... modificaremos Add
para que devuelva una instancia de Mul
si sus argumentos son iguales. Esto es un poco más complicado ya que hemos llegado a él antes de la creación de la instancia; tenemos que utilizar __new__()
instead of __init__()
:
class Add(Operator):
def __new__(cls, left, right):
if left == right:
return Mul(2, left)
return Operator.__new__(cls)
...
No se olvide de poner en práctica el operador de igualdad para los Símbolos:
class Symbol(Basic):
...
def __eq__(self, other):
if type(self) == type(other):
return repr(self) == repr(other)
else:
return False
...
y listo. De todos modos, puedes pensar en todo tipo de otras cosas para implementar, como la precedencia del operador, la evaluación con sustitución, la simplificación avanzada, la diferenciación, etc., pero creo que es genial que los conceptos básicos sean tan simples.
Nadie dio una respuesta satisfactoria a esta pregunta, y tengo mucha curiosidad. – Omnifarious