Bagaimana saya bisa mendapatkan kode sumber fungsi Python?

407

Misalkan saya memiliki fungsi Python seperti yang didefinisikan di bawah ini:

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

Saya bisa mendapatkan nama fungsi menggunakan foo.func_name. Bagaimana saya bisa mendapatkan kode sumber secara terprogram, seperti yang saya ketikkan di atas?

Vertexwahn
sumber
1
Catatan, dalam Python 3 Anda bisa mendapatkan nama fungsi menggunakanfoo.__name__
MikeyE
Anda bisa mendapatkan banyak hal lainnya juga.
not2qubit

Jawaban:

542

Jika fungsinya dari file sumber yang tersedia di sistem file, maka inspect.getsource(foo) mungkin bisa membantu:

Jika foodidefinisikan sebagai:

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

Kemudian:

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

Pengembalian:

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

Tetapi saya percaya bahwa jika fungsi dikompilasi dari string, stream atau diimpor dari file yang dikompilasi, maka Anda tidak dapat mengambil kode sumbernya.

Rafał Dowgird
sumber
2
Mengembalikan tuple; tuple [0] adalah daftar string yang mewakili baris kode sumber, dan tuple [1] adalah nomor baris dalam konteks eksekusi di mana ia dijalankan. Dalam IPython; ini adalah nomor baris di dalam sel, bukan keseluruhan buku catatan
The Red Pea
12
Jawaban ini tidak secara eksplisit menyebutkannya, tetapi inspect.getsource (foo) mengembalikan sumber dalam string tunggal alih-alih tuple di mana tuple [0] adalah daftar baris. mendapat sumber akan lebih berguna jika Anda perlu mengintip di repl
whaley
itu tidak berfungsi dengan mis fungsi len. Di mana saya dapat menemukan kode sumber untuk lenfungsi ini?
oaklander114
1
atauinspect.getsourcelines(foo)
Sławomir Lenart
1
@ oaklander113 inspect.getsource tidak berfungsi dengan built-in seperti kebanyakan fungsi dari perpustakaan standar. Anda dapat memeriksa kode sumber untuk cpython di situs web mereka
Nicolas Abril
183

The memeriksa modul memiliki metode untuk mengambil kode sumber dari objek python. Tampaknya itu hanya berfungsi jika sumbernya berada dalam file sekalipun. Jika Anda punya itu saya kira Anda tidak perlu mendapatkan sumber dari objek.

runeh
sumber
3
Ya, tampaknya hanya berfungsi untuk objek yang ditentukan dalam file. Bukan untuk yang didefinisikan dalam juru bahasa.
sastanin
3
yang mengejutkan saya, ia bekerja di notebook Ipython / Jupyter juga
Dr. Goulu
1
Saya mencoba menggunakan inspeksi dalam python 3.5.3juru bahasa. import inspect+ inspect.getsource(foo)bekerja dengan baik.
André C. Andersen
@ AndréChristofferAndersen Ya tapi itu seharusnya tidak berfungsi untuk fungsi-fungsi yang didefinisikan dalam penerjemah
seseorang
86

dis adalah teman Anda jika kode sumber tidak tersedia:

>>> 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
sumber
1
Melempar TypeError untuk bawaan.
Noumenon
8
@ Noumenon karena mereka biasanya tidak memiliki kode sumber dalam Python, mereka ditulis dalam C
schlamar
83

Jika Anda menggunakan IPython, maka Anda perlu mengetik "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
sumber
9
Sangat membantu dalam IPython dan notebook Jupyter jika / ketika Anda secara tidak sengaja menghapus lebih dari satu sel yang berisi fungsi-fungsi yang baru saja Anda habiskan hari itu untuk membuat dan menguji ....
AGS
Kepada siapa, yang kehilangan seluruh kelas: Anda dapat memulihkannya metode dengan metode:, dir(MyClass)lalu MyClass.__init__??dan seterusnya.
Valerij
61

Meskipun saya umumnya setuju bahwa itu inspectadalah jawaban yang baik, saya tidak setuju bahwa Anda tidak bisa mendapatkan kode sumber objek yang didefinisikan dalam interpreter. Jika Anda menggunakan dill.source.getsourcedari dill, Anda bisa mendapatkan sumber fungsi dan lambda, bahkan jika mereka didefinisikan secara interaktif. Ia juga bisa mendapatkan kode dari metode dan fungsi kelas yang diikat atau tidak terikat yang didefinisikan dalam kari ... namun, Anda mungkin tidak dapat mengkompilasi kode itu tanpa kode objek yang dilampirkan.

>>> 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
sumber
1
@ Ant6n: yah, itu hanya licik. dill.source.getsourcememeriksa sejarah penerjemah untuk fungsi, kelas, lambdas, dll - itu tidak memeriksa konten string yang diteruskan ke exec.
Mike McKerns
Ini sepertinya sangat menarik. Apakah mungkin digunakan dilluntuk menjawab pertanyaan ini: stackoverflow.com/questions/13827543/…
ArtOfWarfare
@ ArtOfWarfare: sebagian, ya. dill.sourcememiliki fungsi seperti getnamedan importabledan getsourceyang berfokus pada mendapatkan kode sumber (atau yang dapat diimpor yang menghasilkan objek) untuk objek yang diberikan. Untuk hal-hal sederhana seperti inttidak ada sumber, sehingga tidak berfungsi seperti yang diharapkan (yaitu untuk 'a = 10' ia mengembalikan '10').
Mike McKerns
Namun ini berlaku untuk global: >>> a = 10; print( [key for key, val in globals().items() if val is a][0] )
Mike McKerns
@MikeMcKerns: Saya sudah melakukan yang terbaik untuk menjawab pertanyaan itu tanpa menggunakan dill, tetapi jawaban saya meninggalkan sedikit yang diinginkan (yaitu, jika Anda memiliki beberapa nama untuk nilai yang sama, itu tidak dapat mencari tahu yang digunakan. Jika Anda lewat sebuah ekspresi, itu tidak bisa mengatakan apa ekspresi itu. Heck, jika Anda memasukkan ekspresi yang mengevaluasi sama dengan nama, itu akan memberikan nama itu sebagai gantinya.) Dapat dillmemecahkan semua kekurangan jawaban saya di sini: stackoverflow.com/a/28634996/901641
ArtOfWarfare
21

