IT story

Python에서 매개 변수의 강제 이름 지정

hot-time 2020. 9. 4. 08:07
반응형

Python에서 매개 변수의 강제 이름 지정


Python에서는 함수 정의가있을 수 있습니다.

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

다음 방법 중 하나로 호출 할 수 있습니다.

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

파이썬이 이름이 지정되어있는 한 임의의 순서 인수를 허용했기 때문입니다.

우리가 겪고있는 문제는 더 큰 함수 중 일부가 커짐에 따라 사람들이 spacing사이에 매개 변수를 추가 collapse할 수 있다는 것입니다. 즉, 이름이 지정되지 않은 매개 변수에 잘못된 값이 전달 될 수 있습니다. 또한 때때로 무엇이 들어가야하는지 항상 명확하지 않은 경우도 있습니다. 우리는 사람들이 특정 매개 변수를 명명하도록 강제하는 방법을 찾고 있습니다. 코딩 표준이 아니라 이상적으로는 플래그 또는 pydev 플러그인입니까?

위의 4 가지 예에서 모든 매개 변수의 이름이 지정되므로 마지막 사람 만 검사를 통과합니다.

확률은 우리가 특정 기능에 대해서만 활성화 할 것이지만이를 구현하는 방법에 대한 제안 또는 가능하다면 감사하겠습니다.


Python 3-예, *인수 목록에서 지정할 수 있습니다 .

에서 문서 :

"*"또는 "* identifier"뒤의 매개 변수는 키워드 전용 매개 변수이며 사용 된 키워드 인수로만 전달 될 수 있습니다.

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

다음과 함께 사용할 수도 있습니다 **kwargs.

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

다음과 같은 방법으로 함수를 정의하여 사람들이 Python3에서 키워드 인수를 사용하도록 할 수 있습니다.

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

첫 번째 인수를 이름이없는 위치 인수로 만들면 함수를 호출하는 모든 사람이 내가 요청한 키워드 인수를 사용하도록 강요합니다. Python2에서이를 수행하는 유일한 방법은 다음과 같은 함수를 정의하는 것입니다.

def foo(**kwargs):
    pass

그러면 호출자가 kwargs를 사용하도록 강요하지만 필요한 인수 만 수락하도록 확인을해야하는만큼 좋은 해결책은 아닙니다.


사실, 대부분의 프로그래밍 언어 는 매개 변수 순서를 함수 호출 계약의 일부로 만들지 만 그럴 필요 는 없습니다 . 왜 그럴까요? 질문에 대한 나의 이해는 파이썬이 이와 관련하여 다른 프로그래밍 언어와 다른지 여부입니다. Python 2에 대한 다른 좋은 답변 외에도 다음을 고려하십시오.

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

호출자가 인수를 제공 spacing하고 collapse위치 (예외없이) 를 제공 할 수있는 유일한 방법은 다음 같습니다.

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

이미 다른 모듈에 속하는 private 요소를 사용하지 않는 규칙은 Python에서 매우 기본적입니다. Python 자체와 마찬가지로 매개 변수에 대한이 규칙은 부분적으로 만 적용됩니다.

그렇지 않으면 호출은 다음과 같은 형식이어야합니다.

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

통화

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

매개 변수에 값 11을 할당 _p하고 함수의 첫 번째 명령에 의해 발생하는 예외 를 지정합니다 .

형질:

  • 앞의 매개 변수 _p=__named_only_start는 위치 (또는 이름)로 허용됩니다.
  • 이후의 매개 변수 _p=__named_only_start는 이름으로 만 제공되어야합니다 (특수 감시 개체에 대한 지식을 __named_only_start얻고 사용 하지 않는 경우 ).

장점 :

  • 매개 변수는 숫자와 의미에서 명시 적입니다 (물론 좋은 이름도 선택되면 나중에).
  • 센티넬이 첫 번째 매개 변수로 지정되면 모든 인수를 이름으로 지정해야합니다.
  • When calling the function, it's possible to switch to positional mode by using the sentinel object __named_only_start in the corresponding position.
  • A better performance than other alternatives can be anticipated.

