2010-07-07 23 views
16

¿Qué ocurre internamente cuando presiono Ingrese?¿Cómo funciona sympy? ¿Cómo interactúa con el shell interactivo de Python y cómo funciona el shell interactivo de Python?

Mi motivación para pedir, además de la curiosidad normal, es averiguar lo que sucede cuando

from sympy import * 

e introduzca una expresión. ¿Cómo pasar de Introduzca a llamar

__sympifyit_wrapper(a,b) 

en sympy.core.decorators? (Ese es el primer lugar que me llevó winpdb cuando intenté inspeccionar una evaluación.) Supongo que hay alguna función de evaluación incorporada a la que se llama normalmente, y se anula cuando se importa sympy.

+0

Nadie dio una respuesta satisfactoria a esta pregunta, y tengo mucha curiosidad. – Omnifarious

Respuesta

11

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.

+0

Ahh, entonces tu pregunta era cómo funcionaba Sympy, no cómo funcionaba el intérprete. Obtendrá la recompensa independientemente, pero realmente estaba esperando algunas descripciones detalladas de cómo el intérprete realmente hizo su trabajo. Supongo que esto no es demasiado complicado y probablemente involucre 'eval' y la biblioteca readline, pero era realmente curioso. Si esa pregunta mía no se responde, cambiaré el título de la pregunta para que sea más precisa. – Omnifarious

+0

Perdón por eso ... después de ver algunas de tus publicaciones, claramente sabes mucho más sobre pitón que yo y probablemente no obtuviste nada de tu generosidad:/erróneamente pensé que Sympy estaba haciendo algo mágico con la forma en que el intérprete evaluaba las expresiones, pero la 'magia' estaba solo en los operadores. Soy bastante nuevo en Python y en la programación en general, y no sabía que existiera la sobrecarga del operador. ¿Por qué no intentas escribir un intérprete de Python? No entendí los síntomas hasta que intenté escribir mi propio sistema de álgebra simbólica. – daltonb

+1

@secondbanana - Ese es un buen consejo. :-) Encuentro realmente interesante cuánto aprender es averiguar qué pregunta hacer. Hay muchas preguntas en StackOverflow donde es claro que alguien realmente tenía una pregunta diferente y en lugar de preguntar eso, hicieron otra pregunta porque estaban suponiendo una solución. "¿Por qué quieres hacer eso?" es una de las mejores preguntas de respuesta/aclaración. – Omnifarious

5

Acabo de inspeccionar el código de sympy (en http://github.com/sympy/sympy) y parece que __sympifyit_wrapper es un decorador. La razón por la que se llama es porque hay algún código en algún lugar que se parece a esto:

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

Y __sympifyit_wrapper es una envoltura que se devuelve por @_sympifyit. Si continúas con la depuración, es posible que hayas encontrado la función (en mi ejemplo, llamada func).

Recolecto en uno de los muchos módulos y paquetes importados en sympy/__init__.py, algunos códigos incorporados se reemplazan por versiones de Sympy. Estas versiones sombradas probablemente usan ese decorador.

exec según lo utilizado por >>> no se han reemplazado, los objetos que se operan habrán sido.

6

Esto no tiene mucho que ver con verdadera cuestión de secondbanana - es sólo un tiro en la generosidad de todo género;)

El intérprete en sí es bastante simple. Como cuestión de hecho, se podría escribir un sencillo (ni de lejos perfecta, no maneja excepciones, etc.) usted mismo:

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) 

Puede hacer la mayor parte de las cosas que estamos acostumbrados en la línea de lo normal :

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 magia real ocurre en el analizador/analizador.

Análisis léxico o lexing está dividiendo la entrada en tokens individuales. Los tokens son palabras clave o elementos "indivisibles". Por ejemplo, =, if, try, :, for, pass y import son todos tokens de Python. Para ver cómo Python tokeniza un programa, puede usar el módulo tokenize.

poner un cierto código en un archivo llamado 'test.py' y ejecute el siguiente en ese directorio:

de tokenize importación tokenize f = open ('test.py') tokenize (f.readline)

Para print "Hello World!" se obtiene el siguiente:

1,0-1,5: NOMBRE 'imprimir'
1,6-1,19: CADENA ' "hola mundo"'
1,19-1,20: nueva línea '\ n'
2,0-2,0: ENDMARKER ''

una vez que se tokenized el código, es parsed en un abstract syntax tree. El resultado final es una representación de bytecode python de su programa. Para print "Hello World!" se puede ver el resultado de este proceso:

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

Por supuesto todos los lenguajes lex, analizar, compilar y ejecutar sus programas. Python lexes, parses y compila a bytecode. Luego, el bytecode se "compila" (traducido podría ser más preciso) al código máquina que luego se ejecuta. Esta es la principal diferencia entre los lenguajes interpretados y compilados: los idiomas compilados se compilan directamente en el código de máquina de la fuente original, lo que significa que solo tiene que leer/analizar antes de la compilación y luego puede ejecutar el programa directamente. Esto significa tiempos de ejecución más rápidos (sin etapa lex/parse), pero también significa que para llegar a ese tiempo de ejecución inicial debe pasar mucho más tiempo porque se debe compilar todo el programa.

+0

Gracias. :-) Sabes, esta pregunta debería dividirse en dos. :-) – Omnifarious

+0

De nada y gracias. Y tienes razón, tal vez alguien con poderes mod puede hacer tal cosa. –

1

El intérprete interactivo de Python no hace mucho que sea diferente de cualquier otra vez que se ejecuta el código de Python. Tiene algo de magia para detectar excepciones y detectar declaraciones incompletas de líneas múltiples antes de ejecutarlas para que pueda terminar de escribirlas, pero eso es todo.

Si es realmente curioso, el estándar code module es una implementación bastante completa de la solicitud interactiva de Python. Creo que no es precisamente lo que Python realmente usa (es decir, creo, implementado en C), pero puedes profundizar en el directorio de la biblioteca del sistema de Python y realmente ver cómo se hace. El mío está en /usr/lib/python2.5/code.py

Cuestiones relacionadas