Como posso obter o código fonte de uma função Python?

407

Suponha que eu tenha uma função Python conforme definida abaixo:

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

Eu posso obter o nome da função usando foo.func_name. Como obtenho programaticamente seu código fonte, conforme digitei acima?

Vertexwahn
fonte
11
Observe que, no Python 3, você pode obter o nome da função usandofoo.__name__
MikeyE 29/04
Você pode obter muitas outras coisas também.
Not2qubit 21/1018

Respostas:

542

Se a função for de um arquivo de origem disponível no sistema de arquivos, inspect.getsource(foo)poderá ser útil :

Se fooé definido como:

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

Então:

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

Devoluções:

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

Mas acredito que, se a função for compilada a partir de uma string, fluxo ou importada de um arquivo compilado, você não poderá recuperar seu código-fonte.

Rafał Dowgird
fonte
2
Retorna uma tupla; tupla [0] é uma lista de cadeias que representam as linhas do código fonte e tupla [1] é o número da linha no contexto de execução em que foi executada. No IPython; este é o número da linha na célula e não o bloco de anotações
The Red Pea
12
Esta resposta não a menciona explicitamente, mas inspect.getsource (foo) retorna a fonte em uma única string em vez de em uma tupla, em que tupla [0] é uma lista das linhas. getSource será mais útil se você precisa para espiar na repl
Whaley
não funciona com, por exemplo, a função len. Onde posso encontrar o código fonte da lenfunção?
oaklander114
11
ouinspect.getsourcelines(foo)
Sławomir Lenart
11
@ oaklander113 inspect.getsource não funciona com built-ins como a maioria das funções da biblioteca padrão. Você pode verificar o código fonte do cpython no site deles
Nicolas Abril
183

O módulo inspecionar possui métodos para recuperar o código-fonte de objetos python. Aparentemente, só funciona se a fonte estiver localizada em um arquivo. Se você tivesse isso, acho que não precisaria obter a fonte do objeto.

runeh
fonte
3
Sim, parece funcionar apenas para objetos definidos em um arquivo. Não para aqueles definidos no intérprete.
sastanin
3
para minha surpresa, ele funciona em ipython / Jupyter cadernos também
Dr. Goulu
11
Eu tentei usar inspecionar em um python 3.5.3intérprete. import inspect+ inspect.getsource(foo)funcionou bem.
André C. Andersen
@ AndréChristofferAndersen Sim, mas ele não deve trabalhar para funções definidas no interpretador
alguém
86

dis é seu amigo se o código fonte não estiver disponível:

>>> 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
fonte
11
Lança um TypeError para builtins.
Noumenon
8
@Noumenon porque eles têm geralmente nenhum código-fonte em Python, eles são escritos em C
schlamar
83

Se você estiver usando IPython, precisará digitar "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
fonte
9
Muito útil para IPython e Jupyter notebook se / quando você acidentalmente excluir mais de uma célula que contém funções que você acabou de passar o dia criar e testar ....
AGS
Para quem, quem perdeu a classe inteira: você pode restaurá-lo método por método:, dir(MyClass)então MyClass.__init__??e assim por diante.
Valerij
61

Embora eu concorde geralmente que essa inspecté uma boa resposta, discordo que você não pode obter o código fonte dos objetos definidos no intérprete. Se você usar dill.source.getsourcefrom dill, poderá obter a fonte de funções e lambdas, mesmo se elas estiverem definidas interativamente. Ele também pode obter o código de métodos e funções de classe vinculados ou não definidos definidos em curries ... no entanto, talvez você não consiga compilar esse código sem o código do objeto que o encerra.

>>> 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
fonte
11
@ Ant6n: bem, isso é apenas ser sorrateiro. dill.source.getsourceinspeciona o histórico do intérprete em busca de funções, classes, lambdas etc. - ele não inspeciona o conteúdo das seqüências de caracteres transmitidas ao exec.
Mike McKerns
Isso parece muito interessante. É possível usar dillpara responder a esta pergunta: stackoverflow.com/questions/13827543/…
ArtOfWarfare
@ArtOfWarfare: parcialmente, sim. dill.sourcetem funções como getnamee importablee getsourceque o foco em obter o código fonte (ou um importable que os rendimentos do objeto) para um determinado objeto. Para coisas simples como um, intnão fonte, portanto não funciona como esperado (por exemplo, para 'a = 10', retorna '10').
Mike McKerns
No entanto, isso funciona para os globais: >>> a = 10; print( [key for key, val in globals().items() if val is a][0] )
Mike McKerns
@MikeMcKerns: Eu fiz o meu melhor para responder a essa pergunta sem usar dill, mas minha resposta deixa um pouco a desejar (ou seja, se você tiver vários nomes para o mesmo valor, não será possível descobrir qual foi usado. passar uma expressão, ela não pode dizer o que era essa expressão.Heck, se você passar uma expressão que é avaliada da mesma forma que um nome, ela fornecerá esse nome.) Pode dillresolver qualquer uma dessas deficiências da minha resposta aqui: stackoverflow.com/a/28634996/901641
ArtOfWarfare
21

