Python-когда вы можете передать позиционный аргумент по имени, а когда нет?

Питон 2.7.5 collections.defaultdict только кажется, что работает, когда вы передаете default_factory как позиционный аргумент-он ломается, когда вы передаете его как именованный параметр.

если вы запустите следующий код, вы увидите, что default_dict_success() работает нормально, но default_dict_failure() бросает!--5-->.

from collections import defaultdict

test_data = [
    ('clay', 'happy'),
    ('jason', 'happy'),
    ('aj', 'sad'),
    ('eric', 'happy'),
    ('sophie', 'sad')
]

def default_dict_success():
    results = defaultdict(list)
    for person, mood in test_data:
        results[mood].append(person)
    print results


def default_dict_failure():
    results = defaultdict(default_factory=list)
    for person, mood in test_data:
        results[mood].append(person)
    print results


default_dict_success()
default_dict_failure()

выход

# First function succeeds
defaultdict(<type 'list'>, {'sad': ['aj', 'sophie'], 'happy': ['clay', 'jason', 'eric']})

# Second function fails
Traceback (most recent call last):
  File "test_default_dict.py", line 26, in <module>
    default_dict_failure()
  File "test_default_dict.py", line 21, in default_dict_failure
    results[mood].append(person)
KeyError: 'happy'

кто-нибудь знает, что происходит?

редактировать: Первоначально я думал, что смотрю на какой-то источник Python, который предложил бы то, что я пытался сделать, было возможно, но комментаторы указали, что я ошибся, так как этот объект реализован в C и поэтому для него нет источника Python. Так это не совсем так таинственно, как я и думал.

это было сказано, это первый раз, когда я столкнулся с позиционным аргументом в Python, который также не мог быть передан по имени. А такая вещь бывает нигде? Есть ли способ реализовать функцию в чистом Python (как в отличие от расширения C), которое обеспечивает этот тип поведения?

2 ответов


Я думаю документы попробуйте сказать, что это то, что произойдет, хотя они не особенно ясно:

первый аргумент предоставляет начальное значение для default_factory атрибут; по умолчанию он равен None. все остальные аргументы обрабатываются так же, как если бы они были переданы конструктору dict,включая аргументы ключевого слова.

выделено мной. "Первый аргумент" не будет аргумент ключевого слова (у них нет порядка). Тем не менее, подача ошибки документации не была бы плохой идеей.

это было сказано, это первый раз, когда я столкнулся с позиционным аргументом в Python, который также не мог быть передан по имени. А такая вещь бывает нигде? Есть ли способ реализовать функцию в чистом Python (в отличие от расширения C), которая обеспечивает этот тип поведения?

это на самом деле так часто есть весь Пеп об этом. Считать range в качестве простого примера.

что касается того, чтобы сделать это самостоятельно,

функции, реализованные в современном Python, могут принимать произвольное количество позиционных аргументов только через параметр variadic *args. Однако нет синтаксиса Python, чтобы указать принятие определенного количества только позиционных параметров. Иными словами, есть много встроенные функции, подписи которых просто не expressable с Синтаксис Python.

можно сделать что-то вроде

def foo(*args):
    a, b, c = args

это упоминается в ОПТОСОЗ:

очевидно, что можно имитировать любой из них в чистом коде Python, принимая (*args, **kwargs) и разбор аргументов вручную. Но это приводит к разъединению между сигнатурой функции Python и тем, что она фактически принимает, не говоря уже о работе по реализации указанного разбора аргументов.


в модулях / _collectionsmodule.c, defdict_init () принимает кварги, но ничего не делает с ним, кроме передачи его в PyDict_Type.tp_init ().

IOW, defaultdict документируется как принятие именованного аргумента, но реализация этого не делает, поэтому именованный аргумент передается вместо использования.

Это, вероятно, можно исправить с помощью PyArg_ParseTupleAndKeywords вместо того, чтобы рассматривать его аргументы как простой кортеж. Тип deque, в тот же модуль является примером того, как это можно сделать, поскольку он принимает пару именованных аргументов.

Я предполагаю, что если вы подадите ошибку в трекере проблем Python, либо документ будет изменен в соответствии с реализацией, либо реализация будет изменена в соответствии с doc.

Supporting detail - при создании defaultdict с именованным аргументом default_factory вы получаете словарь, предварительно созданный с помощью default_factory в качестве ключ:

>>> import collections
>>> dd = collections.defaultdict(default_factory=int)
>>> dd
defaultdict(None, {'default_factory': <class 'int'>})
>>> dd2 = collections.defaultdict(int)
>>> dd2
defaultdict(<class 'int'>, {})
>>>

HTH