¿Cómo puedo obtener el código fuente de una función de Python?

407

Supongamos que tengo una función de Python como se define a continuación:

def foo(arg1,arg2):
    #do something with args
    a = arg1 + arg2
    return a

Puedo obtener el nombre de la función usando foo.func_name. ¿Cómo puedo obtener programáticamente su código fuente, como escribí anteriormente?

Vertexwahn
fuente
1
Tenga en cuenta que en Python 3 puede obtener el nombre de la función usandofoo.__name__
MikeyE
También puede obtener muchas otras cosas .
not2qubit

Respuestas:

543

Si la función es de un archivo fuente disponible en el sistema de archivos, entonces inspect.getsource(foo)podría ser de ayuda:

Si foose define como:

def foo(arg1,arg2):         
    #do something with args 
    a = arg1 + arg2         
    return a  

Entonces:

import inspect
lines = inspect.getsource(foo)
print(lines)

Devoluciones:

def foo(arg1,arg2):         
    #do something with args 
    a = arg1 + arg2         
    return a                

Pero creo que si la función se compila desde una cadena, secuencia o se importa desde un archivo compilado, entonces no puede recuperar su código fuente.

Rafał Dowgird
fuente
2
Devuelve una tupla; tuple [0] es una lista de cadenas que representan las líneas del código fuente, y tuple [1] es el número de línea en el contexto de ejecución donde se ejecutó. En IPython; este es el número de línea dentro de la celda, no el cuaderno
The Red Pea
12
Esta respuesta no lo menciona explícitamente, pero inspect.getsource (foo) devuelve la fuente en una sola cadena en lugar de una tupla donde tuple [0] es una lista de las líneas. getsource será más útil si necesita echar un vistazo a la
respuesta
no funciona con, por ejemplo, la función len. ¿Dónde puedo encontrar el código fuente de la lenfunción?
oaklander114
1
oinspect.getsourcelines(foo)
Sławomir Lenart
1
@ oaklander113 inspect.getsource no funciona con funciones integradas como la mayoría de las funciones de la biblioteca estándar. Puede consultar el código fuente de cpython en su sitio web o en su Github
Nicolas Abril
183

El módulo de inspección tiene métodos para recuperar el código fuente de los objetos python. Aparentemente, solo funciona si la fuente se encuentra en un archivo. Si tuviera eso, supongo que no necesitaría obtener la fuente del objeto.

runeh
fuente
3
Sí, parece funcionar solo para objetos definidos en un archivo. No para aquellos definidos en intérprete.
sastanin
3
para mi sorpresa, también funciona en los cuadernos Ipython / Jupyter
Dr. Goulu
1
Intenté usar inspeccionar en un python 3.5.3intérprete. import inspect+ inspect.getsource(foo)funcionó bien.
André C. Andersen
@ AndréChristofferAndersen Sí, pero no debería funcionar para las funciones definidas en el intérprete
alguien el
86

dis es tu amigo si el código fuente no está disponible:

>>> import dis
>>> def foo(arg1,arg2):
...     #do something with args
...     a = arg1 + arg2
...     return a
...
>>> dis.dis(foo)
  3           0 LOAD_FAST                0 (arg1)
              3 LOAD_FAST                1 (arg2)
              6 BINARY_ADD
              7 STORE_FAST               2 (a)

  4          10 LOAD_FAST                2 (a)
             13 RETURN_VALUE
Schlamar
fuente
1
Lanza un TypeError para builtins.
Noumenon
8
@Noumenon porque generalmente no tienen código fuente en Python, están escritos en C
schlamar el
83

Si está utilizando IPython, entonces debe escribir "foo ??"

In [19]: foo??
Signature: foo(arg1, arg2)
Source:
def foo(arg1,arg2):
    #do something with args
    a = arg1 + arg2
    return a

File:      ~/Desktop/<ipython-input-18-3174e3126506>
Type:      function
prashanth
fuente
99
Muy útil en el cuaderno IPython y Jupyter si / cuando elimina accidentalmente más de una celda que contiene funciones que acaba de pasar el día creando y probando ...
AGS
A quién, que perdió toda la clase: se puede restaurar método mediante el método: dir(MyClass), a continuación, MyClass.__init__??y así sucesivamente.
Valerij
61