Para expandir a resposta 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

EDIT: Como apontado por @ 0sh, este exemplo funciona usando, ipythonmas não é simples python. No entanto, deve ser bom para ambos quando importar código de arquivos de origem.

TomDotTom
fonte
2
Isso não funcionará, pois o intérprete compilaria foo em bytecode e jogaria fora o código fonte, gerando um OSError se você tentar executar getsource(foo).
Milo Wielondek
@ 0sh bom ponto no que diz respeito ao intérprete de baunilha python. No entanto, o exemplo de código acima funciona ao usar o IPython.
TomDotTom 26/06
11

Você pode usar o inspectmódulo para obter o código fonte completo para isso. Você precisa usar o getsource()método para isso no inspectmódulo. Por exemplo:

import inspect

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

print(inspect.getsource(get_my_code))

Você pode conferir mais opções no link abaixo. recuperar seu código python

Krutarth Gor
fonte
7

Como este post está marcado como duplicado deste outro post , respondo aqui pelo caso "lambda", embora o OP não seja sobre lambdas.

Portanto, para funções lambda que não são definidas em suas próprias linhas: além da resposta de marko.ristin , você pode usar o mini-lambda ou o SymPy, conforme sugerido nesta resposta .

  • mini-lambda é mais leve e suporta qualquer tipo de operação, mas funciona apenas para uma única variável
  • SymPyé mais pesado, mas muito mais equipado com operações matemáticas / de cálculo. Em particular, pode simplificar suas expressões. Ele também suporta várias variáveis ​​na mesma expressão.

Aqui está como você pode fazer isso 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))

Produz corretamente

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

x ** 2

Consulte a mini-lambda documentação para obter detalhes. Eu sou o autor, a propósito;)

smarie
fonte
5

Lembre-se de que as respostas aceitas funcionam apenas se o lambda for fornecido em uma linha separada. Se você o passar como argumento para uma função e desejar recuperar o código do lambda como objeto, o problema será um pouco complicado, pois inspectfornecerá toda a linha.

Por exemplo, considere um arquivo test.py:

import inspect

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

if __name__ == "__main__":
    main()

A execução dá a você (preste atenção à indentação!):

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

Para recuperar o código fonte do lambda, sua melhor aposta, na minha opinião, é analisar novamente o arquivo fonte inteiro (usando f.__code__.co_filename) e corresponder o nó AST lambda pelo número da linha e seu contexto.

Tivemos que fazer exatamente isso em nosso contrato de icon -library por contrato de design, pois tivemos que analisar as funções lambda que passamos como argumentos para os decoradores. É muito código para colar aqui, então dê uma olhada na implementação desta função .

marko.ristin
fonte
4

Se você mesmo estiver definindo estritamente a função e for uma definição relativamente curta, uma solução sem dependências seria definir a função em uma string e atribuir o eval () da expressão à sua função.

Por exemplo

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

opcionalmente, para anexar o código original à função:

func.source = funcstring
cherplunk
fonte
2
O uso de eval () me parece realmente, MUITO ruim, a menos que você esteja escrevendo algum tipo de interpretador interativo de Python. Eval abre problemas drásticos de segurança. Se você adota uma política de avaliar apenas literais de strings, ainda perde uma variedade de comportamentos úteis, que vão desde o destaque da sintaxe até a reflexão adequada das classes que contêm membros avaliados.
Mark E. Haase
2
Voto a favor. @ mehaase: segurança obviamente não é um problema aqui. Seus outros comentários são bastante relevantes, embora eu diria que a falta de destaque da sintaxe é uma combinação da falha do IDE e do fato de o python não ser uma linguagem homoicônica.
Ninjagecko
7
@ninjagecko A segurança é sempre um problema quando você aconselha o público em geral. A maioria dos leitores vem aqui porque estão pesquisando no Google. Eu não acho que muitas pessoas copiem esta resposta literalmente; em vez disso, eles vão pegar o conceito que aprenderam e aplicá-lo ao seu próprio problema.
Mark E. Haase
4

para resumir:

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

Eu acredito que os nomes de variáveis não são armazenados na PYC / PYD / pyo arquivos, para que você não pode recuperar as linhas de código exato, se você não tem arquivos de origem.

Abgan
fonte