Làm thế nào để có được tên tham số phương thức?


236

Cho hàm Python:

def a_method(arg1, arg2):
    pass

Làm thế nào tôi có thể trích xuất số lượng và tên của các đối số. Tức là, cho rằng tôi có một tài liệu tham khảo func, tôi muốn func.[something]trả lại ("arg1", "arg2").

Kịch bản sử dụng cho điều này là tôi có một trình trang trí và tôi muốn sử dụng các đối số phương thức theo cùng thứ tự mà chúng xuất hiện cho hàm thực tế làm khóa. Tức là, người trang trí sẽ trông như thế "a,b"nào khi in a_method("a", "b")?


1
Để biết danh sách các câu trả lời khác nhau cho một câu hỏi gần như giống hệt nhau, hãy xem bài đăng stackoverflow khác này
dan mackinlay

1
Tiêu đề của bạn là sai lệch: khi người ta nói 'phương thức' viết từ 'hàm', người ta thường nghĩ về một phương thức lớp. Đối với chức năng, câu trả lời được chọn của bạn (từ Jouni K. Seppanen) là tốt. Nhưng đối với phương thức (lớp), nó không hoạt động và nên sử dụng giải pháp kiểm tra (từ Brian).
Juh_

Câu trả lời:


351

Hãy xem inspectmô-đun - điều này sẽ thực hiện việc kiểm tra các thuộc tính đối tượng mã khác nhau cho bạn.

>>> inspect.getfullargspec(a_method)
(['arg1', 'arg2'], None, None, None)

Các kết quả khác là tên của các biến * args và ** kwargs và các giá trị mặc định được cung cấp. I E.

>>> def foo(a, b, c=4, *arglist, **keywords): pass
>>> inspect.getfullargspec(foo)
(['a', 'b', 'c'], 'arglist', 'keywords', (4,))

Lưu ý rằng một số tên gọi có thể không được hướng nội trong một số triển khai Python nhất định. Ví dụ, trong CPython, một số hàm dựng sẵn được xác định trong C không cung cấp siêu dữ liệu về các đối số của chúng. Kết quả là, bạn sẽ nhận được ValueErrornếu bạn sử dụng inspect.getfullargspec()chức năng tích hợp.

Kể từ Python 3.3, bạn có thể sử dụng inspect.signature()để xem chữ ký cuộc gọi của một đối tượng có thể gọi được:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>

29
Làm thế nào mã có thể biết rằng tham số mặc định (4,)tương ứng với tham số từ khóa ccụ thể?
fatuhoku

63
@fatuhoku Mình đang thắc mắc điều tương tự. Hóa ra nó không mơ hồ vì bạn chỉ có thể thêm các đối số mặc định ở cuối trong một khối liền kề. Từ các tài liệu: "nếu bộ dữ liệu này có n phần tử, thì chúng tương ứng với n phần tử cuối cùng được liệt kê trong args"
Soverman

9
Tôi nghĩ kể từ khi Python 3.x getargspec (...) được thay thế bởi Inspector.signature (func)
Diego Andrés Díaz Espinoza

2
Đã thay đổi trong phiên bản 2.6: Trả về một tuple ArgSpec có tên (args, varargs, từ khóa, mặc định).
theannouncer

4
Đó là đúng, @ DiegoAndrésDíazEspinoza - bằng Python 3, inspect.getargspecđang bị phản đối , nhưng việc thay thế là inspect.getfullargspec.
j08

100

Trong CPython, số lượng đối số là

a_method.func_code.co_argcount

và tên của họ là vào đầu

a_method.func_code.co_varnames

Đây là các chi tiết triển khai của CPython, vì vậy điều này có thể không hoạt động trong các triển khai khác của Python, như IronPython và Jython.

Một cách di động để thừa nhận các đối số "vượt qua" là xác định hàm của bạn bằng chữ ký func(*args, **kwargs). Điều này được sử dụng rất nhiều trong ví dụ matplotlib , trong đó lớp API bên ngoài chuyển rất nhiều đối số từ khóa cho API cấp thấp hơn.


co_varnames không hoạt động với Python chuẩn, nhưng phương pháp này không thích hợp hơn vì nó cũng sẽ hiển thị các đối số bên trong.
MattK

11
Tại sao không sử dụng aMethod.func_code.co_varnames [: aMethod.func_code.co_argcount]?
hochl

Không làm việc với các đối số sau *args, ví dụ:def foo(x, *args, y, **kwargs): # foo.__code__.co_argcount == 1
Nikolay Makhalin


Vui lòng sử dụng kiểm tra thay thế. Mặt khác, mã của bạn không hoạt động tốt với funcools.wraps trong 3.4+. Xem stackoverflow.com/questions/147816/
Mạnh

22

