Tout droit après avoir joué avec lui un peu plus je pense que je l'ai .. quand j'ai d'abord posé la question je ne savais pas sur operator overloading.
Alors, que se passe-t-il dans cette session python?
>>> from sympy import *
>>> x = Symbol(x)
>>> x + x
2*x
Il s'avère qu'il n'y a rien de spécial sur la façon dont l'interpréteur évalue l'expression; la chose importante est que Python traduit
x + x
dans
x.__add__(x)
et symbole hérite de la classe de base, qui définit __add__(self, other)
pour revenir Add(self, other)
. (Ces classes sont trouvés dans sympy.core.symbol, sympy.core.basic et sympy.core.add si vous voulez jeter un coup d'oeil.)
Alors que Jerub disait, Symbol.__add__()
a un decorator appelé _sympifyit
qui convertit fondamentalement le deuxième argument d'une fonction en une expression sympatique avant d'évaluer la fonction, en retournant une fonction appelée __sympifyit_wrapper
qui est ce que j'ai vu auparavant.L'utilisation d'objets pour définir des opérations est un concept assez simple; en définissant vos propres opérateurs et des représentations de chaîne, vous pouvez mettre en œuvre un système d'algèbre symbolique trivial assez facilement:
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
Maintenant, nous pouvons faire:
>>> from symbolic import *
>>> x = Symbol('x')
>>> x+x
x+x
Avec un peu de refactoring il peut facilement être étendu pour gérer tous les 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)
# ...
Avec juste un peu plus de peaufinage nous pouvons obtenir le même comportement que la session sympy depuis le début .. nous allons modifier Add
donc il retourne une instance Mul
si ses arguments sont égaux. C'est un peu plus compliqué puisque nous y arrivons avant la création de l'instance; nous devons utiliser __new__()
instead of __init__()
:
class Add(Operator):
def __new__(cls, left, right):
if left == right:
return Mul(2, left)
return Operator.__new__(cls)
...
Ne pas oublier de mettre en œuvre l'opérateur d'égalité pour les symboles:
class Symbol(Basic):
...
def __eq__(self, other):
if type(self) == type(other):
return repr(self) == repr(other)
else:
return False
...
Et le tour est joué. Quoi qu'il en soit, vous pouvez penser à toutes sortes d'autres choses à implémenter, comme la priorité des opérateurs, l'évaluation avec substitution, la simplification avancée, la différenciation, etc., mais je pense que c'est plutôt cool que les bases soient si simples.
Personne n'a jamais donné une réponse satisfaisante à cette question, et je suis vraiment curieux. – Omnifarious