当前位置 主页 > 服务器问题 > Linux/apache问题 >

    Python函数的默认参数设计示例详解

    栏目:Linux/apache问题 时间:2019-12-01 10:43

    在Python教程里,针对默认参数,给了一个“重要警告”的例子:

    def f(a, L=[]):
      L.append(a)
      return L
    
    print(f(1))
    print(f(2))
    print(f(3))

    默认值只会执行一次,也没说原因。会打印出结果:

    [1]
    [1, 2]
    [1, 2, 3]

    因为学的第一门语言是Ruby,所以感觉有些奇怪。 但肯定的是方法f一定储存了变量L。

    准备知识:指针

    p指向不可变对象,比如数字。则相当于p指针指向了不同的内存地址。

    p指向的是可变对象,比如list。list自身的改变,并不会改变list对象自身所在的内存地址。所以p指向的内存地址不变。

    >>> p = 1
    >>> id(p)
    >>> p = p + 1
    >>> id(p)
    >>> p = 11
    >>> id(p)
    
    >>> p = []
    >>> id(p)
    >>> p.append(11)
    >>> id(p)

    根本原因

    Python函数的参数默认值,是在编译阶段就绑定了。(写代码时就定义了。)

    下面是一段从Python Common Gotchas中摘录的原因解释:

    Python's default arguments are evaluated once when the function is defined, not each time the function is called (like it is in say, Ruby). This means that if you use a mutable default argument and mutate it, you will and have mutated that object for all future calls to the function as well.

    由此可知:

    在运行代码时,运行到函数定义时,默认参数的表达式就被执行了。 函数调用时,不会再次运行默认参数的表达式。⚠️ 这点和Ruby完全不同。 由此可知,如果默认参数,指向一个不变对象,例如L = 1。那么在函数调用时,在函数体内对L重新赋值,L其实是一个新的指针, 指向的是一个新的内存地址。而原来默认参数L本身及指向的内存地址,已经储存在最开始编译时的函数定义中。可以用__default__查看。 如果默认参数指向的是一个可变对象,如list, 那么L.append(a)是对可变对象自身的修改,L指向的内存地址不变。所以每次调用函数,默认参数取出的都是这个内存地址的对象。

    第三条,修改上面的例子:

    def f(a, L = 1):
      L = a
      print(id(L))
      return L
    
    print("self",id(f.__defaults__[0]))
    print(f(1))
    print("self",id(f.__defaults__[0]))
    print(f(33))
    print("self",id(f.__defaults__[0]))
    
    #运行结果:
    self 4353170064
    1
    self 4353170064
    33
    self 4353170064

    默认参数L,在编译阶段就绑定了,储存在__default__内。函数体内的L = a表达式,生成的是新的变量。返回的L是新的变量,和默认参数无关。

    第四条,还是上面的例子, 改一下默认参数的类型为可变对象list:

    def f(a, L = []):
      L.append(a)
      print(id(L))
      return L
    # L = f(1)
    print("self",id(f.__defaults__[0]))
    print(f(1))
    print("self",id(f.__defaults__[0]))
    print(f(33))
    print("self",id(f.__defaults__[0]))
    #返回结果
    self 4337586048
    [1]
    self 4337586048
    [1, 33]
    self 4337586048

    由id号可知,返回的是默认参数自身。

    如何避免这个陷阱带来不必要麻烦

    def f(a, L = None):
      if L is None:
        L = []
      L.append(a)
      return L

    为什么Python要这么设计

     StackOverflow 上争论很多。