2010-07-07 7 views
16

Que se passe-t-il en interne lorsque j'appuie sur Entrez?Comment fonctionne Sympy? Comment interagit-il avec le shell interactif Python, et comment fonctionne le shell interactif Python?

Ma motivation pour demander, en plus simple curiosité, est de savoir ce qui se passe lorsque vous

from sympy import * 

et entrez une expression. Comment aller de Entrez pour appeler

__sympifyit_wrapper(a,b) 

à sympy.core.decorators? (C'est le premier endroit winpdb m'a pris quand j'ai essayé d'inspecter une évaluation.) Je suppose qu'il y a une fonction eval intégrée qui est appelée normalement, et est substituée lorsque vous importez le sympy?

+0

Personne n'a jamais donné une réponse satisfaisante à cette question, et je suis vraiment curieux. – Omnifarious

Répondre

11

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.

+0

Ahh, alors votre question était de savoir comment fonctionnait Sympy, pas comment l'interprète travaillait. Vous obtiendrez la prime, mais j'espérais vraiment des descriptions détaillées de la façon dont l'interprète a fait son travail. Je suppose que ce n'est pas trop compliqué et implique probablement 'eval' et la bibliothèque readline, mais j'étais vraiment curieux. Si je ne réponds pas à cette question, je vais changer le titre de la question pour être plus précis. – Omnifarious

+0

Désolé pour ça ... après avoir regardé quelques-uns de vos articles, vous en savez beaucoup plus sur Python que moi et vous n'avez probablement rien trouvé de plus:/J'ai pensé que sympy faisait quelque chose de magique avec la façon dont l'interprète évaluait les expressions, mais la «magie» était juste dans les opérateurs. Je suis assez nouveau pour Python et la programmation en général, et je ne savais pas que la surcharge de l'opérateur existait. Pourquoi n'essaies-tu pas d'écrire un interpréteur python? Je n'ai pas compris sympé jusqu'à ce que j'essaie d'écrire mon propre système d'algèbre symbolique. – daltonb

+1

@secondbanana - C'est un bon conseil. :-) Je trouve vraiment intéressant de voir combien d'apprentissage consiste à déterminer quelle question poser. Il y a beaucoup de questions sur StackOverflow où il est clair que quelqu'un avait une question différente et au lieu de poser cette question, ils ont posé une autre question parce qu'ils supposaient une solution. "Pourquoi est ce que tu veux faire ça?" est l'une des meilleures questions de réponse/clarification. – Omnifarious

5

Je viens d'inspecter le code de sympy (à http://github.com/sympy/sympy) et il semble que __sympifyit_wrapper est un décorateur. La raison pour laquelle il est appelé parce qu'il ya quelque part un code qui ressemble à ceci:

class Foo(object): 
    @_sympifyit 
    def func(self): 
     pass 

Et __sympifyit_wrapper est une enveloppe qui est retourné par @_sympifyit. Si vous avez poursuivi le débogage, vous avez peut-être trouvé la fonction (dans mon exemple, func).

Je rassemble dans l'un des nombreux modules et paquets importés en sympy/__init__.py certains codes intégrés sont remplacés par des versions sympy. Ces versions sympas utilisent probablement ce décorateur.

exec comme utilisé par >>> n'aura pas été remplacé, les objets qui ont opéré auront été.

6

Cela n'a pas grand-chose à voir avec réel question secondbanana - il est juste un coup à la générosité de très varié;)

L'interprète lui-même est assez simple. En fait, vous pouvez écrire un simple (loin d'être parfait, ne gère pas les exceptions, etc.) vous:

print "Wayne's Python Prompt" 

def getline(prompt): 
    return raw_input(prompt).rstrip() 

myinput = '' 

while myinput.lower() not in ('exit()', 'q', 'quit'): 
    myinput = getline('>>> ') 
    if myinput: 
     while myinput[-1] in (':', '\\', ','): 
      myinput += '\n' + getline('... ') 
     exec(myinput) 

Vous pouvez faire la plupart des choses que vous êtes habitué à l'invite normale :

Waynes Python Prompt 
>>> print 'hi' 
hi 
>>> def foo(): 
...  print 3 
>>> foo() 
3 
>>> from dis import dis 
>>> dis(foo) 
    2   0 LOAD_CONST    1 (3) 
       3 PRINT_ITEM 
       4 PRINT_NEWLINE 
       5 LOAD_CONST    0 (None) 
       8 RETURN_VALUE 
>>> quit 
Hit any key to close this window... 

La vraie magie se produit dans le lexer/parser.

L'analyse lexicale ou lexing fractionne l'entrée en jetons individuels. Les jetons sont des mots-clés ou des éléments "indivisibles". Par exemple, =, if, try, :, for, pass, et import sont tous des jetons Python. Pour voir comment Python symbolise un programme, vous pouvez utiliser le module tokenize.

Mettez un peu de code dans un fichier appelé 'test.py' et exécutez ce qui suit dans ce répertoire:

de tokenize import tokenize f = open ('test.py') tokenize (f.readline)

Pour print "Hello World!" vous obtenez les éléments suivants:

1,0-1,5: NOM 'print'
1,6-1,19: STRING ' "Bonjour tout le monde"'
1,19-1,20: NEWLINE '\ n'
2,0-2,0: balisefin ''

une fois le code tokenizé, il est parsed dans un abstract syntax tree. Le résultat final est une représentation bytecode python de votre programme. Pour print "Hello World!" vous pouvez voir le résultat de ce processus:

from dis import dis 
def heyworld(): 
    print "Hello World!" 
dis(heyworld) 

Bien sûr, toutes les langues lex, analyser, compiler et exécuter ensuite leurs programmes. Python lexe, analyse et compile en bytecode. Alors le bytecode est "compilé" (traduit peut-être plus précis) en code machine qui est ensuite exécuté. Ceci est la principale différence entre les langages interprétés et compilés - les langages compilés sont compilés directement dans le code machine à partir de la source originale, ce qui signifie que vous devez seulement faire lex/parse avant la compilation et ensuite exécuter directement le programme. Cela signifie des temps d'exécution plus rapides (pas d'étape lex/parse), mais cela signifie aussi que pour atteindre ce temps d'exécution initial, vous devez passer beaucoup plus de temps car le programme entier doit être compilé.

+0

Merci. :-) Vous savez, cette question devrait être scindée en deux. :-) – Omnifarious

+0

De rien et merci. Et vous avez raison, peut-être quelqu'un avec des pouvoirs de mod peut faire une telle chose. –

1

L'interpréteur interactif Python ne fait pas beaucoup de différence avec le code Python. Il a un peu de magie pour attraper les exceptions et pour détecter les instructions multilignes incomplètes avant de les exécuter afin que vous puissiez finir de les taper, mais c'est à peu près tout.

Si vous êtes vraiment curieux, le standard code module est une implémentation assez complète de l'invite interactive Python. Je pense que ce n'est pas exactement ce que Python utilise réellement (c'est-à-dire, je crois, implémenté en C), mais vous pouvez creuser dans le répertoire de la bibliothèque de votre système Python et regarder comment ça fonctionne. Le mien à /usr/lib/python2.5/code.py

Questions connexes