ฉันจะรับซอร์สโค้ดของฟังก์ชัน Python ได้อย่างไร


406

สมมติว่าฉันมีฟังก์ชัน Python ตามที่กำหนดไว้ด้านล่าง:

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

foo.func_nameฉันจะได้รับชื่อของฟังก์ชั่นที่ใช้ ฉันจะรับซอร์สโค้ดของโปรแกรมโดยวิธีตามที่ฉันพิมพ์ด้านบนได้อย่างไร



1
หมายเหตุใน Python 3 คุณจะได้รับชื่อฟังก์ชั่นการใช้งานfoo.__name__
MikeyE

คุณสามารถได้รับสิ่งต่าง ๆ มากมายเช่นกัน
not2qubit

คำตอบ:


541

หากฟังก์ชั่นนั้นมาจากไฟล์ต้นฉบับที่มีอยู่ในระบบไฟล์นั่นinspect.getsource(foo)อาจเป็นประโยชน์:

หากfooถูกกำหนดเป็น:

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

แล้ว:

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

ผลตอบแทน:

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

แต่ฉันเชื่อว่าหากฟังก์ชั่นนั้นรวบรวมจากสตริงสตรีมหรือนำเข้าจากไฟล์ที่คอมไพล์แล้วคุณไม่สามารถเรียกคืนรหัสต้นฉบับได้


2
ส่งคืน tuple tuple [0] คือรายการของสตริงที่แสดงถึงบรรทัดของซอร์สโค้ดและ tuple [1] เป็นหมายเลขบรรทัดในบริบทของการดำเนินการที่ถูกรัน ใน IPython นี่คือหมายเลขบรรทัดภายในเซลล์ไม่ใช่สมุดบันทึก
The Red Pea

12
คำตอบนี้ไม่ได้กล่าวถึงอย่างชัดเจน แต่ inspect.getsource (foo) จะส่งคืนแหล่งที่มาในสตริงเดียวแทนที่จะเป็น tuple โดยที่ tuple [0] เป็นรายการของบรรทัด getsource จะมีประโยชน์มากขึ้นถ้าคุณต้องการที่จะมองหาตัวแทน
whaley

lenมันไม่ได้ทำงานร่วมกับฟังก์ชั่นเช่น ฉันจะหาซอร์สโค้ดของlenฟังก์ชั่นได้จากที่ใด
oaklander114

1
หรือinspect.getsourcelines(foo)
Sławomir Lenart

1
@ oaklander113 inspect.getsource ไม่ทำงานกับบิวด์อินเช่นฟังก์ชันส่วนใหญ่จากไลบรารีมาตรฐาน คุณสามารถตรวจสอบซอร์สโค้ดของ cpython ได้ที่เว็บไซต์หรือGithub ของพวกเขา
Nicolas Abril

183

ตรวจสอบโมดูลมีวิธีการสำหรับการดึงรหัสที่มาจากวัตถุหลาม ดูเหมือนว่าจะใช้งานได้หากแหล่งที่มาอยู่ในไฟล์เท่านั้น หากคุณมีสิ่งที่ฉันคิดว่าคุณไม่จำเป็นต้องได้รับแหล่งที่มาจากวัตถุ


3
ใช่ดูเหมือนว่าจะทำงานเฉพาะกับวัตถุที่กำหนดไว้ในไฟล์ ไม่ใช่สำหรับผู้ที่กำหนดไว้ในล่าม
sastanin

3
แปลกใจของฉันมันทำงานในสมุดบันทึก Ipython / Jupyter ด้วย
ดร. Goulu

1
ฉันลองใช้การตรวจสอบในpython 3.5.3ล่าม import inspect+ inspect.getsource(foo)ทำงานได้ดี
André C. Andersen

@ AndréChristofferAndersenใช่ แต่มันไม่ควรใช้กับฟังก์ชั่นที่กำหนดในล่าม
บางคน

86

dis เป็นเพื่อนของคุณถ้ารหัสแหล่งที่มาไม่สามารถใช้ได้:

>>> 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

1
โยน TypeError สำหรับ builtins
Noumenon

8
@Noumenon เพราะพวกเขามักจะไม่มีซอร์สโค้ดใน Python พวกเขาเขียนใน C
schlamar

83

หากคุณใช้ IPython คุณต้องพิมพ์ "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

9
ประโยชน์มากในการ IPython และ Jupyter โน้ตบุ๊คถ้า / เมื่อคุณเผลอลบมากกว่าหนึ่งมือถือที่มีฟังก์ชั่นที่คุณได้ใช้เวลาเพียงวันสร้างและทดสอบ ....
AGS

สำหรับผู้ที่สูญเสียทั้งคลาส: คุณสามารถกู้คืนได้โดยวิธี: dir(MyClass)จากนั้นMyClass.__init__??เป็นต้น
Valerij

61

