2012-04-06 31 views
13

xem xét mã python sau:Python mẫn: tên hàm truy cập và docstring bên trong định nghĩa hàm

def function(): 
    "Docstring" 

    name = ??? 
    doc = ??? 

    return name, doc 

>>> function() 
"function", "Docstring" 

gì tôi cần phải thay thế các dấu hỏi với vì vậy mà tôi có được tên và docstring của hàm từ bên trong cùng một chức năng?

EDIT: Hầu hết các câu trả lời cho đến nay rõ ràng mã hóa tên của hàm bên trong định nghĩa của nó. Có thể làm một cái gì đó như dưới đây, nơi một hàm get_name_doc mới sẽ truy cập chức năng từ khung bên ngoài mà từ đó nó được gọi, và trả về tên và tài liệu của nó?

def get_name_doc(): 
    ??? 

def function(): 
    "Docstring" 

    name, doc = get_name_doc() 

    return name, doc 

>>> function() 
"function", "Docstring" 

Trả lời

13

Điều này không thể làm sạch một cách nhất quán vì tên có thể được thay đổi và gán lại.

Tuy nhiên, bạn có thể sử dụng điều này miễn là chức năng không được đổi tên hoặc trang trí.

>>> def test(): 
...  """test""" 
...  doc = test.__doc__ 
...  name = test.__name__ 
...  return doc, name 
... 
>>> test() 
('test', 'test') 
>>> 

Hoàn toàn không đáng tin cậy. Đây là một ví dụ của nó đi sai.

>>> def dec(f): 
...  def wrap(): 
...   """wrap""" 
...   return f() 
...  return wrap 
... 
>>> @dec 
... def test(): 
...  """test""" 
...  return test.__name__, test.__doc__ 
... 
>>> test() 
('wrap', 'wrap') 
>>> 

Điều này là do tên test không được xác định tại thời điểm hàm thực sự được tạo và tham chiếu toàn cục trong hàm. Nó do đó được nhìn lên trong phạm vi toàn cầu trên mọi thực thi. Vì vậy, thay đổi tên trong phạm vi toàn cầu (chẳng hạn như trang trí) sẽ phá vỡ mã của bạn.

+1

Trong trường hợp đó, có cách nào để nhận chuỗi ký tự 'đúng' không? Bất kỳ công việc xung quanh giải pháp cho vấn đề? – George

+0

@George, xem phần cuối bài đăng của tôi để biết ý tưởng. Tôi đã chơi xung quanh với những thứ như vậy chỉ cần hack xung quanh nhưng chưa bao giờ có thể làm cho nó làm việc ở một mức độ mà tôi coi là sản xuất đã sẵn sàng. – aaronasterling

+0

Về 'inspect.stack()' trên liên kết này: http://stackoverflow.com/questions/900392/getting-the-caller-function-name-inside-another-function-in-python, làm thế nào anh biết chỉ mục nào để truy cập ngăn xếp? Tôi cho rằng đó không phải là một quyền đoán? Tôi quan tâm nếu bạn có thể tự động tìm ra 'chồng [x] [y]' để truy cập? – George

1

Bạn cần phải sử dụng tên của các chức năng để có được nó:

def function(): 
    "Docstring" 

    name = function.__name__ 
    doc = function.__doc__ 

    return name, doc 

Ngoài ra còn có một module gọi là kiểm tra: http://docs.python.org/library/inspect.html. Điều này rất hữu ích để có thêm thông tin về hàm (hoặc bất kỳ đối tượng python nào).

+0

Có thể làm điều đó mà không sử dụng tên một cách rõ ràng? – dzhelil

+2

Python có nghĩa là phải rõ ràng. – jamylak

+1

Câu trả lời này sai và không đáng tin cậy. Chỉ cần thử áp dụng một trang trí. – aaronasterling

1
def function(): 
    "Docstring" 

    name = function.__name__ 
    doc = function.__doc__ 

    return name, doc  

Điều này nên làm điều đó, sử dụng tên của hàm, trong trường hợp của bạn là function.

Đây là một hướng dẫn rất đẹp mà nói về nó: http://epydoc.sourceforge.net/docstrings.html

Và tất nhiên: http://docs.python.org/tutorial/controlflow.html#documentation-strings

Edit: Tham khảo phiên bản chỉnh sửa của bạn trong những câu hỏi, tôi nghĩ bạn có thể phải gây rối với inspect.stack()from this SO question. Câu trả lời của Ayman Hourieh đưa ra một ví dụ nhỏ.

