Python'da parametrelerin zorla adlandırılması


111

Python'da bir işlev tanımınız olabilir:

def info(object, spacing=10, collapse=1)

aşağıdaki yollardan herhangi biriyle çağrılabilir:

info(odbchelper)                    
info(odbchelper, 12)                
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)

Python'un, adlandırıldıkları sürece herhangi bir sıralı argümanlara izin vermesi sayesinde.

Yaşadığımız sorun, daha büyük işlevlerimizden bazıları büyüdükçe, insanlar spacingve arasına parametreler ekliyor olabilir collapse, bu da yanlış değerlerin adlandırılmayan parametrelere gidebileceği anlamına gelir. Ek olarak, bazen neyin girilmesi gerektiği her zaman net değildir. İnsanları belirli parametreleri adlandırmaya zorlamanın bir yolunun peşindeyiz - sadece bir kodlama standardı değil, ideal olarak bir bayrak veya pydev eklentisi mi?

böylece yukarıdaki 4 örnekte, tüm parametreler adlandırıldığı için yalnızca sonuncusu kontrolü geçecektir.

Muhtemelen, onu yalnızca belirli işlevler için açacağız, ancak bunun nasıl uygulanacağına dair herhangi bir öneri - ya da mümkünse bile takdir edilecektir.

Yanıtlar:


214

Python 3'te - Evet, *bağımsız değişken listesinde belirtebilirsiniz .

Gönderen docs :

"*" Veya "* tanımlayıcı" dan sonraki parametreler yalnızca anahtar kelime parametreleridir ve yalnızca kullanılan anahtar kelime bağımsız değişkenleri olarak iletilebilir.

>>> def foo(pos, *, forcenamed):
...   print(pos, forcenamed)
... 
>>> foo(pos=10, forcenamed=20)
10 20
>>> foo(10, forcenamed=20)
10 20
>>> foo(10, 20)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 positional argument (2 given)

Bu aynı zamanda şunlarla birleştirilebilir **kwargs:

def foo(pos, *, forcenamed, **kwargs):

32

Aşağıdaki şekilde bir işlevi tanımlayarak insanları Python3'te anahtar kelime argümanlarını kullanmaya zorlayabilirsiniz.

def foo(*, arg0="default0", arg1="default1", arg2="default2"):
    pass

İlk argümanı isimsiz konumsal bir argüman haline getirerek, işlevi çağıran herkesi, sorduğunuzu düşündüğüm anahtar kelime argümanlarını kullanmaya zorlarsınız. Python2'de bunu yapmanın tek yolu bunun gibi bir işlevi tanımlamaktır

def foo(**kwargs):
    pass

Bu, arayan kişiyi kwarg kullanmaya zorlar, ancak bu o kadar da harika bir çözüm değildir, çünkü daha sonra yalnızca ihtiyacınız olan argümanı kabul etmek için bir kontrol yapmanız gerekir.


11

Doğru, çoğu programlama dili parametre sırasını işlev çağrısı sözleşmesinin bir parçası yapar, ancak bunun böyle olması gerekmez . Neden yapsın? O halde soruyu anladığım, Python'un bu açıdan diğer programlama dillerinden herhangi bir farkı olup olmadığıdır. Python 2 için diğer iyi yanıtlara ek olarak, lütfen aşağıdakileri de göz önünde bulundurun:

__named_only_start = object()

def info(param1,param2,param3,_p=__named_only_start,spacing=10,collapse=1):
    if _p is not __named_only_start:
        raise TypeError("info() takes at most 3 positional arguments")
    return str(param1+param2+param3) +"-"+ str(spacing) +"-"+ str(collapse)

Bir arayanın argümanlar spacingve collapsekonumsal olarak (istisnasız) sunabilmesinin tek yolu şudur :

info(arg1, arg2, arg3, module.__named_only_start, 11, 2)

Diğer modüllere ait özel öğeleri kullanmama kuralı Python'da zaten çok basittir. Python'un kendisinde olduğu gibi, parametreler için bu kural yalnızca yarı zorlanır.

Aksi takdirde, aramaların şu biçimde olması gerekir:

info(arg1, arg2, arg3, spacing=11, collapse=2)

Bir arama

info(arg1, arg2, arg3, 11, 2)

parametreye 11 değerini atar _pve fonksiyonun ilk talimatıyla ortaya çıkan bir istisna.