Aunque generalmente estoy de acuerdo con que inspectes una buena respuesta, no estoy de acuerdo con que no pueda obtener el código fuente de los objetos definidos en el intérprete. Si usa dill.source.getsourcefrom dill, puede obtener la fuente de funciones y lambdas, incluso si se definen de forma interactiva. También puede obtener el código de los métodos y funciones de clase vinculados o no definidos definidos en curry ... sin embargo, es posible que no pueda compilar ese código sin el código del objeto adjunto.

>>> from dill.source import getsource
>>> 
>>> def add(x,y):
...   return x+y
... 
>>> squared = lambda x:x**2
>>> 
>>> print getsource(add)
def add(x,y):
  return x+y

>>> print getsource(squared)
squared = lambda x:x**2

>>> 
>>> class Foo(object):
...   def bar(self, x):
...     return x*x+x
... 
>>> f = Foo()
>>> 
>>> print getsource(f.bar)
def bar(self, x):
    return x*x+x

>>> 
Mike McKerns
fuente
1
@ Antón: bueno, eso es solo ser astuto. dill.source.getsourceinspecciona el historial del intérprete en busca de funciones, clases, lambdas, etc., no inspecciona el contenido de las cadenas pasadas a exec.
Mike McKerns
Esto parece muy interesante. ¿Es posible utilizar dillpara responder a esta pregunta: stackoverflow.com/questions/13827543/…
ArtOfWarfare
@ArtOfWarfare: parcialmente, sí. dill.sourcetiene funciones como getnamey importabley getsourceque se centran en obtener el código fuente (o un importable que produce el objeto) para cualquier objeto dado. Para cosas simples como un intno hay fuente, por lo que no funciona como se esperaba (es decir, para 'a = 10' devuelve '10').
Mike McKerns
Sin embargo, esto funciona para los globales: >>> a = 10; print( [key for key, val in globals().items() if val is a][0] )
Mike McKerns
@MikeMcKerns: he hecho todo lo posible para responder esa pregunta sin usarla dill, pero mi respuesta deja un poco que desear (es decir, si tiene varios nombres para el mismo valor, no puede averiguar cuál se usó). pasar una expresión, no puede decir cuál era esa expresión. Demonios, si pasa una expresión que se evalúa igual que un nombre, en su lugar le dará ese nombre.) Puede dillresolver cualquiera de esas deficiencias de mi respuesta aquí: stackoverflow.com/a/28634996/901641
ArtOfWarfare
21

Para ampliar la respuesta de runeh:

>>> def foo(a):
...    x = 2
...    return x + a

>>> import inspect

>>> inspect.getsource(foo)
u'def foo(a):\n    x = 2\n    return x + a\n'

print inspect.getsource(foo)
def foo(a):
   x = 2
   return x + a

EDITAR: Como lo señaló @ 0sh, este ejemplo funciona usando ipythonpero no es simple python. Sin embargo, debería estar bien en ambos al importar código de archivos fuente.

TomDotTom
fuente
2
Esto no funcionará, ya que el intérprete compilará foo para bytecode y tirará el código fuente, generando un OSError si intenta ejecutar getsource(foo).
Milo Wielondek
@ 0sh buen punto en lo que respecta al intérprete de python de vainilla. Sin embargo, el ejemplo de código anterior funciona cuando se usa IPython.
TomDotTom
11

Puede usar el inspectmódulo para obtener el código fuente completo para eso. Tienes que usar el getsource()método para eso desde el inspectmódulo. Por ejemplo:

import inspect

def get_my_code():
    x = "abcd"
    return x

print(inspect.getsource(get_my_code))

Puede consultar más opciones en el siguiente enlace. recupera tu código de Python

Krutarth Gor
fuente
7

Como esta publicación está marcada como el duplicado de esta otra publicación , respondo aquí para el caso "lambda", aunque el OP no se trata de lambdas.