แม้ว่าโดยทั่วไปฉันจะเห็นด้วยว่าinspectเป็นคำตอบที่ดี แต่ฉันไม่เห็นด้วยที่คุณไม่สามารถรับซอร์สโค้ดของวัตถุที่กำหนดไว้ในล่าม หากคุณใช้dill.source.getsourceจากdillคุณจะได้รับแหล่งที่มาของฟังก์ชั่นและ lambdas แม้ว่าจะมีการกำหนดแบบโต้ตอบ นอกจากนี้ยังสามารถรับรหัสสำหรับจากวิธีการเรียนที่ถูกผูกไว้หรือไม่ถูกผูกไว้และในฟังก์ชัน curries ... อย่างไรก็ตามคุณอาจไม่สามารถรวบรวมรหัสนั้นโดยไม่ต้องใช้รหัสของวัตถุที่ล้อมรอบ

>>> 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

>>> 

1
@ Ant6n: ก็แค่ลับ ๆ ล่อๆ dill.source.getsourceตรวจสอบประวัติของล่ามสำหรับฟังก์ชั่นคลาส lambdas และอื่น ๆ - มันไม่ได้ตรวจสอบเนื้อหาของสตริงที่ส่งไปยัง exec
Mike McKerns

ดูเหมือนว่าน่าสนใจมาก เป็นไปได้ไหมที่จะใช้dillเพื่อตอบคำถามนี้: stackoverflow.com/questions/13827543/…
ArtOfWarfare

@ArtOfWarfare: บางส่วนใช่ dill.sourceมีฟังก์ชั่นเหมือนgetnameและimportableและgetsourceมุ่งเน้นในการรับรหัสที่มา (หรือ importable ที่อัตราผลตอบแทนวัตถุ) สำหรับวัตถุใดก็ตาม สำหรับสิ่งที่ง่ายเหมือนintมีไม่มีแหล่งที่มาดังนั้นจึงไม่สามารถทำงานได้ตามที่คาดไว้ (เช่นสำหรับ 'a = 10' มันกลับ '10')
Mike McKerns

สิ่งนี้ใช้ได้ผลกับ globals: >>> a = 10; print( [key for key, val in globals().items() if val is a][0] )
Mike McKerns

@MikeMcKerns: ฉันได้พยายามอย่างดีที่สุดที่จะตอบคำถามโดยไม่ใช้dillแต่คำตอบของฉันยังคงเป็นที่ต้องการ (IE ถ้าคุณมีชื่อหลายชื่อที่มีค่าเดียวกันก็ไม่สามารถคิดได้ว่าคุณใช้อะไรถ้าคุณ ผ่านไปในนิพจน์ไม่สามารถพูดได้ว่านิพจน์นั้นคืออะไรเฮ้ถ้าคุณผ่านนิพจน์ที่ประเมินชื่อเดียวกันมันจะให้ชื่อนั้นแทน) สามารถdillแก้ไขข้อบกพร่องใด ๆ ในคำตอบของฉันได้ ที่นี่: stackoverflow.com/a/28634996/901641
ArtOfWarfare

21

หากต้องการขยายคำตอบของ 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

แก้ไข: ในฐานะที่เป็นแหลมออกโดย @ 0sh ตัวอย่างนี้ทำงานโดยใช้แต่ไม่ธรรมดาipython pythonอย่างไรก็ตามควรจะใช้ได้ทั้งคู่เมื่อนำเข้ารหัสจากไฟล์ต้นฉบับ


2
นี้จะไม่ทำงานตั้งแต่ล่ามจะรวบรวม foo เพื่อ bytecode และทิ้งรหัสที่มายก OSError getsource(foo)ถ้าคุณลองใช้
Milo Wielondek

@ จุดดี 0sh เท่าที่ล่าวานิลลาหลามเป็นห่วง อย่างไรก็ตามตัวอย่างโค้ดข้างต้นใช้งานได้เมื่อใช้ IPython
TomDotTom

11

คุณสามารถใช้inspectโมดูลเพื่อรับซอร์สโค้ดแบบเต็มสำหรับสิ่งนั้น คุณต้องใช้getsource()วิธีการนั้นจากinspectโมดูล ตัวอย่างเช่น:

import inspect

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

print(inspect.getsource(get_my_code))

คุณสามารถตรวจสอบตัวเลือกเพิ่มเติมได้จากลิงค์ด้านล่าง รับรหัสหลามของคุณ


7

เนื่องจากโพสต์นี้ถูกทำเครื่องหมายว่าซ้ำกับโพสต์อื่นนี้ฉันตอบที่นี่สำหรับเคส "แลมบ์ดา" แม้ว่า OP จะไม่เกี่ยวกับแลมบ์ดา