Cons:

  • Checking occurs during run-time, not compile-time.
  • Use of an extra parameter (though not argument) and an additional check. Small performance degradation respect to regular functions.
  • Functionality is a hack without direct support by the language (see note below).
  • When calling the function, it's possible to switch to positional mode by using the sentinel object __named_only_start in the right position. Yes, this can also be seen as a pro.

Please do keep in mind that this answer is only valid for Python 2. Python 3 implements the similar, but very elegant, language-supported mechanism described in other answers.

I've found that when I open my mind and think about it, no question or other's decision seems stupid, dumb, or just silly. Quite on the contrary: I typically learn a lot.


You can do that in a way that works in both Python 2 and Python 3, by making a "bogus" first keyword argument with a default value that will not occur "naturally". That keyword argument can be preceded by one or more arguments without value:

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

This will allow:

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

but not:

info(odbchelper, 12)                

If you change the function to:

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

then all arguments must have keywords and info(odbchelper) will no longer work.

This will allow you to position additional keyword arguments any place after _kw, without forcing you to put them after the last entry. This often makes sense, e.g. grouping thing logically or arranging keywords alphabetically can help with maintenance and development.

So there is no need to revert to using def(**kwargs) and losing the signature information in your smart editor. Your social contract is to provide certain information, by forcing (some of them) to require keywords, the order these are presented in, has become irrelevant.


Update:

I realized that using **kwargs would not solve the problem. If your programmers change function arguments as they wish, one could, for example, change the function to this:

def info(foo, **kwargs):

and the old code would break again (because now every function call has to include the first argument).

It really comes down to what Bryan says.


(...) people might be adding parameters between spacing and collapse (...)

In general, when changing functions, new arguments should always go to the end. Otherwise it breaks the code. Should be obvious.
If someone changes the function so that the code breaks, this change has to be rejected.
(As Bryan says, it is like a contract)

(...) sometimes it's not always clear as to what needs to go in.

By looking at the signature of the function (i.e def info(object, spacing=10, collapse=1) ) one should immediately see that every argument that has not a default value, is mandatory.
What the argument is for, should go into the docstring.


Old answer (kept for completeness):

This is probably not a good solution:

You can define functions this way:

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

kwargs is a dictionary that contains any keyword argument. You can check whether a mandatory argument is present and if not, raise an exception.

The downside is, that it might not be that obvious anymore, which arguments are possible, but with a proper docstring, it should be fine.


The python3 keyword-only arguments (*) can be simulated in python2.x with **kwargs

Consider the following python3 code:

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

and its behaviour:

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

This can be simulated using the following, note I've taken the liberty of switching TypeError to KeyError in the "required named argument" case, it wouldn't be too much work to make that the same exception type as well

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)

And behaviour:

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

The recipe works equally as well in python3.x, but should be avoided if you are python3.x only


You could declare your functions as receiving **args only. That would mandate keyword arguments but you'd have some extra work to make sure only valid names are passed in.

def foo(**args):
   print args

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

You could use the ** operator:

def info(**kwargs):

this way people are forced to use named parameters.


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

in python if use *args that means you can pass n-number of arguments for this parameter - which will be come a list inside function to access

and if use **kw that means its keyword arguments, that can be access as dict - you can pass n-number of kw args, and if you want to restrict that user must enter the sequence and arguments in order then don't use * and ** - (its pythonic way to provide generic solutions for big architectures...)

if you want to restrict your function with default values then you can check inside it

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

As other answers say, changing function signatures is a bad idea. Either add new parameters to the end, or fix every caller if arguments are inserted.

If you still want to do it, use a function decorator and the inspect.getargspec function. It would be used something like this:

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

Implementation of require_named_args is left as an exercise for the reader.

I would not bother. It will be slow every time the function is called, and you will get better results from writing code more carefully.


I don't get why a programmer will add a parameter in between two others in the first place.

If you want the function parameters to be used with names (e.g. info(spacing=15, object=odbchelper) ) then it shouldn't matter what order they are defined in, so you might as well put the new parameters at the end.

If you do want the order to matter then can't expect anything to work if you change it!

참고URL : https://stackoverflow.com/questions/2965271/forced-naming-of-parameters-in-python

반응형