옵션 기능 매개 변수 설정 여부 확인 방법


90

선택적 매개 변수의 값이 기본값에서 오는지 또는 사용자가 함수 호출에서 명시 적으로 설정했기 때문에 Python에서 쉽게 확인할 수있는 방법이 있습니까?


12
당연히 그 함수에서 확인하고 싶기 때문에 :)
Matthias

2
그냥 사용 None기본으로하고 있는지 확인합니다. 이 테스트를 실제로 설정할 수 있다면 사용자가 기본 동작을 호출하는 값을 명시 적으로 전달할 가능성도 배제 할 수 있습니다.
Michael J. Barber

3
적어도 CPython의 경우 수락 한 답변보다 훨씬 더 재사용 가능하고 아름다운 방식으로 수행 할 수 있습니다. 아래 내 대답을 참조하십시오.
Ellioh 2013

2
@Volatility : 두 세트의 기본값이있는 경우 중요합니다. 재귀 클래스를 고려하십시오. Class My(): def __init__(self, _p=None, a=True, b=True, c=False) 사용자가 x=My(b=False). 클래스 메서드는 x=My(_p=self, c=True)b가 명시 적으로 설정되지 않았고 설정되지 않은 변수가 최상위 수준에서 전달된다는 것을 함수가 감지 할 수있는 경우 자신을 호출 할 수 있습니다. 그러나 할 수없는 경우 재귀 호출은 모든 변수를 명시 적으로 전달해야합니다 x=My(a=self.a, b=self.b, c=True, d=self.d, ...)..
Dave

@ 데이브 그러나 그 질문에 관한 것입니까? 내 이해에서 질문은 x=My()x=My(a=True). 시나리오에는 선택적 매개 변수에 기본값이 아닌 값을 할당하는 것이 포함됩니다.
변동성

답변:


15

많은 답변에 전체 정보의 작은 조각이 있으므로 내가 가장 좋아하는 패턴과 함께 모두 가져오고 싶습니다.

기본값은 mutable유형입니다.

기본값이 변경 가능한 객체 인 경우 운이 좋습니다. 함수가 정의 될 때 Python의 기본 인수가 한 번 평가된다는 사실을 이용할 수 있습니다 (마지막 섹션의 답변 끝에서 이에 대해 더 자세히 설명합니다).

is, 함수 또는 메서드로 다음 예제에서와 같이 기본 변경 가능 값을를 사용하여 쉽게 비교 하여 인수로 전달되었는지 또는 기본적으로 남아 있는지 확인할 수 있습니다.

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

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

변경 불가능한 기본 인수

이제 기본값이 immutable값 이 될 것으로 예상되는 경우 (문자열도 불변임을 기억하십시오!) 트릭을있는 그대로 악용 할 수 없지만 여전히 변경할 수있는 작업이 있기 때문에 다소 덜 우아합니다 . 유형; 기본적으로 함수 시그니처에 가변 "가짜"기본값을, 함수 본문에 원하는 "실제"기본값 을 넣습니다 .

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)

실제 기본값이 None이지만 None변경 불가능한 경우 특히 재미있게 느껴지 므로 여전히 변경 가능을 함수 기본 매개 변수로 명시 적으로 사용하고 코드에서 없음으로 전환해야합니다.

Default변경 불가능한 기본값에 클래스 사용

또는 @cz 제안과 유사하게, 파이썬 문서가 충분하지 않다면 :-), API를 더 명시 적으로 만들기 위해 (문서를 읽지 않고) 사이에 객체를 추가 할 수 있습니다. used_proxy_ Default 클래스 인스턴스는 변경 가능하며 사용하려는 실제 기본값을 포함합니다.

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

지금:

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

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

위의 내용은 Default(None).

기타 패턴

분명히 위의 패턴은 print작동 방식을 보여주기 위해 존재하는 모든 것 때문에 예상보다 추악 해 보입니다 . 그렇지 않으면 나는 그것들을 간결하고 충분히 반복 할 수 있다고 생각합니다.

