Примусове іменування параметрів у Python


111

У Python у вас може бути визначення функції:

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

який можна назвати будь-яким із наступних способів:

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

завдяки тому, що Python дозволяє аргументи будь-якого порядку, поки вони названі.

Проблема у нас виникають як деякі з наших великих функцій рости, люди можуть додавати параметри між spacingі collapse, маючи в увазі , що неправильні значення можуть йти до параметрів, які не названі. Крім того, іноді не завжди зрозуміло, для чого потрібно зайти. Ми шукаємо спосіб змусити людей називати певні параметри - не просто стандарт кодування, але в ідеалі прапор або плагін pydev?

так що у вищенаведених 4 прикладах лише останній пройшов би перевірку, як названі всі параметри.

Шанси на це, ми увімкнемо його лише для певних функцій, але будь-які пропозиції щодо того, як це реалізувати - або, якщо це можливо, були б вдячні.

Відповіді:


214

У Python 3 - Так, ви можете вказати *у списку аргументів.

З документів :

Параметри після "*" або "* ідентифікатор" - це лише параметри ключових слів і можуть передаватися лише використовуваним аргументам ключових слів.

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

32

Ви можете змусити людей використовувати аргументи ключових слів у Python3, визначивши функцію наступним чином.

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

Зробивши перший аргумент позиційним аргументом без імені, ви змушуєте всіх, хто викликає функцію, використовувати аргументи ключових слів, про що я думаю, ви запитували. У Python2 єдиний спосіб зробити це - визначити функцію на зразок цієї

def foo(**kwargs):
    pass

Це змусить абонента використовувати kwargs, але це не таке велике рішення, як тоді вам доведеться поставити чек, щоб прийняти лише той аргумент, який вам потрібен.


11

Правда, більшість мов програмування робить замовлення параметрів частиною контракту на виклик функції, але це не обов'язково . Навіщо це? Моє розуміння питання полягає в тому, якщо Python в цьому відношенні відрізняється від інших мов програмування. Окрім інших хороших відповідей на 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)

Конвенція про використання приватних елементів, що належать до інших модулів, є дуже базовою в 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отримано та не використовується знання про спеціальний дозорний об'єкт ).

Плюси:

  • Параметри є явними за кількістю та значенням (пізніше, звичайно, також вибираються хороші імена).
  • Якщо дозорний вказаний як перший параметр, то всі аргументи потрібно вказувати по імені.
  • При виклику функції можливо перейти в позиційний режим за допомогою об'єкта дозорного __named_only_start у відповідному положенні.
  • Можна очікувати кращої ефективності, ніж інші альтернативи.

Мінуси:

  • Перевірка відбувається під час виконання, а не під час компіляції.
  • Використання додаткового параметра (хоча не аргумент) та додаткової перевірки. Мала деградація продуктивності відносно регулярних функцій.
  • Функціональність - це злом без прямої підтримки з боку мови (див. Примітку нижче).
  • При виклику функції можливо перейти в позиційний режим, використовуючи дозорний об'єкт __named_only_startу потрібному положенні. Так, це також можна сприймати як профі.

Зауважте, що ця відповідь справедлива лише для Python 2. Python 3 реалізує аналогічний, але дуже елегантний, підтримуваний мовою механізм, описаний в інших відповідях.

Я виявив, що коли я відкриваю свою думку і замислююся над цим, жодне питання чи рішення інших людей не здається дурним, німим або просто дурним. Зовсім навпаки: я зазвичай багато чого вчу.


"Перевірка відбувається під час виконання, а не час компіляції." - Я думаю, що це справедливо для всіх перевірок аргументів функції. Поки ви фактично не виконаєте рядок виклику функції, ви не завжди знаєте, яка функція виконується. Також +1 - це розумно.
Ерік

@Eric: Просто я вважав за краще статичну перевірку. Але ви праві: це взагалі не був би Python. Хоча це не вирішальне значення, конструкція Python 3 також "динамічно" перевіряється. Дякуємо за Ваш коментар
Маріо Россі

Крім того, якщо ви назвали змінну модуля _named_only_start, неможливо посилатися на нього із зовнішнього модуля, який виймає профі та конфігурацію. (Одні окремі підкреслення в області модуля приватні, IIRC)
Ерік

Що стосується іменування дозорного, ми можемо також мати як a, так __named_only_startі a named_only_start(без початкового підкреслення), друге вказує на те, що названий режим "рекомендується", але не до рівня "активного просування" (як один є загальнодоступним і іншого немає). Що стосується "приватності", _namesпочинаючи з підкреслення, вона не дуже сильно закріплена за мовою: її можна легко обійти, використовуючи специфічний (не *) імпорт або кваліфіковані назви. Ось чому кілька документів Python вважають за краще використовувати термін "непублічний", а не "приватний".
Маріо Россі

6

Це можна зробити так, як це працює і в Python 2, і в Python 3 , зробивши "фальшивий" перший аргумент ключового слова зі значенням за замовчуванням, яке не відбудеться "природно". Цей аргумент ключового слова може передувати одному або більше аргументів без значення:

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

Це дозволить:

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

але ні:

info(odbchelper, 12)                

Якщо змінити функцію на:

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

то всі аргументи повинні мати ключові слова та info(odbchelper) більше не працюватимуть.

Це дозволить розмістити додаткові аргументи ключових слів у будь-якому місці після _kw , не змушуючи їх ставити їх після останнього запису. Це часто має сенс, наприклад, групування речей логічно чи впорядкування ключових слів за алфавітом може допомогти у підтримці та розвитку.

Тому немає необхідності повертатися до використання def(**kwargs)та втрати інформації про підписи у вашому розумному редакторі. Ваш соціальний договір полягає в наданні певної інформації, змушуючи (деякі з них) вимагати ключові слова;