Untuk memperluas jawaban 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: Seperti yang ditunjukkan oleh @ 0sh contoh ini berfungsi menggunakan ipythontetapi tidak sederhana python. Namun, keduanya harus baik ketika mengimpor kode dari file sumber.

TomDotTom
sumber
2
Ini tidak akan berhasil, karena penerjemah akan mengkompilasi foo untuk bytecode dan membuang kode sumber, meningkatkan OSError jika Anda mencoba menjalankan getsource(foo).
Milo Wielondek
@ 0sh poin bagus sejauh menyangkut interpreter vanilla python. Namun contoh kode di atas berfungsi ketika menggunakan IPython.
TomDotTom
11

Anda dapat menggunakan inspectmodul untuk mendapatkan kode sumber lengkap untuk itu. Anda harus menggunakan getsource()metode untuk itu dari inspectmodul. Sebagai contoh:

import inspect

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

print(inspect.getsource(get_my_code))

Anda dapat memeriksanya lebih banyak opsi pada tautan di bawah ini. ambil kode python Anda

Krutarth Gor
sumber
7

Karena pos ini ditandai sebagai duplikat dari pos lain ini , saya jawab di sini untuk kasus "lambda", meskipun OP bukan tentang lambda.

Jadi, untuk fungsi lambda yang tidak didefinisikan dalam baris mereka sendiri: selain jawaban marko.ristin , Anda mungkin ingin menggunakan mini-lambda atau menggunakan SymPy seperti yang disarankan dalam jawaban ini .

  • mini-lambda lebih ringan dan mendukung segala jenis operasi, tetapi hanya berfungsi untuk satu variabel
  • SymPylebih berat tetapi jauh lebih dilengkapi dengan operasi matematika / kalkulus. Secara khusus itu dapat menyederhanakan ekspresi Anda. Ini juga mendukung beberapa variabel dalam ekspresi yang sama.

Berikut ini cara melakukannya 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))

Ini menghasilkan dengan benar

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

x ** 2

Lihat mini-lambda dokumentasi untuk detailnya. Aku pengarangnya;)

nyonya
sumber
5

Harap diingat bahwa jawaban yang diterima hanya berfungsi jika lambda diberikan pada baris terpisah. Jika Anda meneruskannya sebagai argumen ke fungsi dan ingin mengambil kode lambda sebagai objek, masalahnya menjadi sedikit rumit karena inspectakan memberi Anda seluruh baris.

Misalnya, pertimbangkan sebuah file test.py:

import inspect

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

if __name__ == "__main__":
    main()

Melaksanakannya memberi Anda (ingatlah indensi!):

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

Untuk mengambil kode sumber lambda, taruhan terbaik Anda, menurut saya, adalah mengurai kembali seluruh file sumber (dengan menggunakan f.__code__.co_filename) dan mencocokkan node lambda AST dengan nomor baris dan konteksnya.

Kami harus melakukan tepat bahwa di perpustakaan kami desain-by-kontrak icontract karena kami harus mengurai fungsi lambda kita lulus sebagai argumen untuk dekorator. Terlalu banyak kode untuk disisipkan di sini, jadi lihat implementasi dari fungsi ini .

marko.ristin
sumber
4

Jika Anda benar-benar mendefinisikan fungsi sendiri dan itu adalah definisi yang relatif singkat, solusi tanpa dependensi adalah mendefinisikan fungsi dalam sebuah string dan menetapkan eval () ekspresi untuk fungsi Anda.

Misalnya

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

kemudian secara opsional untuk melampirkan kode asli ke fungsi:

func.source = funcstring
keriput
sumber
2
Penggunaan eval () menurut saya benar-benar, BENAR-BENAR buruk, kecuali jika Anda menulis semacam juru bahasa Python interaktif. Eval membuka masalah keamanan yang drastis. Jika Anda mengadopsi kebijakan eval'ing string literal, Anda masih kehilangan berbagai perilaku yang membantu, mulai dari penyorotan sintaksis hingga refleksi kelas yang tepat yang berisi anggota yang dievaluasi.
Mark E. Haase
2
Upvoting. @mease: keamanan jelas bukan masalah di sini. Komentar Anda yang lain cukup relevan, meskipun saya akan mengatakan kurangnya penyorotan sintaks adalah kombinasi dari kesalahan IDE dan fakta bahwa python bukan bahasa homoikonik.
ninjagecko
7
@ninjagecko Keamanan selalu menjadi masalah saat Anda memberikan saran kepada masyarakat umum. Sebagian besar pembaca datang ke sini karena mereka googling pertanyaan. Saya tidak berpikir banyak orang akan menyalin jawaban ini kata demi kata; sebaliknya, mereka akan mengambil konsep yang mereka pelajari dan menerapkannya pada masalah mereka sendiri.
Mark E. Haase
4

untuk meringkas :

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

Saya percaya bahwa nama variabel tidak disimpan dalam file pyc / pyd / pyo, jadi Anda tidak dapat mengambil baris kode yang tepat jika Anda tidak memiliki file sumber.

Abgan
sumber