Dapatkah Anda membuat daftar argumen kata kunci yang diterima suatu fungsi?

104

Saya memiliki dict, yang harus saya berikan kunci / nilai sebagai argumen kata kunci .. Misalnya ..

d_args = {'kw1': 'value1', 'kw2': 'value2'}
example(**d_args)

Ini berfungsi dengan baik, tetapi jika ada nilai di d_args dict yang tidak diterima oleh examplefungsi, itu jelas mati .. Katakanlah, jika fungsi contoh didefinisikan sebagaidef example(kw2):

Ini adalah masalah karena saya tidak mengontrol pembuatan d_args, atau examplefungsi .. Keduanya berasal dari modul eksternal, dan examplehanya menerima beberapa argumen kata kunci dari dict ..

Idealnya saya akan melakukannya

parsed_kwargs = feedparser.parse(the_url)
valid_kwargs = get_valid_kwargs(parsed_kwargs, valid_for = PyRSS2Gen.RSS2)
PyRSS2Gen.RSS2(**valid_kwargs)

Saya mungkin hanya akan memfilter dict, dari daftar argumen kata kunci yang valid, tetapi saya bertanya-tanya: Apakah ada cara untuk mendaftar secara programatik argumen kata kunci yang dibutuhkan oleh fungsi tertentu?

dbr
sumber

Jawaban:

150

Sedikit lebih baik daripada memeriksa objek kode secara langsung dan mengerjakan variabel adalah dengan menggunakan modul inspect.

>>> import inspect
>>> def func(a,b,c=42, *args, **kwargs): pass
>>> inspect.getargspec(func)
(['a', 'b', 'c'], 'args', 'kwargs', (42,))

Jika Anda ingin mengetahui apakah dapat dipanggil dengan kumpulan argumen tertentu, Anda memerlukan argumen tanpa default yang telah ditentukan. Ini bisa didapat dengan:

def getRequiredArgs(func):
    args, varargs, varkw, defaults = inspect.getargspec(func)
    if defaults:
        args = args[:-len(defaults)]
    return args   # *args and **kwargs are not required, so ignore them.

Kemudian fungsi untuk mengetahui apa yang Anda lewatkan dari dikt khusus Anda adalah:

def missingArgs(func, argdict):
    return set(getRequiredArgs(func)).difference(argdict)

Demikian pula, untuk memeriksa argumen yang tidak valid, gunakan:

def invalidArgs(func, argdict):
    args, varargs, varkw, defaults = inspect.getargspec(func)
    if varkw: return set()  # All accepted
    return set(argdict) - set(args)

Dan tes penuh jika itu bisa dipanggil adalah:

def isCallableWithArgs(func, argdict):
    return not missingArgs(func, argdict) and not invalidArgs(func, argdict)

(Ini bagus hanya sejauh parsing arg python. Setiap pemeriksaan runtime untuk nilai yang tidak valid di kwarg jelas tidak dapat dideteksi.)

Brian
sumber
Bagus! Saya tidak tahu fungsi ini!
DzinX
1
Mengingat bahwa metode yang menggunakan objek kode kurang lebih identik, apakah ada manfaatnya harus mengimpor satu modul lagi ...?
jmetz
@jmets - pasti - itu hampir selalu lebih baik menggunakan modul perpustakaan daripada menggulung modul Anda sendiri. Juga, atribut pada objek kode lebih internal, dan siap untuk diubah (misalnya, perhatikan bahwa ini dipindahkan ke kode di pyhon3). Menggunakan modul sebagai antarmuka lebih tahan lama jika beberapa internal ini pernah berubah. Itu juga akan melakukan hal-hal yang mungkin tidak terpikirkan untuk Anda lakukan, seperti melempar kesalahan jenis yang sesuai pada fungsi yang tidak dapat Anda periksa (mis. Fungsi C).
Brian
13
inspect.getargspec(f)tidak digunakan lagi sejak Python 3.0; metode modern adalah inspect.signature(f).
gerrit
FYI, jika Anda ingin mendukung Cython dan Python, metode ini tidak berfungsi pada fungsi Cython. The co_varnamespilihan, di sisi lain, tidak bekerja di kedua.
tanggal
32

