Bagaimana cara mendapatkan nama parameter metode?

236

Diberi fungsi Python:

def a_method(arg1, arg2):
    pass

Bagaimana saya bisa mengekstrak jumlah dan nama argumen. Yaitu, mengingat bahwa saya memiliki referensi func, saya ingin func.[something]kembali ("arg1", "arg2").

Skenario penggunaan untuk ini adalah bahwa saya memiliki dekorator, dan saya ingin menggunakan argumen metode dalam urutan yang sama dengan yang muncul untuk fungsi aktual sebagai kunci. Yaitu, bagaimana dekorator akan terlihat dicetak "a,b"saat saya menelepon a_method("a", "b")?

Staale
sumber
1
Untuk daftar jawaban yang berbeda untuk pertanyaan yang hampir identik, lihat post stackoverflow lainnya
dan mackinlay
1
Judul Anda menyesatkan: ketika seseorang mengatakan 'metode' menggunakan kata 'fungsi', orang biasanya memikirkan metode kelas. Untuk fungsi, jawaban yang Anda pilih (dari Jouni K. Seppanen) baik. Tetapi untuk metode (kelas), itu tidak berfungsi dan solusi pemeriksaan (dari Brian) harus digunakan.
Juh_

Jawaban:

351

Lihatlah inspectmodul - ini akan melakukan pemeriksaan berbagai properti objek kode untuk Anda.

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

Hasil lainnya adalah nama variabel * args dan ** kwargs, dan default yang disediakan. yaitu.

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

Perhatikan bahwa beberapa callable mungkin tidak dapat ditinjau dalam implementasi Python tertentu. Misalnya, dalam CPython, beberapa fungsi bawaan yang didefinisikan dalam C tidak memberikan metadata tentang argumennya. Akibatnya, Anda akan mendapatkan ValueErrorjika Anda menggunakan inspect.getfullargspec()fungsi bawaan.

Karena Python 3.3, Anda dapat menggunakan inspect.signature()untuk melihat tanda tangan panggilan dari objek yang bisa dipanggil:

>>> inspect.signature(foo)
<Signature (a, b, c=4, *arglist, **keywords)>
Brian
sumber
29
Bagaimana mungkin kode mengetahui bahwa parameter default (4,)sesuai dengan parameter kata kunci csecara khusus?
fatuhoku
63
@fatuhoku saya bertanya-tanya hal yang sama. Ternyata itu tidak ambigu karena Anda hanya bisa menambahkan argumen default di akhir di blok yang berdekatan. Dari dokumen: "jika tuple ini memiliki n elemen, mereka bersesuaian dengan n elemen terakhir yang tercantum dalam argumen"
Soverman
9
Saya pikir karena Python 3.x getargspec (...) digantikan oleh inspector.signature (func)
Diego Andrés Díaz Espinoza
2
Berubah dalam versi 2.6: Mengembalikan tuple bernama ArgSpec (args, varargs, kata kunci, default).
theannouncer
4
Itu benar, @ DiegoAndrésDíazEspinoza - dengan Python 3, inspect.getargspecsudah usang , tetapi penggantinya inspect.getfullargspec.
j08lue
100

Dalam CPython, jumlah argumen adalah

a_method.func_code.co_argcount

dan nama mereka ada di awal

a_method.func_code.co_varnames

Ini adalah detail implementasi CPython, jadi ini mungkin tidak berfungsi dalam implementasi Python lainnya, seperti IronPython dan Jython.

Salah satu cara portabel untuk mengakui argumen "pass-through" adalah dengan mendefinisikan fungsi Anda dengan tanda tangan func(*args, **kwargs). Ini banyak digunakan di misalnya matplotlib , di mana lapisan API luar melewati banyak argumen kata kunci ke API tingkat rendah.

Jouni K. Seppänen
sumber
co_varnames berfungsi dengan standar Python, tetapi metode ini tidak disukai karena juga akan menampilkan argumen internal.
MattK
11
Mengapa tidak menggunakan aMethod.func_code.co_varnames [: aMethod.func_code.co_argcount]?
hochl
Tidak bekerja dengan argumen setelah *args, misalnya:def foo(x, *args, y, **kwargs): # foo.__code__.co_argcount == 1
Nikolay Makhalin
@Nikolay lihat stackoverflow.com/questions/147816/…
Brian McCutchon
Silakan gunakan inspeksi sebagai gantinya. Jika tidak, kode Anda tidak berfungsi dengan baik dengan functools.wraps di 3.4+. Lihat stackoverflow.com/questions/147816/…
Brian McCutchon
22

Dalam metode dekorator, Anda dapat membuat daftar argumen dari metode asli dengan cara ini:

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)

Jika **kwargsyang penting bagi Anda, maka itu akan sedikit rumit:

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