Özellikler:

  • Önceki parametreler _p=__named_only_startkonumsal olarak (veya adıyla) kabul edilir.
  • Daha sonraki parametreler _p=__named_only_startyalnızca isimle sağlanmalıdır (özel gözlemci nesnesi hakkında bilgi __named_only_startedinilmediği ve kullanılmadığı sürece ).

Artıları:

  • Parametreler sayı ve anlam bakımından açıktır (tabii ki iyi isimler de seçilirse daha sonra).
  • Sentinel ilk parametre olarak belirtilmişse, tüm argümanların isme göre belirtilmesi gerekir.
  • Fonksiyonu çağırırken __named_only_start, ilgili pozisyondaki gözcü nesneyi kullanarak pozisyon moduna geçmek mümkündür .
  • Diğer alternatiflerden daha iyi bir performans beklenebilir.

Eksileri:

  • Denetleme, derleme zamanında değil, çalıştırma sırasında gerçekleşir.
  • Ekstra bir parametrenin kullanılması (bağımsız değişken olmasa da) ve ek bir kontrol. Normal işlevlere göre küçük performans düşüşü.
  • İşlevsellik, dil tarafından doğrudan desteklenmeyen bir hack'tir (aşağıdaki nota bakın).
  • İşlevi çağırırken, sentinel nesneyi __named_only_startdoğru konumda kullanarak konumsal moda geçmek mümkündür . Evet, bu aynı zamanda bir profesyonel olarak da görülebilir.

Lütfen bu yanıtın yalnızca Python 2 için geçerli olduğunu unutmayın. Python 3, diğer yanıtlarda açıklanan benzer, ancak çok zarif, dil destekli mekanizmayı uygular.

Zihnimi açıp düşündüğümde, hiçbir sorunun veya başkasının kararının aptalca, aptalca veya aptalca görünmediğini anladım. Tam tersine: Genelde çok şey öğreniyorum.


"Denetim, derleme zamanında değil, çalışma zamanı sırasında gerçekleşir." - Sanırım bu, tüm işlev bağımsız değişkenleri için geçerli. İşlev çağırma satırını gerçekten çalıştırana kadar, hangi işlevin çalıştırıldığını her zaman bilemezsiniz. Ayrıca +1 - bu akıllıca.
Eric

@Eric: Sadece statik kontrolü tercih ederdim. Ama haklısın: bu Python olmazdı. Belirleyici bir nokta olmasa da, Python 3'ün "*" yapısı da dinamik olarak kontrol edilir. Yorumun için teşekkürler.
Mario Rossi

Ayrıca, modül değişkenini adlandırırsanız _named_only_start, ona bir pro ve bir ek çıkaran harici bir modülden başvurmak imkansız hale gelir. (modül kapsamındaki tek baştaki alt çizgiler özeldir, IIRC)
Eric

Nöbetçinin adlandırma ile ilgili olarak, biz de a ikisine de sahip olabilir __named_only_startve bir named_only_starttek kamu ve şekilde (hiçbir başlangıç çizgi), ikinci (ancak, "aktif terfi" olma seviyesine, yani adlandırılmış modu "tavsiye" olduğunu belirtmek için diğeri değildir). Alt çizgilerle _namesbaşlamanın "özelliğine" gelince, dil tarafından çok güçlü bir şekilde uygulanmaz: belirli (* olmayan) içe aktarmalar veya nitelikli adlar kullanılarak kolayca aşılabilir. Bu nedenle birçok Python belgesi "özel" yerine "genel olmayan" terimini kullanmayı tercih eder.
Mario Rossi

6

Bunu hem Python 2 hem de Python 3'te çalışacak şekilde, "doğal olarak" oluşmayacak varsayılan bir değere sahip "sahte" bir ilk anahtar kelime argümanı oluşturarak yapabilirsiniz. Bu anahtar kelime bağımsız değişkeninden önce değeri olmayan bir veya daha fazla bağımsız değişken gelebilir:

_dummy = object()

def info(object, _kw=_dummy, spacing=10, collapse=1):
    if _kw is not _dummy:
        raise TypeError("info() takes 1 positional argument but at least 2 were given")

Bu şunlara izin verecektir:

info(odbchelper)        
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)

Ama değil:

info(odbchelper, 12)                

İşlevi şu şekilde değiştirirseniz:

def info(_kw=_dummy, spacing=10, collapse=1):

bu durumda tüm bağımsız değişkenler anahtar kelimelere sahip olmalıdır ve info(odbchelper)artık çalışmayacaktır.