Ini akan mencetak nama semua argumen yang bisa dilewati, kata kunci dan non-kata kunci:

def func(one, two="value"):
    y = one, two
    return y
print func.func_code.co_varnames[:func.func_code.co_argcount]

Ini karena pertama co_varnamesselalu parameter (selanjutnya adalah variabel lokal, seperti ypada contoh di atas).

Jadi sekarang Anda bisa memiliki fungsi:

def getValidArgs(func, argsDict):
    '''Return dictionary without invalid function arguments.'''
    validArgs = func.func_code.co_varnames[:func.func_code.co_argcount]
    return dict((key, value) for key, value in argsDict.iteritems() 
                if key in validArgs)

Yang kemudian bisa Anda gunakan seperti ini:

>>> func(**getValidArgs(func, args))

EDIT : Tambahan kecil: jika Anda benar-benar hanya membutuhkan argumen kata kunci dari suatu fungsi, Anda dapat menggunakan func_defaultsatribut untuk mengekstraknya:

def getValidKwargs(func, argsDict):
    validArgs = func.func_code.co_varnames[:func.func_code.co_argcount]
    kwargsLen = len(func.func_defaults) # number of keyword arguments
    validKwargs = validArgs[-kwargsLen:] # because kwargs are last
    return dict((key, value) for key, value in argsDict.iteritems() 
                if key in validKwargs)

Anda sekarang dapat memanggil fungsi Anda dengan argumen yang dikenal, tetapi kwarg yang diekstrak, misalnya:

func(param1, param2, **getValidKwargs(func, kwargsDict))

Ini mengasumsikan bahwa functidak menggunakan *argsatau **kwargssihir dalam tanda tangannya.

DzinX
sumber
bagaimana jika saya ingin mencetak "kata kunci" argumen "kunci" saja?
Jia
7

Di Python 3.0:

>>> import inspect
>>> import fileinput
>>> print(inspect.getfullargspec(fileinput.input))
FullArgSpec(args=['files', 'inplace', 'backup', 'bufsize', 'mode', 'openhook'],
varargs=None, varkw=None, defaults=(None, 0, '', 0, 'r', None), kwonlyargs=[], 
kwdefaults=None, annotations={})
jfs
sumber
7

Untuk solusi Python 3, Anda dapat menggunakan inspect.signaturedan memfilter menurut jenis parameternya yang ingin Anda ketahui.

Mengambil contoh fungsi dengan posisi atau kata kunci, hanya kata kunci, var posisi dan parameter kata kunci var:

def spam(a, b=1, *args, c=2, **kwargs):
    print(a, b, args, c, kwargs)

Anda dapat membuat objek tanda tangan untuk itu:

from inspect import signature
sig =  signature(spam)

dan kemudian filter dengan pemahaman daftar untuk mengetahui detail yang Anda butuhkan:

>>> # positional or keyword
>>> [p.name for p in sig.parameters.values() if p.kind == p.POSITIONAL_OR_KEYWORD]
['a', 'b']
>>> # keyword only
>>> [p.name for p in sig.parameters.values() if p.kind == p.KEYWORD_ONLY]
['c']

dan, demikian pula, untuk posisi var menggunakan p.VAR_POSITIONALkata kunci dan var denganVAR_KEYWORD .

Selain itu, Anda dapat menambahkan klausa ke jika untuk memeriksa apakah ada nilai default dengan memeriksa apakah p.defaultsama p.empty.

Dimitris Fasarakis Hilliard
sumber
3

Memperluas jawaban DzinX:

argnames = example.func_code.co_varnames[:func.func_code.co_argcount]
args = dict((key, val) for key,val in d_args.iteritems() if key in argnames)
example(**args)
Claudiu
sumber