Почему я не могу изменить значение функции по умолчанию после ее определения?

#python

#питон

Вопрос:

 i = 5

def f(arg=i):
    print(arg)

i = 6
f()
 

Я изучаю Python по официальной документации. Там я нахожу приведенный выше фрагмент кода, который я не могу понять, почему печатается 5 вместо 6. Я относительно новичок в Python. Кто-нибудь может помочь мне понять эту концепцию?

Комментарии:

1. Значение по умолчанию вычисляется при определении функции, а не при ее вызове.

2. Поэтому вы должны передать i функции, чтобы получить ожидаемое поведение.

3. @Barmar Не могли бы вы объяснить это подробнее? После перехода с Java это кажется мне довольно странным.

4. @dspr В коде вызов функции выполняется после обновления значения i до 6. Тогда почему значение приходит 5 вместо 6?

5. Как объяснил Barmar , это связано с тем, что значение по умолчанию arg фиксируется при объявлении функции, а не при ее вызове: arg становится 5 в строке 3 и остается 5 даже при ее вызове, потому что функции не был передан аргумент (поэтому используется значение по умолчанию). Вместо этого перейдите I к функции, и напечатанное значение будет равно 6.

Ответ №1:

def f(arg=i) говорит: «Сделай мне функцию f , в которой значение по умолчанию arg равно тому, что i есть прямо сейчас». Во время определения функции, i=5 .

Комментарии:

1. Лучшая формулировка «сделайте мне функцию f , в которой объектом по умолчанию для arg является любой объект i , на который ссылается прямо сейчас». Изменяемые объекты по умолчанию действительно могут быть изменены, и это неожиданный побочный эффект для новых пользователей Python.

2. Это основано на том факте, что объекты являются значениями по ссылке, и, таким образом, выходит за рамки данного вопроса. Хотя это важное замечание, ссылка на i=5 объект может ввести в заблуждение.

3. Точное, не вводящее в заблуждение. Это фундаментальная проблема Python, которую следует хорошо понимать.

Ответ №2:

 i = 5
def f(arg=i)
    print(arg)
 

Вычисляется i во время определения, поэтому приведенный выше код имеет то же значение, что и приведенный ниже код:

 def f(arg=5)
    print(arg)
 

Это означает, что, когда функция вызывается без аргументов, arg будет иметь значение 5 , независимо от того, какое значение i сейчас.

Чтобы получить то, что вы хотите, просто выполните следующие действия:

 def f(arg)
    print(arg)

i = 6
f(i)
 

Ответ №3:

Потому что функция принимает значение по умолчанию при первом объявлении ‘i’ .

Измените значение i= 6 в первой строке, если вы хотите, чтобы ваш код печатал 6.

Надеюсь, я помог!

Ответ №4:

В этом разница между тем, что обрабатывается по ссылке, и значением. Когда вы определяли функцию f , вы сказали ей установить для аргумента значение по умолчанию i , это делается по значению, а не по ссылке, поэтому он взял любое значение i , которое было в то время, и установил для функции значение по умолчанию. Изменение значения i после этой точки не приводит к изменению значения arg . Если вы хотите, чтобы это работало таким образом, вы могли бы сделать это:

 i = 5
def f(arg = None):
    if (arg = None)
        arg = i
    print(arg)

i = 6
f()
 

Это позволяет вам передавать значение for arg в функцию как обычно, но если вы этого не сделаете (или явно передадите None ), оно обновляется arg до текущего значения i if arg по-прежнему None (версия Python NULL, если вы знакомы с другими языками)


Нечто подобное можно сделать с помощью or оператора, arg = arg or i ,но это проверит, является ли arg оно ложным, и при использовании целых чисел, как в вашем примере, 0 будет пойман проверкой.

Ответ №5:

То, что говорили другие, верно … значение по умолчанию вычисляется во время создания функции, но это не значит, что оно принимает «значение i» во время создания. Значение по умолчанию присваивается объекту, на который ссылается «i» во время создания. Это важный момент, потому что, если этот объект является изменяемым, значение по умолчанию может быть изменено!