2

Оновлення:

Я зрозумів, що використання **kwargsне вирішить проблему. Якщо ваші програмісти змінюють аргументи функцій за своїм бажанням, можна, наприклад, змінити функцію на це:

def info(foo, **kwargs):

і старий код знову зламається (тому що тепер кожен виклик функції повинен включати перший аргумент).

Це дійсно зводиться до того, що говорить Брайан.


(...) люди можуть додавати параметри між spacingта collapse(...)

Загалом, при зміні функцій нові аргументи завжди повинні йти до кінця. Інакше він порушує код. Повинно бути очевидним.
Якщо хтось змінить функцію, щоб код зламався, цю зміну потрібно відхилити.
(Як каже Брайан, це як контракт)

(...) іноді не завжди зрозуміло, для чого потрібно зайти.

Дивлячись на підпис функції (тобто def info(object, spacing=10, collapse=1)), слід негайно побачити, що кожен аргумент, який не має значення за замовчуванням, є обов'язковим.
Що стосується аргументу, має зайти в docstring.


Стара відповідь (зберігається для повноти) :

Це, мабуть, не вдале рішення:

Ви можете визначити функції таким чином:

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це словник, який містить будь-який аргумент ключового слова. Ви можете перевірити, чи є обов’язковий аргумент, а якщо ні, - виняток.

Мінус полягає в тому, що це може бути вже не очевидним, які аргументи можливі, але з належним докстрингом це повинно бути добре.


3
Мені більше сподобалась ваша стара відповідь. Просто поставте коментар, чому ви приймаєте лише ** kwargs у функції. Зрештою, кожен може змінити що-небудь у вихідному коді - вам потрібна документація для опису наміру та мети, що стоїть за вашими рішеннями.
Брендон

В цій відповіді немає реальної відповіді!
Філ

2

Аргументи, що стосуються лише ключового слова python3 ( *), можна імітувати в python2.x за допомогою**kwargs

Розглянемо наступний код python3:

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

та її поведінка:

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

Це може бути змодельоване з допомогою наступного, зауважте , я взяв на себе сміливість переходу TypeErrorдо KeyErrorв разі «потрібно названий аргумент», це не було б занадто багато роботи , щоб зробити це ж типу винятків , а також

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)

І поведінка:

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

Рецепт працює однаково добре в python3.x, але його слід уникати, якщо ви використовуєте лише python3.x


Так kwargs.pop('foo'), це ідіома Python 2? Мені потрібно оновити стиль кодування. Я все ще використовував такий підхід у Python 3 🤔
Ніл

0

Ви можете оголосити свої функції **argsлише як отримані . Це вимагатиме аргументів ключових слів, але у вас є додаткова робота над тим, щоб переконатися, що передаються лише дійсні імена.

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
Вам не тільки потрібно додати перевірку ключових слів, але подумайте про споживача, який знає, що повинен викликати метод із підписом foo(**kwargs). Що я переходжу до цього? foo(killme=True, when="rightnowplease")
Dagrooms

Це дійсно залежить. Розглянемо dict.
Нуфал Ібрагім

0

Як говорять інші відповіді, зміна підписів функції - погана ідея. Або додайте нові параметри до кінця або виправте кожного абонента, якщо вставлені аргументи.

Якщо ви все ще хочете це зробити, використовуйте функцію декоратора функцій та функції inspect.getargspec . Було б використано щось подібне:

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

Реалізація require_named_argsзалишається вправою для читача.

Я б не турбував. Він буде повільним щоразу, коли функція викликається, і ви отримаєте кращі результати від написання коду більш ретельно.


-1

Ви можете скористатися **оператором:

def info(**kwargs):

таким чином люди змушені використовувати названі параметри.


2
І поняття не маю, як викликати свій метод, не читаючи код, збільшуючи когнітивне навантаження на споживача :(
Dagrooms

Через вказану причину це дійсно погана практика, і цього слід уникати.
Девід С.

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

у python, якщо використовується * args, це означає, що ви можете передавати n-кількість аргументів для цього параметра - який буде наданий список всередині функції для доступу

а якщо використовувати ** kw, що означає його аргументи ключових слів, то це може бути доступ як dict - ви можете передати n-число kw аргументів, а якщо ви хочете обмежити цього користувача, потрібно ввести послідовність і аргументи, щоб не використовувати * і ** - (пітонічний спосіб надання загальних рішень для великих архітектур ...)

якщо ви хочете обмежити свою функцію значеннями за замовчуванням, ви можете перевірити її всередині

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

що станеться, якщо інтервал бажано дорівнювати 0? (відповідай, отримуєш 10). Ця відповідь є такою ж помилковою, як і всі інші відповіді ** kwargs з тих же причин.
Філ

-2

Я не розумію, чому програміст в першу чергу додасть параметр між двома іншими.

Якщо ви хочете, щоб параметри функції використовувались з іменами (наприклад, info(spacing=15, object=odbchelper) ), тоді не має значення, в якому порядку вони визначені, тому ви можете також поставити нові параметри в кінці.

Якщо ви хочете, щоб замовлення було важливим, тоді ви не можете очікувати, що щось спрацює, якщо ви його зміните!


2
Це не відповідає на запитання. Незалежно від того, чи це гарна ідея, це не має значення - хтось може це зробити.
Graeme Perrow

1
Як згадував Грейм, хтось все одно це зробить. Крім того, якщо ви пишете бібліотеку, яку повинні використовувати інші, примушуючи (лише python 3) передача аргументів лише для ключових слів забезпечує додаткову гнучкість, коли вам доведеться переробляти API.
s0undt3ch
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.