Bu, sizi _kwson girişten sonra koymaya zorlamadan, herhangi bir yere ek anahtar kelime argümanları yerleştirmenize olanak tanır . Bu genellikle mantıklıdır, örneğin bir şeyi mantıksal olarak gruplamak veya anahtar kelimeleri alfabetik olarak düzenlemek bakım ve geliştirmeye yardımcı olabilir.

Bu nedenle def(**kwargs), akıllı düzenleyicinizdeki imza bilgilerini kullanmaya ve kaybetmeye geri dönmenize gerek yoktur . Sosyal sözleşmeniz, (bazılarını) anahtar kelimeler gerektirmeye zorlayarak belirli bilgileri sağlamaktır, bunların sunulduğu sıra alakasız hale gelmiştir.


2

Güncelleme:

Kullanmanın **kwargssorunu çözmeyeceğini anladım . Programcılarınız işlev bağımsız değişkenlerini istedikleri gibi değiştirirse, örneğin işlevi şu şekilde değiştirebilirsiniz:

def info(foo, **kwargs):

ve eski kod tekrar kırılırdı (çünkü artık her işlev çağrısı ilk argümanı içermelidir).

Bu gerçekten Bryan'ın söylediklerine bağlı.


(...) insanlar spacingve collapse(...) arasına parametreler ekliyor olabilir

