Угловой случай с позиционными параметрами в Python 3.8?

Я возился с позиционными параметрами, как указано в PEP 570 и представлен с Python 3.8, и мне просто интересно о конкретном угловом случае.

Допустим, я определяю функцию следующим образом (независимо от того, хороший ли это дизайн или имеет ли вообще какой-то смысл):

def func(p1, p2=None, p3=None, /): 
    print(p1, p2, p3)

Итак, есть один обязательный параметр (p1), за которым следуют два необязательных параметра (p2 и p3). Я могу вызвать функцию только с p1, p1 и p2 или p1 и p2 и p3:

func(1)       # 1, None, None
func(1, 2)    # 1, 2, None
func(1, 2, 3) # 1, 2, 3

Но я никак не могу просто вызвать его с p1 и аргументом для p3, сохраняя значение по умолчанию для p2, так как я не могу предоставить аргументы ключевого слова:

func(1, p3=3)

Это, конечно, вызовет TypeError:

TypeError: func() got some positional-only arguments passed as keyword arguments: 'p3'

Я не смог найти никаких обсуждений или примеров по этому делу, так как все примеры в PEP 570 просто закрывает один необязательный параметр как часть аргументов только для позиционирования:

def name(p1, p2, /, p_or_kw, *, kw):
def name(p1, p2=None, /, p_or_kw=None, *, kw):
def name(p1, p2=None, /, *, kw):
def name(p1, p2=None, /):
def name(p1, p2, /, p_or_kw):
def name(p1, p2, /):

Итак, мой вопрос: является ли это предполагаемым поведением, чтобы вызывающая сторона предоставляла несколько необязательных аргументов слева направо, переопределяя их в принудительном порядке? Действительно ли это особенность позиционных аргументов?


person hjs    schedule 30.10.2019    source источник
comment
"Is that the intended behavior, to have a caller provide multiple optional arguments from left to right, overriding them in a forced order? Is this actually a feature of positional-only arguments?" Возможно, я вас неправильно понял, но это не только предполагаемое поведение позиционных аргументов, но и его определение.   -  person DeepSpace    schedule 30.10.2019
comment
func(1, p3=3) прямо противоречит использованию / в определении функции, поскольку он предоставляет аргумент ключевого слова для функции, которая принимает только позиционные аргументы. Тот факт, что p2 имеет значение по умолчанию, не имеет значения.   -  person DeepSpace    schedule 30.10.2019
comment
› func(1, p3=3) прямо противоречит использованию / да, это было просто для иллюстрации того, что простое переопределение одного параметра по умолчанию невозможно. Меня просто смутили несколько значений по умолчанию для позиционных параметров, но, похоже, это то поведение, которое вы действительно хотите иметь. Я просто не нашел явной ссылки на это поведение.   -  person hjs    schedule 30.10.2019
comment
С позиционными или ключевыми параметрами существует понятие, что вот некоторые значимые значения по умолчанию, вы можете переопределить их по отдельности, если хотите, тогда как с позиционными параметрами тоже есть значения по умолчанию, но это больше похоже на некоторые значения по умолчанию, вы можете переопределить их один за другим, но только в том порядке, в котором мы вам тоже говорим. Я предполагаю, что меня смутило то, что в позиционном контексте параметры по умолчанию имеют разную семантику. Но это имеет смысл, я думаю, на вопрос дан ответ.   -  person hjs    schedule 30.10.2019


Ответы (2)


Является ли это предполагаемым поведением, чтобы вызывающая сторона предоставляла несколько необязательных аргументов слева направо, переопределяя их в принудительном порядке? Действительно ли это особенность позиционных аргументов?

Мало того, что это «предполагаемое поведение» позиционных аргументов, это в значительной степени его определение.

func(1, p3=3) прямо противоречит использованию / в сигнатуре функции, поскольку он предоставляет аргумент ключевого слова для функции, которая принимает только позиционные аргументы. Тот факт, что p2 имеет значение по умолчанию, не имеет значения (хотя, как вы обнаружили, он практически бесполезен).

Я буду продолжать искать подробное объяснение в документации, но его может и не быть. По сути, это прямое следствие использования /.

Однако этот пример включен в PEP570. :

def name(positional_only_parameters, /, positional_or_keyword_parameters,
         *, keyword_only_parameters):

Что предполагает, что мы можем переписать func как:

def func(p1,p3=None, /, p2=None):
    print(p1, p2, p3)

Тогда оба из них работают:

func(1, 3)
func(1, 3, 2)

Выход

1 None 3
1 2 3

Попробуйте онлайн

person DeepSpace    schedule 30.10.2019
comment
Конечно, это также позволило бы называть его как func(1, 3, p2=2) и даже func(1, p2=2). Но тогда это другой тип подписи, так как он перемещает p2 в аргументы позиции или ключевого слова. Но вы в любом случае правы, это предполагаемое поведение. Меня просто смутила новая семантика, которую несколько необязательных аргументов имеют в позиционном контексте, но на самом деле это то, что вы хотите иметь. - person hjs; 30.10.2019
comment
В контексте позиционного или ключевого слова вызывающая сторона может передавать несколько необязательных аргументов по отдельности в любой комбинации и порядке (например, выбор вишни), тогда как в позиционном контексте параметры по умолчанию могут быть переопределены, но только один за другим, из слева направо, в порядке, установленном подписью. Это то, чего я раньше не улавливал. - person hjs; 30.10.2019

Хорошим примером точно описанного поведения, которое меня смутило, является eval Builtin, который имеет два необязательных аргумента только для позиции:

eval(source, globals=None, locals=None, /)
    Evaluate the given source in the context of globals and locals.

    The source may be a string representing a Python expression
    or a code object as returned by compile().
    The globals must be a dictionary and locals can be any mapping,
    defaulting to the current globals and locals.
    If only globals is given, locals defaults to it.

Таким образом, невозможно указать только locals:

>>> eval("1 + 2 + x", locals={'x': 3})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: eval() takes no keyword arguments

Позиционно globals должен быть указан первым (если locals опущены, по умолчанию они все равно равны globals):

>>> eval("1 + 2 + x", {'x': 3}) # globals = locals
6

... или, если они должны отличаться:

>>> eval("1 + 2 + x", {'x': 3}, {'x': 4})
7

Итак, чтобы ответить на вопрос: это именно предполагаемое поведение.

person hjs    schedule 31.10.2019