【问题标题】:Why can't I change the default value of a function after it was defined?为什么定义后不能更改函数的默认值?
【发布时间】:2021-01-29 16:59:18
【问题描述】:
i = 5

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

i = 6
f()

我正在从官方文档中学习 Python。在那里我发现上面的代码我无法理解为什么打印 5 而不是 6。我对 Python 比较陌生。有人可以帮我理解这个概念吗?

【问题讨论】:

  • 默认是在定义函数时计算,而不是在调用时计算。
  • 因此您应该将 i 传递给函数以获得您期望的行为。
  • @Barmar 你能解释一下吗?从 Java 转行后,我觉得这很奇怪。
  • @dspr 在代码中,函数调用是在 i 的值更新为 6 之后。那为什么值是 5 而不是 6?
  • 正如 Barmar 所解释的,这是因为 arg 的默认值在声明函数时是固定的,而不是在调用它时:arg 在第 3 行变为 5,即使在之所以调用它,是因为没有将参数传递给函数(因此使用默认值)。将 I 传递给函数,打印的值为 6。

标签: python


【解决方案1】:

def f(arg=i) 说“让我成为一个函数f,其中arg 的默认值是i 现在的任何值”。在定义函数时,i=5

【讨论】:

  • 更好的措辞“让我成为一个函数 f,其中 arg 的默认 objecti 现在所指的任何对象”。可变的默认对象实际上可以更改,这对 Python 新用户来说是一个令人惊讶的副作用。
  • 这是基于对象是引用值的事实,因此超出了此问题的范围。虽然这是一个重要说明,但将 i=5 称为对象会产生误导。
  • 准确,没有误导性。这是 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' 的第一个声明中采用其默认值。

    如果您希望代码打印 6,请在第一行更改为 i=6。

    希望我能帮上忙!

    【讨论】:

      【解决方案4】:

      这是按引用处理与按值处理的区别。当您定义函数f 时,您告诉它将参数的默认值设置为i,这是按值完成的,而不是按引用完成的,因此它采用当时i 的值并将默认值设置为的功能。在该点之后更改i 的值不会更改arg 的值。如果您希望它以这种方式工作,您可以这样做:

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

      这使您可以像往常一样将arg 的值传递给函数,但如果您不这样做(或者您明确传递None),它会将arg 更新为i 的当前值(如果@987654331) @ 仍然是 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],因此在第二次调用中附加为[1,2]yx 引用相同的默认对象,因此两个名称引用的对象内容相同。

        要解决此问题,请使默认值不可变并在看到默认值时创建一个新列表:

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

          【讨论】:

            猜你喜欢
            • 2020-08-28
            • 2010-09-24
            • 1970-01-01
            • 2017-08-26
            • 2014-07-18
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多