당신은 추가 할 수있는 장식 쓸 수있는 __call__당신이 밖으로 분할해야 - 더 능률적 인 방법으로 @dmg에 의해 제안 패턴을, 그러나 이것은 여전히 함수 정의 자체가 이상한 트릭을 사용하도록 강요합니다 valuevalue_default그래서, 당신의 코드를 필요 구별하는 경우 그 나는 많은 이점을 보지 못하고 예제를 쓰지 않을 것입니다 :-)

Python에서 기본값으로 변경 가능한 유형

# 1 python gotcha 에 대해 조금 더 ! , 위의 자신의 즐거움을 위해 학대 받았습니다. 다음을 수행하여 정의 에서 평가 로 인해 어떤 일이 발생하는지 확인할 수 있습니다 .

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

원하는 testme()만큼 실행할 수 있으며 항상 동일한 기본 인스턴스에 대한 참조를 볼 수 있습니다 (기본적으로 기본값은 변경 불가능합니다 :-)).

파이썬에 있다는 것을 기억 불과 3 변경 가능한 내장 유형 : set, list, dict, 다른 모든 것-심지어 문자열! -불변입니다.


"불변 기본 인수"에있는 예제에는 실제로 불변 기본 인수가 없습니다. 그랬다면 작동하지 않을 것입니다.
Karol

@Karol, 자세히 설명 하시겠습니까? 이 예에서 기본값은입니다.이 값은 1변경할 수 없습니다 ...
Stefano

나는 함수의 서명을 def f(value={}).
Karol

@Karol은 서명이지만 원하는 기본값은 1; 설명에서 명확하지 않은 경우 미안하지만 응답의 해당 부분의 요점은 변경 불가능한 기본값 ( 1) 을 가질 수 있다는 것 입니다. 예를 확인하면 다음과 같이 표시됩니다. print('default'); value = 1, notvalue={}
Stefano

1
하, 이제 알겠습니다. 감사합니다. 누군가가 당신의 텍스트를 매우주의 깊게 읽지 않는 한 따라 가기는 쉽지 않습니다. 단어 변경을 고려하십시오.
Karol 2011

56

별로. 표준 방법은 사용자가 전달할 것으로 예상되지 않는 기본값 ( object예 : 인스턴스 )을 사용하는 것입니다 .

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

일반적으로 None사용자가 전달하려는 값으로 이해되지 않는 경우 기본값으로 사용할 수 있습니다 .

대안은 다음을 사용하는 것입니다 kwargs.

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

그러나 이것은 지나치게 장황하고 문서에 param매개 변수가 자동으로 포함되지 않으므로 함수를 사용하기가 더 어려워집니다 .


8
나는 또한 이것이 필요하고 None이 유효한 입력으로 간주되는 곳에서 Ellipsis 빌트인을 사용하는 몇몇 사람들을 보았습니다. 이것은 본질적으로 첫 번째 예와 동일합니다.
GrandOpener 2013

None이 전달 된 경우 특별한 동작을 구현하고 싶지만 사용자가 인수를 제공했는지 테스트하는 방법이 필요한 경우이 값을 건너 뛰Ellipsis 도록 명시 적으로 설계된 싱글 톤을 기본값으로 사용할 수 있습니다 . 는의 별칭 이므로 위치 인수를 사용하려는 사용자 는 호출하기 만하면 읽기 쉽고 읽기 쉽습니다. ...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.inspect모듈을 사용하여 함수 및 매개 변수에 대한 설명을 설정할 수 있으므로 실제로는 사실이 아닙니다 . 작동 여부는 IDE에 따라 다릅니다.
EZLearner

15

다음 함수 데코레이터 explicit_checker는 명시 적으로 주어진 모든 매개 변수의 매개 변수 이름 세트를 만듭니다. 결과를 explicit_params함수에 추가 매개 변수 ( )로 추가 합니다. 'a' in explicit_params매개 변수 a가 명시 적으로 주어 졌는지 확인하기 만하면 됩니다.

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)

이 코드는 python2에서만 작동합니다. 파이썬 3의 경우 아래 내 대답을 참조하십시오. stackoverflow.com/questions/14749328/…
R. Yang

