Apakah ada cara mudah dengan Python untuk memeriksa apakah nilai parameter opsional berasal dari nilai defaultnya, atau karena pengguna telah menyetelnya secara eksplisit pada pemanggilan fungsi?
python
function
optional-parameters
Matthias
sumber
sumber
None
sebagai default dan periksa itu. Jika Anda benar-benar dapat menyiapkan pengujian ini, Anda juga akan mengecualikan kemungkinan apa pun bagi pengguna untuk secara eksplisit meneruskan nilai yang memanggil perilaku default.Class My(): def __init__(self, _p=None, a=True, b=True, c=False)
Pengguna menyebutnya denganx=My(b=False)
. Metode kelas dapat memanggil dirinya sendiri denganx=My(_p=self, c=True)
jika fungsi dapat mendeteksi bahwa b tidak disetel secara eksplisit dan variabel yang tidak disetel akan diturunkan dari tingkat atas. Tetapi jika mereka tidak bisa, panggilan rekursif harus melewati setiap variabel secara eksplisit:x=My(a=self.a, b=self.b, c=True, d=self.d, ...)
.x=My()
danx=My(a=True)
. Skenario Anda melibatkan penetapan parameter opsional nilai selain nilai defaultnya.Jawaban:
Banyak jawaban memiliki potongan-potongan kecil dari info lengkap, jadi saya ingin menggabungkan semuanya dengan pola favorit saya.
nilai default adalah
mutable
tipeJika nilai default adalah objek yang bisa berubah, Anda beruntung: Anda dapat mengeksploitasi fakta bahwa argumen default Python dievaluasi satu kali ketika fungsi didefinisikan (lebih banyak lagi tentang ini di akhir jawaban di bagian terakhir)
Ini berarti Anda dapat dengan mudah membandingkan nilai default yang dapat berubah menggunakan
is
untuk melihat apakah nilai itu diteruskan sebagai argumen atau dibiarkan secara default, seperti dalam contoh berikut sebagai fungsi atau metode:def f(value={}): if value is f.__defaults__[0]: print('default') else: print('passed in the call')
dan
class A: def f(self, value={}): if value is self.f.__defaults__[0]: print('default') else: print('passed in the call')
Argumen default yang tidak dapat diubah
Sekarang, agak kurang elegan jika default Anda diharapkan menjadi
immutable
nilai (dan ingat bahwa bahkan string tidak dapat diubah!) Karena Anda tidak dapat mengeksploitasi trik apa adanya, tetapi masih ada sesuatu yang dapat Anda lakukan, masih mengeksploitasi mutable Tipe; pada dasarnya Anda menempatkan default "palsu" yang bisa berubah di tanda tangan fungsi, dan nilai default "nyata" yang diinginkan di badan fungsi.def f(value={}): """ my function :param value: value for my function; default is 1 """ if value is f.__defaults__[0]: print('default') value = 1 else: print('passed in the call') # whatever I want to do with the value print(value)
Rasanya sangat lucu jika Anda benar-benar default
None
, tetapiNone
tetap jadi ... Anda masih perlu secara eksplisit menggunakan mutable sebagai parameter default fungsi, dan beralih ke None dalam kode.Menggunakan
Default
kelas untuk default yang tidak dapat diubahatau, mirip dengan saran @cz, jika dokumen python tidak cukup :-), Anda dapat menambahkan objek di antaranya untuk membuat API lebih eksplisit (tanpa membaca dokumen); instance kelas used_proxy_ Default bisa berubah, dan akan berisi nilai default sebenarnya yang ingin Anda gunakan.
class Default: def __repr__(self): return "Default Value: {} ({})".format(self.value, type(self.value)) def __init__(self, value): self.value = value def f(default=Default(1)): if default is f.__defaults__[0]: print('default') print(default) default = default.value else: print('passed in the call') print("argument is: {}".format(default))
sekarang:
>>> f() default Default Value: 1 (<class 'int'>) argument is: 1 >>> f(2) passed in the call argument is: 2
Di atas bekerja dengan baik juga untuk
Default(None)
.Pola lainnya
Jelas sekali pola di atas terlihat lebih jelek dari yang seharusnya karena semua
print
yang ada hanya untuk menunjukkan bagaimana mereka bekerja. Kalau tidak, saya menganggapnya singkat dan cukup berulang.Anda dapat menulis dekorator untuk menambahkan
__call__
pola yang disarankan oleh @dmg dengan cara yang lebih efisien, tetapi ini tetap mengharuskan penggunaan trik aneh dalam definisi fungsi itu sendiri - Anda perlu membagivalue
danvalue_default
jika kode Anda perlu membedakannya, jadi Saya tidak melihat banyak keuntungan dan saya tidak akan menulis contohnya :-)Jenis yang dapat berubah sebagai nilai default dengan Python
Sedikit lebih banyak tentang # 1 python gotcha! , disalahgunakan untuk kesenangan Anda sendiri di atas. Anda dapat melihat apa yang terjadi karena evaluasi pada definisi dengan melakukan:
def testme(default=[]): print(id(default))
Anda dapat menjalankan
testme()
sebanyak yang Anda inginkan, Anda akan selalu melihat referensi ke instance default yang sama (jadi pada dasarnya default Anda tidak dapat diubah :-)).Ingat bahwa dalam Python hanya ada 3 bisa berubah built-in jenis :
set
,list
,dict
; yang lainnya - bahkan string! - tidak bisa diubah.sumber
1
, yang seharusnya tidak berubah ...def f(value={})
.1
; maaf jika itu tidak jelas dalam penjelasannya, tetapi inti dari bagian tanggapan itu adalah agar dapat memiliki default yang tidak dapat diubah (1
). Jika Anda memeriksa contoh, Anda akan melihatnya mengatakanprint('default'); value = 1
value={}
Tidak juga. Cara standar adalah dengan menggunakan nilai default yang tidak diharapkan oleh pengguna untuk dilewati, misalnya sebuah
object
contoh:DEFAULT = object() def foo(param=DEFAULT): if param is DEFAULT: ...
Biasanya Anda hanya dapat menggunakan
None
sebagai nilai default, jika tidak masuk akal sebagai nilai yang ingin diteruskan oleh pengguna.Alternatifnya adalah dengan menggunakan
kwargs
:def foo(**kwargs): if 'param' in kwargs: param = kwargs['param'] else: ...
Namun ini terlalu bertele-tele dan membuat fungsi Anda lebih sulit digunakan karena dokumentasinya tidak akan secara otomatis menyertakan
param
parameter.sumber
Ellipsis
singleton sebagai default, yang secara eksplisit dirancang untuk digunakan sebagai pengabaian nilai ini ....
adalah alias untukEllipsis
, jadi pengguna yang ingin menggunakan argumen posisi dapat memanggilyour_function(p1, ..., p3)
yang membuatnya jelas dan bagus untuk dibaca.However this is overly verbose and makes your function more difficult to use as its documentation will not automatically include the param parameter.
Ini sebenarnya tidak benar, karena Anda dapat mengatur deskripsi fungsi dan parameternya menggunakaninspect
modul. Itu tergantung pada IDE Anda apakah itu akan berfungsi atau tidak.Penghias fungsi berikut
explicit_checker
,, membuat satu set nama parameter dari semua parameter yang diberikan secara eksplisit. Ini menambahkan hasil sebagai parameter tambahan (explicit_params
) ke fungsi. Lakukan saja'a' in explicit_params
untuk memeriksa apakah parametera
diberikan secara eksplisit.def explicit_checker(f): varnames = f.func_code.co_varnames def wrapper(*a, **kw): kw['explicit_params'] = set(list(varnames[:len(a)]) + kw.keys()) return f(*a, **kw) return wrapper @explicit_checker def my_function(a, b=0, c=1, explicit_params=None): print a, b, c, explicit_params if 'b' in explicit_params: pass # Do whatever you want my_function(1) my_function(1, 0) my_function(1, c=1)
sumber
Saya terkadang menggunakan string unik universal (seperti UUID).
import uuid DEFAULT = uuid.uuid4() def foo(arg=DEFAULT): if arg is DEFAULT: # it was not passed in else: # it was passed in
Dengan cara ini, tidak ada pengguna yang bisa menebak default jika mereka mencoba sehingga saya bisa sangat yakin bahwa ketika saya melihat nilai
arg
itu, itu tidak diteruskan.sumber
object()
sebagai gantinyauuid4()
- ini masih contoh unik , itulah yangis
diperiksaAku pernah melihat pola ini beberapa kali (misalnya perpustakaan
unittest
,py-flags
,jinja
):class Default: def __repr__( self ): return "DEFAULT" DEFAULT = Default()
... atau satu baris yang setara ...:
DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
Tidak seperti
DEFAULT = object()
, ini membantu pengecekan tipe dan memberikan informasi ketika terjadi kesalahan - seringkali representasi string ("DEFAULT"
) atau nama kelas ("Default"
) digunakan dalam pesan kesalahan.sumber
@ Jawaban Ellioh berfungsi di python 2. Di python 3, kode berikut harus berfungsi:
import inspect def explicit_checker(f): varnames = inspect.getfullargspec(f)[0] def wrapper(*a, **kw): kw['explicit_params'] = set(list(varnames[:len(a)]) + list(kw.keys())) return f(*a, **kw) return wrapper @explicit_checker def my_function(a, b=0, c=1, explicit_params=None): print a, b, c, explicit_params if 'b' in explicit_params: pass # Do whatever you want
Metode ini dapat menyimpan nama argumen dan nilai default (bukan ** kwargs) dengan keterbacaan yang lebih baik.
sumber
Anda dapat memeriksanya dari
foo.__defaults__
danfoo.__kwdefaults__
lihat contoh sederhana di bawah ini
def foo(a, b, c=123, d=456, *, e=789, f=100): print(foo.__defaults__) # (123, 456) print(foo.__kwdefaults__) # {'e': 789, 'f': 100} print(a, b, c, d, e, f) #and these variables are also accessible out of function body print(foo.__defaults__) # (123, 456) print(foo.__kwdefaults__) # {'e': 789, 'f': 100} foo.__kwdefaults__['e'] = 100500 foo(1, 2) #(123, 456) #{'f': 100, 'e': 100500} #1 2 123 456 100500 100
kemudian dengan menggunakan operator
=
danis
Anda dapat membandingkannyadan untuk beberapa kasus kode di bawah ini sudah cukup
Misalnya, Anda perlu menghindari perubahan nilai default, maka Anda dapat memeriksa kesetaraan dan menyalinnya jika demikian
def update_and_show(data=Example): if data is Example: data = copy.deepcopy(data) update_inplace(data) #some operation print(data)
Juga, sangat nyaman untuk digunakan
getcallargs
dariinspect
seperti mengembalikan argumen nyata dengan yang fungsi akan dipanggil. Anda meneruskan fungsi dan args dan kwargs ke it (inspect.getcallargs(func, /, *args, **kwds)
), itu akan mengembalikan argumen metode nyata yang digunakan untuk pemanggilan, dengan mempertimbangkan nilai default dan hal-hal lainnya. Lihat contoh di bawah ini.from inspect import getcallargs # we have a function with such signature def show_params(first, second, third=3): pass # if you wanted to invoke it with such params (you could get them from a decorator as example) args = [1, 2, 5] kwargs = {} print(getcallargs(show_params, *args, **kwargs)) #{'first': 1, 'second': 2, 'third': 5} # here we didn't specify value for d args = [1, 2, 3, 4] kwargs = {} # ---------------------------------------------------------- # but d has default value =7 def show_params1(first, *second, d = 7): pass print(getcallargs(show_params1, *args, **kwargs)) # it will consider b to be equal to default value 7 as it is in real method invocation # {'first': 1, 'second': (2, 3, 4), 'd': 7} # ---------------------------------------------------------- args = [1] kwargs = {"d": 4} def show_params2(first, d=3): pass print(getcallargs(show_params2, *args, **kwargs)) #{'first': 1, 'd': 4}
https://docs.python.org/3/library/inspect.html
sumber
Saya setuju dengan komentar Volatility. Tetapi Anda dapat memeriksanya dengan cara berikut:
def function(arg1,...,**optional): if 'optional_arg' in optional: # user has set 'optional_arg' else: # user has not set 'optional_arg' optional['optional_arg'] = optional_arg_default_value # set default
sumber
def func(optional=value)
tidak**kwargs
**kwargs
sedikit berbeda. PS tidak masalah tentang -1 :) Dan -1 saya untuk Anda tidak disengaja :)Ini adalah variasi dari jawaban Stefano, tetapi menurut saya sedikit lebih mudah dibaca:
not_specified = {} def foo(x=not_specified): if x is not_specified: print("not specified") else: print("specified")
sumber
Pendekatan yang sedikit aneh adalah:
class CheckerFunction(object): def __init__(self, function, **defaults): self.function = function self.defaults = defaults def __call__(self, **kwargs): for key in self.defaults: if(key in kwargs): if(kwargs[key] == self.defaults[key]): print 'passed default' else: print 'passed different' else: print 'not passed' kwargs[key] = self.defaults[key] return self.function(**kwargs) def f(a): print a check_f = CheckerFunction(f, a='z') check_f(a='z') check_f(a='b') check_f()
Output mana:
passed default z passed different b not passed z
Sekarang ini, seperti yang saya sebutkan, cukup aneh, tapi berhasil. Namun hal ini cukup dibaca dan juga untuk ecatmur 's saran tidak akan secara otomatis didokumentasikan.
sumber
check_f('z')
, yang juga, seperti yang Anda katakan, aneh.