İsteğe bağlı işlev parametresinin ayarlanıp ayarlanmadığı nasıl kontrol edilir


92

Python'da, isteğe bağlı bir parametrenin değerinin varsayılan değerinden gelip gelmediğini kontrol etmenin kolay bir yolu var mı, yoksa kullanıcı bunu işlev çağrısında açıkça ayarlamış mı?


12
Çünkü elbette bu işlevi kontrol etmek istiyorum :)
Matthias

2
NoneVarsayılan olarak kullanın ve kontrol edin. Bu testi gerçekten ayarlayabilseydiniz, kullanıcının varsayılan davranışı çağıran değeri açıkça geçirme olasılığını da hariç tutarsınız.
Michael J. Barber

3
Bu, kabul ettiğinizden çok daha yeniden kullanılabilir ve güzel bir şekilde yapılabilir, en azından CPython için. Aşağıdaki cevabıma bakın.
Ellioh

2
@Volatility: İki varsayılan ayarınız varsa önemlidir. Özyinelemeli bir sınıf düşünün: Class My(): def __init__(self, _p=None, a=True, b=True, c=False) Kullanıcı onu ile çağırır x=My(b=False). Bir sınıf yöntemi, x=My(_p=self, c=True)işlevler b'nin açıkça ayarlanmadığını ve ayarlanmayan değişkenlerin en üst seviyeden aktarılacağını algılayabilirse kendisini çağırabilir . Ama bunu yapamazlar eğer, özyinelemeli çağrılar açıkça her değişken geçmek zorunda: x=My(a=self.a, b=self.b, c=True, d=self.d, ...).
Dave

@Dave ama soru bu mu? Benim anlayışıma göre, soru nasıl ayırt edileceğini soruyor x=My()ve x=My(a=True). Senaryonuz, isteğe bağlı parametrelere varsayılan değerlerinden farklı bir değer atamayı içerir.
Oynaklık

Yanıtlar:


16

Pek çok cevapta tam bilginin küçük parçaları var, bu yüzden hepsini en sevdiğim kalıplarla bir araya getirmek istiyorum.

varsayılan değer bir mutabletürdür

Varsayılan değer değiştirilebilir bir nesneyse, şanslısınız: Python'un varsayılan argümanlarının işlev tanımlandığında bir kez değerlendirildiği gerçeğinden yararlanabilirsiniz (bununla ilgili olarak son bölümdeki cevabın sonunda biraz daha fazlası var)

Bu is, işlev veya yöntem olarak aşağıdaki örneklerde olduğu gibi, bir bağımsız değişken olarak iletilip aktarılmadığını görmek için varsayılan değişken bir değeri kullanarak kolayca karşılaştırabileceğiniz anlamına gelir :

def f(value={}):
    if value is f.__defaults__[0]:
        print('default')
    else:
        print('passed in the call')

ve

class A:
    def f(self, value={}):
        if value is self.f.__defaults__[0]:
            print('default')
        else:
            print('passed in the call')

Değişmez varsayılan argümanlar

Şimdi, varsayılan immutabledeğerinizin bir değer olması bekleniyorsa (ve dizelerin bile değişmez olduğunu unutmayın!) Çünkü hileyi olduğu gibi kullanamazsınız, ancak yine de yapabileceğiniz bir şey var, yine de değişken tip; temelde işlev imzasına değişebilir bir "sahte" varsayılan ve işlev gövdesine istenen "gerçek" varsayılan değeri koyarsınız .

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)

Eğer gerçek varsayılan değeriniz ise özellikle komiktir None, ancak Nonedeğişmezdir, bu yüzden ... yine de fonksiyon varsayılan parametresi olarak bir değişkene açık bir şekilde kullanmanız ve kodda Yok'a geçmeniz gerekir.

DefaultDeğişmez varsayılanlar için bir sınıf kullanma

veya @cz önerisine benzer şekilde, python belgeleri yeterli değilse :-), API'yi daha açık hale getirmek için araya bir nesne ekleyebilirsiniz (belgeleri okumadan); used_proxy_ Default sınıf örneği değiştirilebilir ve kullanmak istediğiniz gerçek varsayılan değeri içerecektir.

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

şimdi:

>>> f()
default
Default Value: 1 (<class 'int'>)
argument is: 1

>>> f(2)
passed in the call
argument is: 2

Yukarıdakiler için de güzel çalışıyor Default(None).

Diğer desenler

Açıkça görülüyor ki, yukarıdaki modeller olması gerekenden daha çirkin görünüyor çünkü printbunların hepsi nasıl çalıştıklarını göstermek için var. Aksi takdirde onları yeterince kısa ve tekrarlanabilir buluyorum.