Entonces, para las funciones lambda que no están definidas en sus propias líneas: además de la respuesta de marko.ristin , es posible que desee usar mini-lambda o SymPy como se sugiere en esta respuesta .

  • mini-lambda es más ligero y admite cualquier tipo de operación, pero solo funciona para una sola variable
  • SymPyes más pesado pero está mucho más equipado con operaciones matemáticas / de cálculo. En particular, puede simplificar sus expresiones. También admite varias variables en la misma expresión.

Así es como puedes hacerlo usando mini-lambda:

from mini_lambda import x, is_mini_lambda_expr
import inspect

def get_source_code_str(f):
    if is_mini_lambda_expr(f):
        return f.to_string()
    else:
        return inspect.getsource(f)

# test it

def foo(arg1, arg2):
    # do something with args
    a = arg1 + arg2
    return a

print(get_source_code_str(foo))
print(get_source_code_str(x ** 2))

Produce correctamente

def foo(arg1, arg2):
    # do something with args
    a = arg1 + arg2
    return a

x ** 2

Consulte la mini-lambda documentación para más detalles. Soy el autor por cierto;)

smarie
fuente
5

Tenga en cuenta que las respuestas aceptadas funcionan solo si la lambda se proporciona en una línea separada. Si lo pasa como argumento a una función y desea recuperar el código de lambda como objeto, el problema se vuelve un poco complicado ya que inspectle dará toda la línea.

Por ejemplo, considere un archivo test.py:

import inspect

def main():
    x, f = 3, lambda a: a + 1
    print(inspect.getsource(f))

if __name__ == "__main__":
    main()

Ejecutarlo te da (¡cuidado con la sangría!):

    x, f = 3, lambda a: a + 1

Para recuperar el código fuente de la lambda, su mejor opción, en mi opinión, es volver a analizar todo el archivo fuente (usando f.__code__.co_filename ) y hacer coincidir el nodo AST lambda por el número de línea y su contexto.

Tuvimos que hacer precisamente eso en nuestro resumen de iconos de la biblioteca de diseño por contrato ya que tuvimos que analizar las funciones lambda que pasamos como argumentos a los decoradores. Es demasiado código para pegar aquí, así que eche un vistazo a la implementación de esta función .

marko.ristin
fuente
4

Si está definiendo estrictamente la función usted mismo y es una definición relativamente corta, una solución sin dependencias sería definir la función en una cadena y asignar el eval () de la expresión a su función.

P.ej

funcstring = 'lambda x: x> 5'
func = eval(funcstring)

luego, opcionalmente, para adjuntar el código original a la función:

func.source = funcstring
Cherplunk
fuente
2
El uso de eval () me parece realmente, REALMENTE malo, a menos que esté escribiendo algún tipo de intérprete interactivo de Python. Eval abre drásticos problemas de seguridad. Si adopta una política de solo evaluar literales de cadena, aún pierde una variedad de comportamientos útiles, que van desde el resaltado de sintaxis hasta el reflejo adecuado de las clases que contienen miembros evaluados.
Mark E. Haase
2
Votación a favor. @mehaase: la seguridad obviamente no es un problema aquí. Sin embargo, sus otros comentarios son bastante relevantes, aunque yo diría que la falta de resaltado de sintaxis es una combinación de la falla del IDE y el hecho de que Python no es un lenguaje homoicónico.
ninjagecko
77
@ninjagecko La seguridad siempre es un problema cuando da consejos al público en general. La mayoría de los lectores vienen aquí porque están buscando preguntas en Google. No creo que mucha gente vaya a copiar esta respuesta literalmente; en cambio, tomarán el concepto que aprendieron y lo aplicarán a su propio problema.
Mark E. Haase
4

para resumir :

import inspect
print( "".join(inspect.getsourcelines(foo)[0]))
douardo
fuente
0

Yo creo que los nombres de variables no se almacenan en archivos pyc / pyd / pyo, por lo que no puede recuperar las líneas de código exacto si usted no tiene los archivos de origen.

Abgan
fuente