1
>>> def function(): 
     "Docstring" 

     name = function.__name__ 
     doc = function.__doc__ 

     return name, doc 

>>> function() 
('function', 'Docstring') 
1

Điều này sẽ tìm tên và tài liệu của hàm gọi là get_doc. Trong cảm giác của tôi, get_doc nên có chức năng như là đối số (mà có thể đã làm cho nó thực sự dễ dàng hơn, nhưng cách thú vị ít hơn để đạt được;))

import inspect 

def get_doc(): 
    """ other doc 
    """ 
    frame = inspect.currentframe() 

    caller_frame = inspect.getouterframes(frame)[1][0] 
    caller_name = inspect.getframeinfo(caller_frame).function 
    caller_func = eval(caller_name) 

    return caller_name, caller_func.__doc__ 


def func(): 
    """ doc string """ 
    print get_doc() 
    pass 


def foo(): 
    """ doc string v2 """ 
    func() 

def bar(): 
    """ new caller """ 
    print get_doc() 

func() 
foo() 
bar() 
+0

cần kiểm tra xem chức năng gọi có được trang trí hay không, nhưng không phải là vấn đề vì trang trí phải là khung tiếp theo và không phải là –

+0

trước đó Nếu bạn không có khung 'del', nó sẽ tạo chu trình. – aaronasterling

+0

@ cũng vậy, không thành công với trình trang trí vì bạn chỉ sử dụng 'eval' để đánh giá tên trong phạm vi toàn cục. Vì vậy, đây là câu trả lời giống như tất cả những người khác đã đưa ra, ngoại trừ bạn sử dụng 'inspect' để lấy tên cho' eval' trong phạm vi toàn cục và giả sử rằng hàm này thậm chí ở đó và không, ví dụ, trong một mô-đun. – aaronasterling

6

Đoạn code dưới đây giải quyết vấn đề cho tên của hàm. Tuy nhiên, nó không phát hiện được docstring đúng cho ví dụ được đưa ra bởi aaronasterling. Tôi tự hỏi nếu có một cách để có được trở lại cây cú pháp trừu tượng kết hợp với một đối tượng bytecode. Sau đó, nó sẽ khá dễ dàng để đọc docstring.

import inspect 

def get_name_doc(): 
    outerframe = inspect.currentframe().f_back 
    name = outerframe.f_code.co_name 
    doc = outerframe.f_back.f_globals[name].__doc__  
    return name, doc 

if __name__ == "__main__": 

    def function(): 
     "Docstring" 

     name, doc = get_name_doc() 

     return name, doc 

    def dec(f): 
     def wrap(): 
      """wrap""" 
      return f() 
     return wrap 

    @dec 
    def test(): 
     """test""" 
     return get_name_doc() 

    assert function() == ('function', "Docstring") 
    #The assertion below fails:. It gives: ('test', 'wrap') 
    #assert test() == ('test', 'test') 
1

Làm thế nào về điều này:

import functools 

def giveme(func): 
    @functools.wraps(func) 
    def decor(*args, **kwargs): 
     return func(decor, *args, **kwargs) 
    return decor 

@giveme 
def myfunc(me): 
    "docstr" 
    return (me.__name__, me.__doc__) 

# prints ('myfunc', 'docstr') 
print myfunc() 

thời gian ngắn, các giveme trang trí thêm (trang trí) chức năng đối tượng như là đối số đầu tiên. Bằng cách này, hàm có thể truy cập tên và chuỗi tài liệu của chính nó khi được gọi.

Do trang trí, chức năng ban đầu myfunc được thay thế bằng decor. Để làm cho đối số đầu tiên chính xác giống như myfunc, những gì được chuyển vào hàm là decor và không phải là func.

Trình trang trí functools.wraps được sử dụng để cung cấp cho decor các thuộc tính (tên, docstring, v.v.) của hàm myfunc gốc.

+0

Bạn đang chuyển chức năng gói trang trí làm đối số đầu tiên của nó chứ không phải chức năng ... – aaronasterling

+0

Có, tôi đã giải thích lý do. – yak

+0

Trong mô tả của bạn, bạn yêu cầu chuyển đối tượng hàm. – aaronasterling

2
>>> import inspect 
>>> def f(): 
...  """doc""" 
...  name = inspect.getframeinfo(inspect.currentframe()).function 
...  doc = eval(name + '.__doc__') 
...  return name, doc 
... 
>>> f() 
('f', 'doc') 
>>> class C: 
...  def f(self): 
...   """doc""" 
...   name = inspect.getframeinfo(inspect.currentframe()).function 
...   doc = eval(name + '.__doc__') 
...   return name, doc 
... 
>>> C().f() 
('f', 'doc') 
1