Trong một phương thức trang trí, bạn có thể liệt kê các đối số của phương thức ban đầu theo cách này:

import inspect, itertools 

def my_decorator():

        def decorator(f):

            def wrapper(*args, **kwargs):

                # if you want arguments names as a list:
                args_name = inspect.getargspec(f)[0]
                print(args_name)

                # if you want names and values as a dictionary:
                args_dict = dict(itertools.izip(args_name, args))
                print(args_dict)

                # if you want values as a list:
                args_values = args_dict.values()
                print(args_values)

Nếu điều **kwargsquan trọng đối với bạn, thì nó sẽ hơi phức tạp:

        def wrapper(*args, **kwargs):

            args_name = list(OrderedDict.fromkeys(inspect.getargspec(f)[0] + kwargs.keys()))
            args_dict = OrderedDict(list(itertools.izip(args_name, args)) + list(kwargs.iteritems()))
            args_values = args_dict.values()

Thí dụ:

@my_decorator()
def my_function(x, y, z=3):
    pass


my_function(1, y=2, z=3, w=0)
# prints:
# ['x', 'y', 'z', 'w']
# {'y': 2, 'x': 1, 'z': 3, 'w': 0}
# [1, 2, 3, 0]

Câu trả lời này đã lỗi thời một phần và cần được cập nhật.
Imago

15

Tôi nghĩ những gì bạn đang tìm kiếm là phương pháp địa phương -


In [6]: def test(a, b):print locals()
   ...: 

In [7]: test(1,2)              
{'a': 1, 'b': 2}

7
Điều này là vô ích bên ngoài một chức năng là bối cảnh quan tâm ở đây (trang trí).
Piotr Dobrogost

8
Trên thực tế chính xác những gì tôi đang tìm kiếm mặc dù nó không phải là câu trả lời cho câu hỏi ở đây.
javabeangrinder

15

Phiên bản Python 3 là:

def _get_args_dict(fn, args, kwargs):
    args_names = fn.__code__.co_varnames[:fn.__code__.co_argcount]
    return {**dict(zip(args_names, args)), **kwargs}

Phương thức trả về một từ điển chứa cả args và kwargs.


Lưu ý rằng điều [:fn.__code__.co_argcount]này rất quan trọng nếu bạn đang tìm kiếm các đối số hàm - nếu không thì nó cũng bao gồm các tên được tạo trong hàm.
Soren Bjornstad

13

Đây là một cái gì đó tôi nghĩ sẽ làm việc cho những gì bạn muốn, sử dụng một trang trí.

class LogWrappedFunction(object):
    def __init__(self, function):
        self.function = function

    def logAndCall(self, *arguments, **namedArguments):
        print "Calling %s with arguments %s and named arguments %s" %\
                      (self.function.func_name, arguments, namedArguments)
        self.function.__call__(*arguments, **namedArguments)

def logwrap(function):
    return LogWrappedFunction(function).logAndCall

@logwrap
def doSomething(spam, eggs, foo, bar):
    print "Doing something totally awesome with %s and %s." % (spam, eggs)


doSomething("beans","rice", foo="wiggity", bar="wack")

Chạy nó, nó sẽ mang lại đầu ra sau:

C:\scripts>python decoratorExample.py
Calling doSomething with arguments ('beans', 'rice') and named arguments {'foo':
 'wiggity', 'bar': 'wack'}
Doing something totally awesome with beans and rice.

11

Python 3,5+:

DeprecationWarning: notify.getargspec () không dùng nữa, thay vào đó, hãy sử dụng notify.signature ()

Vì vậy, trước đây:

func_args = inspect.getargspec(function).args

Hiện nay:

func_args = list(inspect.signature(function).parameters.keys())

Để kiểm tra:

'arg' in list(inspect.signature(function).parameters.keys())

Cho rằng chúng ta có hàm 'hàm' lấy tham số 'arg', điều này sẽ đánh giá là Đúng, nếu không là Sai.

Ví dụ từ bảng điều khiển Python:

Python 3.6.0 (v3.6.0:41df79263a11, Dec 23 2016, 07:18:10) [MSC v.1900 32 bit (Intel)] on win32
>>> import inspect
>>> 'iterable' in list(inspect.signature(sum).parameters.keys())
True

7

Trong Python 3. + với Signatuređối tượng trong tay, một cách dễ dàng để có được ánh xạ giữa các tên đối số thành các giá trị, là sử dụng Chữ kýbind() phương thức !

Ví dụ, đây là một trang trí để in một bản đồ như thế:

import inspect

def decorator(f):
    def wrapper(*args, **kwargs):
        bound_args = inspect.signature(f).bind(*args, **kwargs)
        bound_args.apply_defaults()
        print(dict(bound_args.arguments))

        return f(*args, **kwargs)

    return wrapper