Eklenecek bir dekoratör yazabilirim __call__dışarı bölmek gerekir - daha aerodinamik şekilde @dmg önerdiği model, ama bu hala işlev tanımı kendi içinde garip hileler kullanmak mecbur edecek valueve value_defaultböylece, kod gerek ayırt etmek ise onları Pek avantaj görmüyorum ve örneği yazmayacağım :-)

Python'da varsayılan değerler olarak değişken türler

# 1 python gotcha hakkında biraz daha fazla bilgi ! , yukarıda kendi zevkiniz için istismar edilmiş. Tanımdaki değerlendirmeden dolayı neler olduğunu aşağıdaki şekilde görebilirsiniz :

def testme(default=[]):
    print(id(default))

İstediğiniz kadar çalıştırabilirsiniz testme(), her zaman aynı varsayılan örneğe bir referans göreceksiniz (yani temelde varsayılanınız değişmez :-)).

Python olduğunu unutmayın sadece 3 değişken yerleşik türleri : set, list, dict; diğer her şey - dizeler bile! - değişmezdir.


"Immutable default arguments" içinde sahip olduğunuz örnekte aslında değişmez bir varsayılan argüman yoktur. Olsaydı işe yaramazdı.
Karol

@Karol, detaylandırmak ister misin? Bu örnekteki varsayılan değer, 1değişmez olması gereken ...
Stefano

Fonksiyonun imzasını olarak görüyorum def f(value={}).
Karol

@Karol imzadır, ancak istenen varsayılan değer 1; Açıklamada bu net değilse özür dilerim, ancak yanıtın bu kısmının tüm amacı, sabit bir varsayılana ( 1) sahip olabilmektir . Örneği kontrol ederseniz, şunu göreceksiniz: print('default'); value = 1değilvalue={}
Stefano

1
Ha, şimdi anladım, teşekkürler. Birisi metninizi çok dikkatli bir şekilde okumadıkça takip etmek kolay değildir, bu SO'da çok sık gerçekleşmeyebilir. Yeniden yazmayı düşünün.
Karol

56

Pek sayılmaz. Standart yol, kullanıcının geçmesinin beklenmeyeceği varsayılan bir değer kullanmaktır, örneğin bir objectörnek:

DEFAULT = object()
def foo(param=DEFAULT):
    if param is DEFAULT:
        ...

Genellikle None, kullanıcının geçmek isteyeceği bir değer olarak mantıklı değilse, yalnızca varsayılan değer olarak kullanabilirsiniz .

Alternatif, kullanmaktır kwargs:

def foo(**kwargs):
    if 'param' in kwargs:
        param = kwargs['param']
    else:
        ...

Ancak bu, aşırı derecede ayrıntılıdır ve dokümantasyonu paramparametreyi otomatik olarak içermeyeceğinden işlevinizin kullanımını zorlaştırır .


9
Ayrıca birkaç kişinin gerekli olduğu yerlerde Ellipsis yerleşikini kullandığını ve Hiçbirinin geçerli girdi olarak kabul edildiğini gördüm. Bu esasen ilk örnekle aynıdır.
GrandOpener

Hiçbiri geçirildiyse özel bir davranış uygulamak istiyorsanız, ancak yine de bağımsız değişkenin kullanıcı tarafından verilip verilmediğini test etmek için bir yönteme ihtiyacınız varsa, bu değeri atlamakEllipsis için açıkça tasarlanmış olan tekliyi varsayılan olarak kullanabilirsiniz . için bir takma addır , bu nedenle konumsal argümanlar kullanmak isteyen kullanıcılar arayabilir, bu da onu açık ve okunması güzel yapar. ...Ellipsisyour_function(p1, ..., p3)
Bachsau

However this is overly verbose and makes your function more difficult to use as its documentation will not automatically include the param parameter.inspectModülü kullanarak bir işlevin ve parametrelerinin açıklamasını ayarlayabileceğiniz için bu aslında doğru değildir . IDE'nizin çalışıp çalışmayacağına bağlıdır.
EZLearner

15

Aşağıdaki işlev dekoratörü, explicit_checkeraçıkça verilen tüm parametrelerin bir dizi parametre adını oluşturur. Sonucu explicit_params, fonksiyona ekstra bir parametre ( ) olarak ekler . Sadece 'a' in explicit_paramsparametrenin aaçıkça belirtilip belirtilmediğini kontrol edin .

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)

Bu kod yalnızca python2'de çalışır. Python 3 için aşağıdaki
R. Yang

1
Bu oldukça havalı, ancak mümkünse ilk etapta daha iyi tasarımla sorunu önlemek daha iyidir.
Karol