ดังนั้นสำหรับฟังก์ชั่นแลมบ์ดาที่ไม่ได้กำหนดไว้ในสายของตนเอง: นอกเหนือจากคำตอบของmarko.ristinคุณอาจต้องการใช้mini-lambdaหรือใช้SymPyตามที่แนะนำในคำตอบนี้

  • mini-lambda มีน้ำหนักเบาและรองรับการทำงานทุกประเภท แต่ใช้ได้กับตัวแปรเดียวเท่านั้น
  • SymPyมีน้ำหนักมากขึ้น แต่มีความพร้อมมากขึ้นในการดำเนินการทางคณิตศาสตร์ / แคลคูลัส โดยเฉพาะอย่างยิ่งสามารถทำให้การแสดงออกของคุณง่ายขึ้น นอกจากนี้ยังรองรับตัวแปรหลายตัวในนิพจน์เดียวกัน

นี่คือวิธีที่คุณสามารถทำได้โดยใช้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))

มันให้ผลตอบแทนที่ถูกต้อง

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

x ** 2

ดูmini-lambda เอกสารประกอบสำหรับรายละเอียด ฉันเป็นผู้แต่งโดยวิธี;)


5

โปรดทราบว่าคำตอบที่ได้รับการยอมรับจะใช้ได้ก็ต่อเมื่อแลมบ์ดานั้นแยกกัน หากคุณส่งผ่านมันเป็นอาร์กิวเมนต์ให้กับฟังก์ชั่นและต้องการที่จะดึงรหัสของแลมบ์ดาเป็นวัตถุปัญหาจะยุ่งยากเล็กน้อยเนื่องจากinspectจะให้ทั้งบรรทัด

ตัวอย่างเช่นพิจารณาไฟล์test.py:

import inspect

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

if __name__ == "__main__":
    main()

การดำเนินการจะช่วยให้คุณ (ใจเยื้อง!):

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

ในการดึงซอร์สโค้ดของแลมบ์ดาทางออกที่ดีที่สุดของคุณคือการแยกวิเคราะห์ไฟล์ต้นฉบับทั้งหมด (โดยใช้f.__code__.co_filename) และจับคู่โหนดแลมบ์ดา AST ด้วยหมายเลขบรรทัดและบริบท

เราต้องทำอย่างแม่นยำว่าในไอคอนของห้องสมุดออกแบบตามสัญญาเนื่องจากเราต้องแยกฟังก์ชั่นแลมบ์ดาที่เราส่งผ่านเป็นอาร์กิวเมนต์สำหรับนักตกแต่ง มันเป็นรหัสที่มากเกินไปที่จะวางที่นี่ดังนั้นดูที่การใช้งานฟังก์ชั่นนี้


4

หากคุณกำหนดฟังก์ชั่นอย่างเคร่งครัดด้วยตัวคุณเองและเป็นคำจำกัดความที่ค่อนข้างสั้นโซลูชันที่ไม่มีการอ้างอิงจะเป็นการกำหนดฟังก์ชันในสตริงและกำหนด eval () ของนิพจน์ให้กับฟังก์ชันของคุณ

เช่น

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

จากนั้นเลือกที่จะแนบรหัสต้นฉบับเข้ากับฟังก์ชัน:

func.source = funcstring

2
การใช้ eval () ทำให้ฉันรู้สึกแย่มากจริงๆเว้นแต่คุณจะเขียนล่าม Python แบบโต้ตอบบางชนิด Eval เปิดปัญหาด้านความปลอดภัยที่รุนแรง หากคุณนำนโยบายการใช้ตัวอักษรสตริงมาเพียงอย่างเดียวคุณยังคงสูญเสียพฤติกรรมที่เป็นประโยชน์มากมายตั้งแต่การเน้นไวยากรณ์ไปจนถึงการสะท้อนที่เหมาะสมของคลาสที่มีสมาชิก eval'ed
Mark E. Haase

2
Upvoting @mehaase: ความปลอดภัยไม่เห็นได้ชัดว่าเป็นปัญหาที่นี่ ความคิดเห็นอื่น ๆ ของคุณมีความเกี่ยวข้องแม้ว่าฉันจะบอกว่าการขาดการเน้นไวยากรณ์คือการรวมกันของความผิดพลาดของ IDE และความจริงที่ว่าหลามไม่ใช่ภาษา homoiconic
ninjagecko

7
@ninjagecko Security เป็นปัญหาเสมอเมื่อคุณให้คำแนะนำแก่สาธารณชนทั่วไป ผู้อ่านส่วนใหญ่มาที่นี่เพราะพวกเขาเป็นคำถาม googling ฉันไม่คิดว่าหลายคนจะคัดลอกคำต่อคำนี้ แต่พวกเขาจะใช้แนวคิดที่เรียนรู้และนำไปใช้กับปัญหาของตนเอง
Mark E. Haase


0

ฉันเชื่อว่าชื่อตัวแปรไม่ได้ถูกจัดเก็บไว้ในไฟล์ pyc / pyd / pyo ดังนั้นคุณจึงไม่สามารถเรียกรหัสบรรทัดที่แน่นอนได้หากคุณไม่มีไฟล์ต้นฉบับ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.