cho phiên bản được mã hóa cứng hoạt động với các trang trí 'cư xử tốt'. Nó phải được khai báo sau khi chức năng. nếu chức năng được phục hồi sau này, những thay đổi được cập nhật tại đây.

def get_name_doc(): 
    # global function # this is optional but makes your intent a bit more clear. 
    return function.__name__, function.__doc__ 

Đây là một hack khá khó chịu, ở chỗ nó lạm dụng theo cách mặc định args hoạt động. Nó sẽ sử dụng bất cứ chức năng nào bị ràng buộc vào lúc chức năng này được 'khởi tạo', và nhớ nó ngay cả khi chức năng được phục hồi. Gọi nó với args sẽ dẫn đến kết quả thú vị.

def get_name_doc(fn=function): 
    return fn.__name__, fn.__doc__ 

và mã động vẫn được mã hóa cứng nhưng cập nhật chức năng được gọi với đối số là True. Về cơ bản phiên bản này sẽ chỉ cập nhật khi được yêu cầu làm như vậy.

def get_name_doc(update=False): 
    global fn 
    if update: 
     fn = function 
    return fn.__name__, fn.__doc__ 

Bây giờ tất nhiên cũng có những ví dụ trang trí cho điều này.

@decorator # applying the decorator decorator to make it well behaved 
def print_name_doc(fn, *args, **kwargs): 
    def inner(*args, **kwargs): 
     print(fn.__doc__, fn.__name__) # im assuming you just want to print in this case 
     return fn(*args, **kwargs) 
return inner 

bạn nên đọc trên trang trí trang trí (ít nhất). Nhìn vào nguồn NamedTuple (từ mô-đun bộ sưu tập) vì nó liên quan đến việc ghi chú mã hóa cứng. Đáng buồn thay, mã tuple được đặt tên khá lạ. Nó là một định dạng chuỗi được sử dụng với mã eval thay vì sau đó truyền thống, nhưng nó hoạt động thực sự gọn gàng. Đây có vẻ là biến thể hứa hẹn nhất. Bạn có thể làm điều này với metaclasess quá, dẫn đến mã gọn gàng, nhưng thay vì những thứ khó chịu ẩn đằng sau hậu trường, mà bạn cần phải mã. ID này khuyên chống lại

Tôi nghi ngờ rằng có thể có cách dễ hơn là kiểm tra/phản ánh/mẫu/metaclasess bằng cách thêm dòng sau vào cuối mô-đun.

help(<module>) 

tên của mô-đun bạn đang làm việc trên (chuỗi). Hoặc thậm chí biến __name__. Điều này cũng có thể được thực hiện trong tệp __init__.py nếu làm việc với nhiều mô-đun hoặc trên các lớp riêng lẻ quá tôi nghĩ rằng.

1

Như đã lưu ý nhiều lần, việc sử dụng tên hàm bên trong hàm thực sự là tra cứu động trong globals() của mô-đun hiện tại. Sử dụng bất kỳ loại eval() nào chỉ là một biến thể từ nó khi độ phân giải tên của nó sẽ hoạt động trở lại với từ điển globals().Hầu hết các ví dụ sẽ thất bại với một hàm thành viên - bạn cần tìm kiếm tên lớp đầu tiên từ globals() và sau đó bạn có thể truy cập chức năng thành viên từ nó. Vì vậy, thực sự

def function(): 
    """ foo """ 
    doc = function.__doc__ 

class Class: 
    def function(): 
     """ bar """ 
     doc = Class.function.__doc__ 

tương đương với

def function(): 
    """ foo """ 
    doc = globals()["function"].__doc__ 

class Class: 
    def function(): 
     """ bar """ 
     doc = globals()["Class"].function.__doc__ 

Trong nhiều trường hợp tra cứu năng động này sẽ là đủ. Nhưng thực tế bạn phải gõ lại tên hàm bên trong hàm. Tuy nhiên, nếu bạn viết một hàm trợ giúp để tìm ra chuỗi tài liệu của người gọi thì bạn sẽ phải đối mặt với thực tế là hàm trợ giúp có thể sống trong một mô-đun khác với một từ điển globals() khác nhau. Vì vậy, cách chính xác nhất là sử dụng thông tin khung hiện tại để tìm hàm - nhưng đối tượng khung của Python không có tham chiếu đến đối tượng hàm, nó chỉ mang tham chiếu tới mã "f_code" mà nó sử dụng. Nó cần phải tìm kiếm thông qua các "f_globals" tham chiếu từ điển để tìm ra ánh xạ từ f_code đến đối tượng chức năng, ví dụ như thế này:

import inspect 

def get_caller_doc(): 
    frame = inspect.currentframe().f_back.f_back 
    for objref in frame.f_globals.values(): 
     if inspect.isfunction(objref): 
      if objref.func_code == frame.f_code: 
       return objref.__doc__ 
     elif inspect.isclass(objref): 
      for name, member in inspect.getmembers(objref): 
       if inspect.ismethod(member): 
        if member.im_func.func_code == frame.f_code: 
         return member.__doc__ 

Nó được đặt tên get_caller_doc() thay vì get_my_doc() bởi vì trong phần lớn các trường hợp bạn muốn có chuỗi tài liệu để chuyển nó thành một đối số cho một số hàm trợ giúp. Nhưng hàm helper có thể dễ dàng lấy chuỗi doc từ người gọi của nó - tôi đang sử dụng nó trong các script không liên quan của tôi, nơi mà một hàm trợ giúp có thể sử dụng chuỗi doc của bài kiểm tra để đăng nó vào nhật ký hoặc sử dụng nó làm dữ liệu thử nghiệm thực tế. Đó là lý do tại sao trình trợ giúp được trình bày chỉ tìm kiếm chuỗi tài liệu của các hàm thử nghiệm và các hàm thành viên thử nghiệm.

class MyTest: 
    def test_101(self): 
     """ some example test """ 
     self.createProject("A") 
    def createProject(self, name): 
     description = get_caller_doc() 
     self.server.createProject(name, description) 

Để người đọc mở rộng ví dụ cho các trường hợp sử dụng khác.

2

Đối với các dự án cá nhân của mình, tôi đã phát triển tên hàm và doc các kỹ thuật khôi phục cho các hàm và phương thức lớp. Chúng được thực hiện trong một mô-đun có thể nhập (SelfDoc.py) có tự kiểm tra riêng của nó trong chính của nó. Nó được bao gồm dưới đây. Mã này thực hiện như trong Python 2.7.8 trên Linux và MacOS. Nó đang được sử dụng.

#!/usr/bin/env python 

from inspect import (getframeinfo, currentframe, getouterframes) 

class classSelfDoc(object): 

    @property 
    def frameName(self): 
     frame = getframeinfo(currentframe().f_back) 
     return str(frame.function) 

    @property 
    def frameDoc(self): 
     frame = getframeinfo(currentframe().f_back) 
     doc = eval('self.'+str(frame.function)+'.__doc__') 
     return doc if doc else 'undocumented' 

def frameName(): 
    return str(getframeinfo(currentframe().f_back).function) 

def frameDoc(): 
    doc = eval(getframeinfo(currentframe().f_back).function).__doc__ 
    return doc if doc else 'undocumented' 

if __name__ == "__main__": 

    class aClass(classSelfDoc): 
     "class documentation" 

     def __init__(self): 
      "ctor documentation" 
      print self.frameName, self.frameDoc 

     def __call__(self): 
      "ftor documentation" 
      print self.frameName, self.frameDoc 

     def undocumented(self): 
      print self.frameName, self.frameDoc 

    def aDocumentedFunction(): 
     "function documentation" 
     print frameName(), frameDoc() 

    def anUndocumentedFunction(): 
     print frameName(), frameDoc() 

    anInstance = aClass() 
    anInstance() 
    anInstance.undocumented() 

    aDocumentedFunction() 
    anUndocumentedFunction() 
1

Reference http://stefaanlippens.net/python_inspect

import inspect 
# functions 
def whoami(): 
    return inspect.stack()[1][3] 
def whocalledme(): 
    return inspect.stack()[2][3] 
def foo(): 
    print "hello, I'm %s, daddy is %s" % (whoami(), whocalledme()) 
    bar() 
def bar(): 
    print "hello, I'm %s, daddy is %s" % (whoami(), whocalledme()) 
johny = bar 
# call them! 
foo() 
bar() 
johny() 

Output:

hello, I'm foo, daddy is ? 
hello, I'm bar, daddy is foo 
hello, I'm bar, daddy is ? 
hello, I'm bar, daddy is ? 
Các vấn đề liên quan