@Karol, katılıyorum. Çoğu durumda, tasarım makul ise buna ihtiyaç duyulmamalıdır.
Ellioh

4

Bazen evrensel olarak benzersiz bir dize kullanıyorum (bir UUID gibi).

import uuid
DEFAULT = uuid.uuid4()
def foo(arg=DEFAULT):
  if arg is DEFAULT:
    # it was not passed in
  else:
    # it was passed in

Bu şekilde, hiçbir kullanıcı denediğinde varsayılanı tahmin bile edemezdi, bu yüzden için bu değeri gördüğümde arggeçmediğinden çok emin olabilirim .


4
Python nesneleri referanslardır, object()bunun yerine kullanabilirsiniz uuid4()- hala benzersiz bir örnek , bu da iskontrol eder
cz

3

Bu desen gördüğüm bir kaç kez (örneğin kütüphane unittest, py-flags, jinja):

class Default:
    def __repr__( self ):
        return "DEFAULT"

DEFAULT = Default()

... veya eşdeğeri tek satırlık ...:

DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()

Bunun aksine DEFAULT = object(), bu, tür kontrolüne yardımcı olur ve hata oluştuğunda bilgi sağlar - genellikle hata mesajlarında dize temsili ( "DEFAULT") veya sınıf adı ( "Default") kullanılır.


3

@ Ellioh'un cevabı python 2'de çalışır. Python 3'te aşağıdaki kod çalışmalıdır:

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

Bu yöntem, argüman adlarını ve varsayılan değerleri (** kwargs yerine) daha iyi okunabilirlikle tutabilir.


3

Sen onu kontrol edebilirsiniz foo.__defaults__vefoo.__kwdefaults__

aşağıdaki basit bir örneğe bakın

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

Daha sonra operatörünü kullanarak =ve isbunları karşılaştırabilirsiniz

ve bazı durumlarda aşağıdaki kod yeterlidir

Örneğin, varsayılan değeri değiştirmekten kaçınmanız gerekir, ardından eşitliği kontrol edebilir ve ardından kopyalayabilirsiniz.

    def update_and_show(data=Example):
        if data is Example:
            data = copy.deepcopy(data)
        update_inplace(data) #some operation
        print(data)

Ayrıca, kullanımı oldukça uygundur getcallargsgelen inspectbu fonksiyon çağrılır hangi gerçek argümanlar döndürür olarak. Bir işlevi, args ve kwargs'ı ona ( inspect.getcallargs(func, /, *args, **kwds)) iletirseniz , varsayılan değerleri ve diğer şeyleri dikkate alarak, çağırma için kullanılan gerçek yöntemin argümanlarını döndürür. Aşağıdaki örneğe bir göz atın.

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


2

Volatility'nin yorumuna katılıyorum. Ancak aşağıdaki şekilde kontrol edebilirsiniz:

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

İsteğe bağlı bir parametrenin öyle bir şey def func(optional=value)olmadığına inanıyorum**kwargs
Zaur Nasibov

Bu, yoruma biraz açık olan bir şey. Varsayılan değeri olan bir bağımsız değişken ile anahtar kelime bağımsız değişkeni arasındaki gerçek fark nedir? Her ikisi de aynı sözdizimi "anahtar kelime = değer" kullanılarak ifade edilir.
isedev

Katılmıyorum, çünkü isteğe bağlı parametrelerin amacı ve **kwargsbiraz farklı. PS Sorun yok -1 ile ilgili :) Ve benim
-1'im

2

Bu, stefano'nun cevabının bir varyasyonu, ancak biraz daha okunaklı buluyorum:

not_specified = {}

def foo(x=not_specified):
    if x is not_specified:
            print("not specified")
    else:
            print("specified")

Bir olumlu oy ?? Bunu en çok sevdim. Basit, yansıma yok. Okunabilir.
vincent

1

Biraz acayip bir yaklaşım şöyle olurdu:

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

Hangi çıktılar:

passed default
z
passed different
b
not passed
z

Şimdi bu, bahsettiğim gibi, oldukça garip, ama işini yapıyor. Ancak bu oldukça okunamaz ve benzer etmek ecatmur 'ın önerisi otomatik belgelenmiş edilmeyecektir.


1
check_f('z')Dediğiniz gibi garip olan davranışını da dahil etmek isteyebilirsiniz .
Michael J. Barber

@ MichaelJ.Barber İyi nokta. * Args ile de biraz "sihir" yapmanız gerekecek. Ancak, benim açımdan, bunun mümkün olduğuydu, ancak şimdi varsayılan değerin geçilip geçilmeyeceğine ihtiyaç duymak kötü bir tasarım.
dmg
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.