Genel olarak, fonksiyonları değiştirirken, yeni argümanlar her zaman sona gitmelidir. Aksi takdirde kodu bozar. Açık olmalı.
Birisi işlevi kodun bozulacağı şekilde değiştirirse, bu değişikliğin reddedilmesi gerekir.
(Bryan'ın dediği gibi, bu bir sözleşme gibidir)

(...) bazen neyin girmesi gerektiği her zaman net değildir.

Fonksiyonun imzasına (yani def info(object, spacing=10, collapse=1)) bakıldığında, varsayılan bir değeri olmayan her argümanın zorunlu olduğu hemen görülmelidir . Argümanın
ne için olduğu, dokümana girmelidir.


Eski cevap (eksiksizlik için saklandı) :

Bu muhtemelen iyi bir çözüm değil:

İşlevleri şu şekilde tanımlayabilirsiniz:

def info(**kwargs):
    ''' Some docstring here describing possible and mandatory arguments. '''
    spacing = kwargs.get('spacing', 15)
    obj = kwargs.get('object', None)
    if not obj:
       raise ValueError('object is needed')

kwargsherhangi bir anahtar kelime argümanı içeren bir sözlüktür. Zorunlu bir argümanın mevcut olup olmadığını kontrol edebilir ve yoksa bir istisna oluşturabilirsiniz.

Olumsuz yanı, artık hangi argümanların mümkün olduğu o kadar açık olmayabilir, ancak uygun bir dokümantasyonla, iyi olması gerekir.


3
Eski cevabını daha çok beğendim. Sadece işlevde neden yalnızca ** kwarg kabul ettiğinize dair bir yorum yapın. Sonuçta, herkes kaynak koddaki herhangi bir şeyi değiştirebilir - kararlarınızın arkasındaki amacı ve amacı açıklamak için belgelere ihtiyacınız vardır.
Brandon

Bu cevapta gerçek bir cevap yok!
Phil

2

Yalnızca python3 anahtar kelime argümanları ( *) python2.x içinde simüle edilebilir.**kwargs

Aşağıdaki python3 kodunu düşünün:

def f(pos_arg, *, no_default, has_default='default'):
    print(pos_arg, no_default, has_default)

ve davranışı:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 1 positional argument but 3 were given
>>> f(1, no_default='hi')
1 hi default
>>> f(1, no_default='hi', has_default='hello')
1 hi hello
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required keyword-only argument: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'wat'

Bu benim anahtarlama özgürlük geçtiniz notu aşağıdakileri kullanarak simüle edilebilir TypeErroriçin KeyError"adlı gerekli argüman" durumunda, çok fazla iş aynı durum türü olun yanı etmek olmaz

def f(pos_arg, **kwargs):
    no_default = kwargs.pop('no_default')
    has_default = kwargs.pop('has_default', 'default')
    if kwargs:
        raise TypeError('unexpected keyword argument(s) {}'.format(', '.join(sorted(kwargs))))

    print(pos_arg, no_default, has_default)

Ve davranış:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 1 argument (3 given)
>>> f(1, no_default='hi')
(1, 'hi', 'default')
>>> f(1, no_default='hi', has_default='hello')
(1, 'hi', 'hello')
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
KeyError: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in f
TypeError: unexpected keyword argument(s) wat

Tarif python3.x'te de aynı şekilde çalışır, ancak yalnızca python3.x iseniz kaçınılmalıdır


Ah, kwargs.pop('foo')Python 2 deyimi de öyle mi? Kodlama stilimi güncellemem gerekiyor. Bu yaklaşımı hala Python 3'te kullanıyordum 🤔
Neil

0

İşlevlerinizi **argsyalnızca alıcı olarak ilan edebilirsiniz . Bu, anahtar kelime argümanlarını zorunlu kılar, ancak yalnızca geçerli adların aktarıldığından emin olmak için fazladan işiniz olur.

def foo(**args):
   print args

foo(1,2) # Raises TypeError: foo() takes exactly 0 arguments (2 given)
foo(hello = 1, goodbye = 2) # Works fine.

1
Sadece anahtar kelime kontrolleri eklemek zorunda değilsiniz, aynı zamanda imzalı bir yöntemi çağırması gerektiğini bilen bir tüketiciyi düşünün foo(**kwargs). Ben buna ne aktarırım? foo(killme=True, when="rightnowplease")
Dagrooms

Gerçekten bağlıdır. Düşünün dict.
Noufal İbrahim

0

Diğer yanıtların dediği gibi, işlev imzalarını değiştirmek kötü bir fikirdir. Sona yeni parametreler ekleyin veya bağımsız değişkenler eklenmişse her çağıranı düzeltin.

Yine de yapmak istiyorsanız, bir işlev dekoratörü ve inspect.getargspec işlevini kullanın. Bunun gibi bir şey kullanılırdı:

@require_named_args
def info(object, spacing=10, collapse=1):
    ....

Uygulaması require_named_argsokuyucuya alıştırma olarak bırakılmıştır.

Ben zahmet etmem. İşlev her çağrıldığında yavaş olacak ve kodu daha dikkatli yazarak daha iyi sonuçlar alacaksınız.


-1

**Operatörü kullanabilirsiniz :

def info(**kwargs):

bu şekilde insanlar adlandırılmış parametreleri kullanmaya zorlanır.


2
Ve kodunuzu okumadan yönteminizi nasıl arayacağınız hakkında hiçbir fikriniz yok, tüketiciniz üzerindeki bilişsel yükü artırın :(
Dagrooms

Belirtilen nedenden dolayı bu gerçekten kötü bir uygulamadır ve bundan kaçınılmalıdır.
David S.

-1
def cheeseshop(kind, *arguments, **keywords):

python'da * args kullanırsanız, bu, bu parametre için n sayıda argüman iletebileceğiniz anlamına gelir - bu, erişmek için işlev içinde bir liste gelecek

ve ** kw kullanırsanız, bu onun anahtar kelime bağımsız değişkenleri anlamına gelir, bu dikte olarak erişilebilir - n sayıda kw bağımsız değişkeni iletebilirsiniz ve bu kullanıcının sırayı ve bağımsız değişkenleri sırayla girmesi gerekir, sonra kullanmayın * ve ** - (büyük mimariler için jenerik çözümler sağlamanın pitonik yolu ...)

İşlevinizi varsayılan değerlerle sınırlamak istiyorsanız, içini kontrol edebilirsiniz.

def info(object, spacing, collapse)
  spacing = spacing or 10
  collapse = collapse or 1

boşluğun 0 olması istenirse ne olur? (cevap, 10 alırsın). Bu cevap, tüm diğer ** kwargs'ın aynı nedenlerle verdiği cevaplar kadar yanlış.
Phil

-2

Bir programcının ilk etapta diğer ikisi arasına neden bir parametre ekleyeceğini anlamıyorum.

Fonksiyon parametrelerinin isimlerle (örneğin info(spacing=15, object=odbchelper)) kullanılmasını istiyorsanız, o zaman hangi sırayla tanımlandıkları önemli olmamalıdır, bu yüzden yeni parametreleri en sona koyabilirsiniz.

Düzenin önemli olmasını istiyorsanız, değiştirirseniz hiçbir şeyin çalışmasını bekleyemezsiniz!


2
Bu soruya cevap vermiyor. İyi bir fikir olsun ya da olmasın konu dışıdır - birisi yine de yapabilir.
Graeme Perrow

1
Graeme'nin bahsettiği gibi, yine de birisi yapacak. Ayrıca, başkaları tarafından kullanılmak üzere bir kitaplık yazıyorsanız, yalnızca anahtar kelime bağımsız değişkenlerinin iletilmesini zorlamak (yalnızca python 3), API'nizi yeniden düzenlemeniz gerektiğinde ekstra esneklik sağlar.
s0undt3ch
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.