Вот что происходит:

 import inspect

i = 5   # name "i" refers to an immutable Python integer object of value 5.
print(f'i = {i} (id={id(i)})')  # Note value and ID

# Create function "f" with a parameter whose default is the object
# referred to by name "i" *at this point*.
def f(arg=i):
    print(f'arg = {arg} (id={id(arg)})')

# Use the inspect module to extract the defaults from the function.
# Note the value and ID
defaults = dict(inspect.getmembers(f))['__defaults__']
print(f'defaults = {defaults} (id={id(defaults[0])})')

# name "i" now refers to a different immutable Python integer object of value 6.
i = 6
print(f'i = {i} (id={id(i)})') # Note value and ID (changed!)
f()  # default for function still referes to object 5.
f(i) # override the default with object currently referred to by name "i"
 

Выходной сигнал:

 i = 5 (id=2731452426672)               # Original object
defaults = (5,) (id=2731452426672)     # default refers to same object
i = 6 (id=2731452426704)               # Different object
arg = 5 (id=2731452426672)             # f() default is the original object
arg = 6 (id=2731452426704)             # f(i) parameter is different object
 

Теперь посмотрите результаты изменяемого значения по умолчанию:

 import inspect

i = [5]  # name "i" refers to an mutable Python list containing immutable integer object 5
print(f'i = {i} (id={id(i)})')  # Note value and ID

# Create function "f" with a parameter whose default is the object
# referred to by name "i" *at this point*.
def f(arg=i):
    print(f'arg = {arg} (id={id(arg)})')

# Use the inspect module to extract the defaults from the function.
# Note the value and ID
defaults = dict(inspect.getmembers(f))['__defaults__']
print(f'defaults = {defaults} (id={id(defaults[0])})')

# name "i" now refers to a different immutable Python integer object of value 6.
i[0] = 6  # MUTATE the content of the object "i" refers to.
print(f'i = {i} (id={id(i)})') # Note value and ID (UNCHANGED!)
f()  # default for function still refers to original list object, but content changed!
i = [7]  # Create a different list object
print(f'i = {i} (id={id(i)})') # Note value and ID (changed)
f(i)     # override the default currently refered to by name "i"
 

Вывод:

 i = [5] (id=2206901216704)            # Original object
defaults = ([5],) (id=2206901216704)  # default refers to original object
i = [6] (id=2206901216704)            # Still original object, but content changed!
arg = [6] (id=2206901216704)          # f() default refers to orginal object, but content changed!
i = [7] (id=2206901199296)            # Create a new list object
arg = [7] (id=2206901199296)          # f(i) parameter refers to new passed object.
 

Это может иметь странные побочные эффекты, если не быть хорошо понято:

 >>> def f(a,b=[]):    # mutable default
...   b.append(a)
...   return b
...
>>> x = f(1)
>>> x
[1]
>>> y = f(2)    # some would think this would return [2]
>>> y
[1, 2]
>>> x           # x changed from [1] to [1,2] as well!
[1, 2]
 

Выше, b относится к исходному объекту списка по умолчанию. Добавление к нему изменяет список по умолчанию. Возвращая его x , вы ссылаетесь на тот же объект. Список по умолчанию теперь содержит [1] so , добавляющийся во 2 — м вызове make it [1,2] . y ссылается на один и тот же объект по умолчанию x , поэтому оба имени ссылаются на одно и то же содержимое объекта.

Чтобы исправить, сделайте значение по умолчанию неизменяемым и создайте новый список, когда будет видно значение по умолчанию:

 >>> def f(a,b=None):
...   if b is None:
...     b = []
...   b.append(a)
...   return b
...
>>> f(1)
[1]
>>> f(2)
[2]
 

Ответ №6:

Это происходит потому, что вы присваиваете значение при создании функции. arg во время создания будет по умолчанию установлено то, что i есть в этот момент. Поскольку во время создания функции значение i равно 5, то именно таким становится значение по умолчанию для этого аргумента. После первоначального создания функции i в аргументе функции больше нет ссылки на i в теле.