¿Cómo funciona sympy? ¿Cómo interactúa con el shell interactivo de Python y cómo funciona el shell interactivo de Python?
scripting eval (4)
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 llamará 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 un contenedor que devuelve @_sympifyit
. Si continuaste tu depuración, es posible que hayas encontrado la función (en mi ejemplo llamado func
).
Me reúno en uno de los muchos módulos y paquetes importados en sympy/__init__.py
algunos sympy/__init__.py
incorporados se reemplazan con versiones de sympy. Estas versiones sympy probablemente usan ese decorador.
exec
según lo utilizado por >>>
no se habrá reemplazado, los objetos que se operan habrán sido reemplazados.
¿Qué pasa internamente cuando presiono Enter ?
Mi motivación para preguntar, además de la simple curiosidad, es averiguar qué sucede cuando
from sympy import *
y escribe una expresión. ¿Cómo va de Enter a Call?
__sympifyit_wrapper(a,b)
en sympy.core.decorators? (Ese fue el primer lugar en el que Winpdb me llevó cuando intenté inspeccionar una evaluación). Supongo que hay una función de evaluación incorporada a la que se llama normalmente y que se anula al importar sympy?
De acuerdo, después de jugar un poco más con él, creo que lo tengo ... cuando hice la pregunta que no sabía sobre la sobrecarga del operador .
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 en cómo el intérprete evalúa la expresión; Lo importante es que Python se traduce.
x + x
dentro
x.__add__(x)
y Symbol hereda de la clase Basic, que define __add__(self, other)
para devolver Add(self, other)
. (Estas clases se encuentran en sympy.core.symbol, sympy.core.basic y sympy.core.add si desea echar un vistazo).
Entonces, como decía Jerub, el Symbol.__add__()
tiene un decorator llamado _sympifyit
que básicamente convierte el segundo argumento de una función en una expresión simpy 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; Al definir sus propios operadores y representaciones de cadenas, puede implementar un sistema de álgebra simbólica trivial con bastante facilidad:
simbolico.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 refactorización se puede extender fácilmente para manejar toda la aritmética básica :
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 ajustes podemos obtener el mismo comportamiento que la sesión 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 antes de la creación de la instancia; tenemos que usar __new__()
lugar de __init__()
:
class Add(Operator):
def __new__(cls, left, right):
if left == right:
return Mul(2, left)
return Operator.__new__(cls)
...
No te olvides de implementar 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 voilá. De todos modos, puede 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 bastante bueno que los conceptos básicos sean tan simples.
El intérprete interactivo de Python no hace mucho que sea diferente de cualquier otro momento en que se ejecuta el código Python. Tiene algo de magia para detectar excepciones y detectar sentencias de varias líneas incompletas antes de ejecutarlas para que pueda terminar de escribirlas, pero eso es todo.
Si realmente tiene curiosidad, el módulo de código estándar es una implementación bastante completa del indicador interactivo de Python. Creo que no es precisamente lo que Python realmente usa (es decir, creo que implementado en C), pero puedes profundizar en el directorio de la biblioteca del sistema de Python y ver cómo se hace. El mío está en /usr/lib/python2.5/code.py
Esto no tiene mucho que ver con la verdadera pregunta de secondbanana, es solo un tiro a la recompensa de Omnifarious;)
El intérprete en sí es bastante simple. De hecho, usted podría escribir uno simple (nada perfecto, no maneja excepciones, etc.):
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)
Puedes hacer la mayoría de las cosas a las que estás acostumbrado en el indicador 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 verdadera magia sucede en el lexer / parser.
El análisis léxico o el lexing está dividiendo la entrada en tokens individuales. Los tokens son palabras clave o elementos "indivisibles". Por ejemplo, =
, if
, try
, :
, for
, pass
e import
son tokens de Python. Para ver cómo Python tokenizes un programa puede utilizar el módulo tokenize
.
Coloque algo de código en un archivo llamado ''test.py'' y ejecute lo siguiente en ese directorio:
desde tokenize import tokenize f = open (''test.py'') tokenize (f.readline)
Para print "Hello World!"
obtienes lo siguiente:
1,0-1,5: NOMBRE ''imprimir''
1,6-1,19: STRING ''"hola mundo"''
1,19-1,20: NEWLINE ''/ n''
2,0-2,0: ENDMARKER ''''
Una vez que el código es tokenizado, se parsed en un árbol de sintaxis abstracta . El resultado final es una representación de bytecode de Python de su programa. Para print "Hello World!"
Puedes 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 luego ejecutar sus programas. Python lexes, analiza y compila a bytecode. Luego, el código de bytes se "compila" (traducido podría ser más preciso) al código de máquina que luego se ejecuta. Esta es la principal diferencia entre los lenguajes interpretados y compilados: los lenguajes compilados se compilan directamente en código de máquina desde la fuente original, lo que significa que solo tiene que lex / parse antes de compilar 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 tiene que pasar mucho más tiempo porque se debe compilar todo el programa.