@decorator
def foo(x, y, param_with_default="bars", **kwargs):
    pass

foo(1, 2, extra="baz")
# This will print: {'kwargs': {'extra': 'baz'}, 'param_with_default': 'bars', 'y': 2, 'x': 1}

6

Đây là một cách khác để có được các tham số chức năng mà không cần sử dụng bất kỳ mô-đun.

def get_parameters(func):
    keys = func.__code__.co_varnames[:func.__code__.co_argcount][::-1]
    sorter = {j: i for i, j in enumerate(keys[::-1])} 
    values = func.__defaults__[::-1]
    kwargs = {i: j for i, j in zip(keys, values)}
    sorted_args = tuple(
        sorted([i for i in keys if i not in kwargs], key=sorter.get)
    )
    sorted_kwargs = {}
    for i in sorted(kwargs.keys(), key=sorter.get):
        sorted_kwargs[i] = kwargs[i]      
    return sorted_args, sorted_kwargs


def f(a, b, c="hello", d="world"): var = a


print(get_parameters(f))

Đầu ra:

(('a', 'b'), {'c': 'hello', 'd': 'world'})

2

Trả về một danh sách các tên đối số, chăm sóc các phần và các hàm thông thường:

def get_func_args(f):
    if hasattr(f, 'args'):
        return f.args
    else:
        return list(inspect.signature(f).parameters)

2

Cập nhật cho câu trả lời của Brian :

Nếu một hàm trong Python 3 có các đối số chỉ từ khóa, thì bạn cần sử dụng inspect.getfullargspec:

def yay(a, b=10, *, c=20, d=30):
    pass
inspect.getfullargspec(yay)

mang lại điều này:

FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=(10,), kwonlyargs=['c', 'd'], kwonlydefaults={'c': 20, 'd': 30}, annotations={})

2

Trong python 3, bên dưới là tạo *args**kwargsthành một dict(sử dụng OrderedDictcho python <3.6 để duy trì dictcác đơn đặt hàng):

from functools import wraps

def display_param(func):
    @wraps(func)
    def wrapper(*args, **kwargs):

        param = inspect.signature(func).parameters
        all_param = {
            k: args[n] if n < len(args) else v.default
            for n, (k, v) in enumerate(param.items()) if k != 'kwargs'
        }
        all_param .update(kwargs)
        print(all_param)

        return func(**all_param)
    return wrapper

1

inspect.signatureRất chậm. Cách nhanh nhất là

def f(a, b=1, *args, c, d=1, **kwargs):
   pass

f_code = f.__code__
f_code.co_varnames[:f_code.co_argcount + f_code.co_kwonlyargcount]  # ('a', 'b', 'c', 'd')

0

Để cập nhật một chút câu trả lời của Brian , giờ đây đã có một bản backport đẹp inspect.signaturemà bạn có thể sử dụng trong các phiên bản python cũ : funcsigs. Vì vậy, sở thích cá nhân của tôi sẽ đi cho

try:  # python 3.3+
    from inspect import signature
except ImportError:
    from funcsigs import signature

def aMethod(arg1, arg2):
    pass

sig = signature(aMethod)
print(sig)

Để giải trí, nếu bạn thích chơi với Signaturecác đối tượng và thậm chí tạo các hàm với chữ ký ngẫu nhiên một cách linh hoạt, bạn có thể xem makefundự án của tôi .


-3

Những gì về dir()vars()bây giờ?

Có vẻ như đang làm chính xác những gì đang được hỏi siêu đơn giản

Phải được gọi từ trong phạm vi chức năng.

Nhưng hãy cảnh giác rằng nó sẽ trả về tất cả các biến cục bộ, vì vậy hãy chắc chắn thực hiện nó ngay từ đầu của hàm nếu cần.

Cũng lưu ý rằng, như được chỉ ra trong các bình luận, điều này không cho phép nó được thực hiện từ bên ngoài phạm vi. Vì vậy, không chính xác kịch bản của OP nhưng vẫn phù hợp với tiêu đề câu hỏi. Do đó câu trả lời của tôi.


dir () trả về danh sách tất cả các tên biến ['var1', 'var2'], vars () trả về từ điển ở dạng {'var1': 0, 'var2': 'Something'} từ trong phạm vi cục bộ hiện tại. Nếu ai đó muốn sử dụng tên biến đối số sau này trong hàm, họ nên lưu vào biến cục bộ khác, vì gọi nó sau trong hàm nơi họ có thể khai báo một biến cục bộ khác sẽ "làm ô nhiễm" danh sách này. Trong trường hợp họ muốn sử dụng nó bên ngoài hàm, họ phải chạy hàm ít nhất một lần và lưu nó trong biến toàn cục. Vì vậy, tốt hơn là sử dụng mô-đun kiểm tra.
Peter Majko
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.