Contoh:

@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]
Mehdi Behrooz
sumber
Jawaban ini sebagian sudah usang dan harus diperbarui.
Imago
15

Saya pikir yang Anda cari adalah metode lokal -


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

In [7]: test(1,2)              
{'a': 1, 'b': 2}
Damian
sumber
7
Ini tidak berguna di luar fungsi yang merupakan konteks yang menarik di sini (dekorator).
Piotr Dobrogost
8
Sebenarnya persis apa yang saya cari walaupun itu bukan jawaban untuk pertanyaan di sini.
javabeangrinder
15

Versi Python 3 adalah:

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

Metode mengembalikan kamus yang berisi args dan kwargs.

argaen
sumber
Perhatikan bahwa [:fn.__code__.co_argcount]ini sangat penting jika Anda mencari argumen fungsi - jika tidak, ia juga menyertakan nama yang dibuat di dalam fungsi.
Soren Bjornstad
13

Ini adalah sesuatu yang saya pikir akan bekerja untuk apa yang Anda inginkan, menggunakan dekorator.

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

Jalankan, itu akan menghasilkan output berikut:

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.
hlzr
sumber
11

Python 3.5+:

DeprecationWarning: inspect.getargspec () sudah tidak digunakan lagi, gunakan inspect.signature () sebagai gantinya

Jadi sebelumnya:

func_args = inspect.getargspec(function).args

Sekarang:

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

Untuk menguji:

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

Mengingat bahwa kita memiliki fungsi 'fungsi' yang mengambil argumen 'argumen', ini akan mengevaluasi sebagai Benar, sebaliknya sebagai Salah.

Contoh dari konsol 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
Peter Majko
sumber
8

Dalam Python 3. + dengan Signatureobjek yang ada di tangan, cara mudah untuk mendapatkan pemetaan antara nama argumen dengan nilai, menggunakan metode Signature bind()!

Sebagai contoh, berikut adalah dekorator untuk mencetak peta seperti itu:

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}
Kfir Eisner
sumber
6

Berikut ini cara lain untuk mendapatkan parameter fungsi tanpa menggunakan modul apa pun.

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

Keluaran:

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

Mengembalikan daftar nama argumen, menangani sebagian dan fungsi reguler:

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

Pembaruan untuk jawaban Brian :

Jika fungsi di Python 3 memiliki argumen hanya kata kunci, maka Anda perlu menggunakan inspect.getfullargspec:

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

menghasilkan ini:

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

Dalam python 3, di bawah ini adalah untuk membuat *argsdan **kwargsmenjadi dict(gunakan OrderedDictuntuk python <3,6 untuk mempertahankan dictpesanan):

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
Alfa
sumber
1

inspect.signaturesangat lambat. Cara tercepat adalah

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')
Nikolay Makhalin
sumber
0

Untuk memperbarui sedikit memagut jawaban Brian , sekarang ada yang bagus backport dari inspect.signatureyang dapat Anda gunakan dalam versi python lebih tua: funcsigs. Jadi preferensi pribadi saya akan berlaku

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

def aMethod(arg1, arg2):
    pass

sig = signature(aMethod)
print(sig)

Untuk bersenang-senang, jika Anda tertarik bermain dengan Signatureobjek dan bahkan membuat fungsi dengan tanda tangan acak secara dinamis, Anda dapat melihat makefunproyek saya .

nyonya
sumber
-3

Bagaimana dir()dan vars()sekarang?

Tampaknya melakukan apa yang diminta dengan sangat sederhana ...

Harus dipanggil dari dalam lingkup fungsi.

Tetapi berhati-hatilah karena itu akan mengembalikan semua variabel lokal jadi pastikan untuk melakukannya di awal fungsi jika diperlukan.

Juga perhatikan bahwa, seperti yang ditunjukkan dalam komentar, ini tidak memungkinkan dilakukan dari luar ruang lingkup. Jadi tidak persis skenario OP tetapi masih cocok dengan judul pertanyaan. Karena itu jawabanku.

jeromej
sumber
dir () mengembalikan daftar semua nama variabel ['var1', 'var2'], vars () mengembalikan kamus dalam bentuk {'var1': 0, 'var2': 'something'} dari dalam lingkup lokal saat ini. Jika seseorang ingin menggunakan nama variabel argumen nanti dalam fungsi, mereka harus menyimpan dalam variabel lokal lain, karena memanggilnya nanti dalam fungsi di mana mereka dapat mendeklarasikan variabel lokal lain akan "mencemari" daftar ini. Jika mereka ingin menggunakannya di luar fungsi, mereka harus menjalankan fungsi setidaknya sekali dan menyimpannya dalam variabel global. Jadi lebih baik menggunakan modul inspect.
Peter Majko