1
이것은 꽤 멋지지만 가능하다면 처음부터 더 나은 디자인으로 문제를 피하는 것이 좋습니다.
Karol

@Karol, 동의합니다. 대부분의 경우 디자인이 합리적이라면 필요하지 않습니다.
Ellioh

4

때때로 UUID와 같은 보편적으로 고유 한 문자열을 사용합니다.

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

이렇게하면 어떤 사용자도 시도해도 기본값을 추측 할 수 없으므로에 대한 값을 볼 때 arg전달되지 않았 음을 확신 할 수 있습니다 .


4
Python 객체는 참조입니다. object()대신 사용할 수 있습니다 uuid4().-여전히 고유 한 인스턴스 입니다. is확인합니다.
cz

3

나는이 패턴을 본 적이 몇 번 (예를 들어, 도서관 unittest, py-flags, jinja) :

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

DEFAULT = Default()

... 또는 이에 상응하는 한 줄짜리 ... :

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

과 달리 DEFAULT = object()이것은 유형 검사를 지원하고 오류가 발생할 때 정보를 제공합니다. 문자열 표현 ( "DEFAULT") 또는 클래스 이름 ( "Default")이 오류 메시지에 자주 사용됩니다.


3

@Ellioh의 대답은 python 2에서 작동합니다. python 3에서는 다음 코드가 작동합니다.

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

이 메서드는 더 나은 가독성으로 인수 이름과 기본값 (** kwargs 대신)을 유지할 수 있습니다.


3

당신은에서 확인할 수 있습니다 foo.__defaults__foo.__kwdefaults__

아래의 간단한 예를보십시오

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

다음 연산자를 사용하여 =그리고 is당신이 그들을 비교할 수 있습니다

그리고 어떤 경우에는 코드 벨로우즈로 충분합니다

예를 들어 기본값 변경을 피해야합니다. 그러면 동등성을 확인한 다음 복사 할 수 있습니다.

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

또한 함수가 호출되는 실제 인수를 반환 하므로 getcallargsfrom 을 사용하는 것이 매우 편리합니다 inspect. 함수와 args 및 kwargs를 전달하면 ( inspect.getcallargs(func, /, *args, **kwds)) 호출에 사용되는 실제 메서드의 인수를 반환하고 기본값 및 기타 사항을 고려합니다. 아래의 예를 살펴보십시오.

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의 의견에 동의합니다. 그러나 다음과 같은 방식으로 확인할 수 있습니다.

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

나는 선택적 매개 변수는 뭔가처럼 생각 def func(optional=value)하지**kwargs
Zaur Nasibov

그것은 해석에 다소 개방적인 것입니다. 기본값이있는 인수와 키워드 인수의 실제 차이점은 무엇입니까? 둘 다 동일한 구문 "keyword = value"를 사용하여 표현됩니다.
isedev 2013

옵션 매개 변수의 목적이 **kwargs조금 다르기 때문에 동의하지 않습니다 . PS이 문제에 대해 -1 : 없습니다 그리고 내 -1 : 당신이 실수였던 것
Zaur Nasibov

2

이것은 stefano의 답변에 대한 변형이지만 조금 더 읽기 쉽습니다.

not_specified = {}

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

하나의 찬성 ?? 나는 이것이 가장 좋다. 단순하고 반성이 없습니다. 읽을 수 있습니다.
vincent

1

약간 이상한 접근 방식은 다음과 같습니다.

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

출력되는 내용 :

passed default
z
passed different
b
not passed
z

이제 제가 언급했듯이 이것은 매우 기괴한 일이지만 일을합니다. 그러나 이것은 매우 읽을 수 없으며 ecatmur제안 과 유사하게 자동으로 문서화되지 않습니다.


1
의 동작을 포함하고 싶을 수 있습니다 check_f('z'). 이것은 또한 말했듯이 기이합니다.
Michael J. Barber

@MichaelJ. Barber 좋은 지적입니다. * args를 사용하여 "마법"을 수행해야합니다. 그러나 내 요점은 가능하다는 것이었지만 기본값 전달 여부를 지금 필요로하는 것은 잘못된 설계입니다.
dmg
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.