Python函数的默认参数的设计【原创】

在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)
4523801232
>>> p = p + 1
>>> id(p)
4523801264
>>> p = 11
>>> id(p)
4523801552

>>> p = []
>>> id(p)
4527080640
>>> p.append(11)
>>> id(p)
4527080640

根本原因

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.

由此可知:

  1. 在运行代码时,运行到函数定义时,默认参数的表达式就被执行了。
  2. 函数调用时,不会再次运行默认参数的表达式。?? 这点和Ruby完全不同。
  3. 由此可知,如果默认参数,指向一个不变对象,例如L = 1。那么在函数调用时,在函数体内对L重新赋值,L其实是一个新的指针, 指向的是一个新的内存地址。而原来默认参数L本身及指向的内存地址,已经储存在最开始编译时的函数定义中。可以用__default__查看。
  4. 如果默认参数指向的是一个可变对象,如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
4353170064
1
self 4353170064
4353171088
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
4337586048
[1]
self 4337586048
4337586048
[1, 33]
self 4337586048

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

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

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

为什么Python要这么设计

StackOverflow 上争论很多。

Ruby之所以没有这个问题,我想是因为Ruby的def关键字定义了一个封闭作用域,任何数据都必须通过参数传入到方法内,才能用。

和Ruby比,Python参数的作用被大大消弱了。Python的出现晚于Ruby,其创始人肯定参考了Ruby的设计。抛弃了这个设计,选择了类似javascript的函数方式。def定义的函数,执行时是可以接收外部作用域的变量的。

有观点认为:

出于Python编译器的实现方式考虑,函数是一个内部一级对象。而参数默认值是这个对象的属性。在其他任何语言中,对象属性都是在对象创建时做绑定的。因此,函数参数默认值在编译时绑定也就不足为奇了。

本文参考了:http://cenalulu.github.io/python/default-mutable-arguments/#toc1 ,并加入了自己的理解。欢迎转载!

原文地址:https://www.cnblogs.com/chentianwei/p/11963383.html

时间: 2024-11-11 01:24:32

Python函数的默认参数的设计【原创】的相关文章

python 函数的默认参数

直接上代码: first = 1 second = 2 def haha(first=first,second=second):#默认值是上面定义的两个变量     print "first: %d"%first     print "second: %d"%second haha() first += 5 second += 6 haha() 输出结果呢? 先不急看结果,可以先假想一下,无非两种结果: 一种是haha这个函数的默认值随first和second这两个

在python函数中默认参数的一些坑

一.默认参数 python为了简化函数的调用,提供了默认参数机制: 这样在调用pow函数时,就可以省略最后一个参数不写: 在定义有默认参数的函数时,需要注意以下: 必选参数必须在前面,默认参数在后: 设置何种参数为默认参数?一般来说,将参数值变化小的设置为默认参数. python标准库实践 python内建函数: 函数签名可以看出,使用print('hello python')这样的简单调用的打印语句,实际上传入了许多默认值,默认参数使得函数的调用变得非常简单. 二.出错了的默认参数 引用一个官

经典面试题-python函数之默认参数

1.可变的默认参数----list  示例: def add(a, mylist=[]): # print(id(mylist)) mylist.append(a) return mylist print(add(5)) print(add(6)) print(add(6, ["0908"])) print(add(7)) 输出结果: 预期输出 实际输出 [5][6]['6', 9008][ 7] [5][5, 6]['0908', 6][5, 6, 7]  查看其id def add

python函数之默认参数 | 关键参数 | 全局变量 | return

默认参数: 关键参数: Tips1:字符串的引用 return 全局变量:1位置的job是参数,ll是变量,两者是不一样的, 报错的情况:

C++虚函数的默认参数问题

#include "stdafx.h" #include <iostream> #include <algorithm> using namespace std; class Base { public: Base(int i):m_num(i) { cout<<"Base Constructor"<<endl; } virtual ~Base() { cout<<"Base Deconstruc

没有躲过的坑--C++函数的默认参数(重新定义默认参数)

默认参数指的是当函数调用中省略了实参时,自动使用一个值. 这里首先需要注意的是: 对于带参数列表的函数,必须从右向左添加默认值. 也就是说,要为某个参数设置默认值,则必须为它右边的所有参数提供默认值. 今天遇到的坑儿,就是函数使用默认参数,并且函数的声明和定义是分开的. char* left(const char* str, int n=1); int main() { } char* left(const char* str, int n = 1)//错误 { } 上面代码可以有两种修改: 1

C++函数:默认参数的函数

1.默认参数的目的 C++可以给函数定义默认参数值.通常,调用函数时,要为函数的每个参数给定对应的实参.例如: void delay(int loops); //函数声明 void delay(int loops) //函数定义 { if(100ps==0) return; for(int i=0;i<loops,i++); } 无论何时调用delay()函数,都必须给loops传一个值以确定时间.但有时需要用相同的实参反复调用delay()函数.C++可以给参数定义默认值.如果将delay(

从汇编看c++函数的默认参数

在c++中,可以为函数提供默认参数,这样,在调用函数的时候,如果不提供参数,编译器将为函数提供参数的默认值.下面从汇编看其原理. 下面是c++源码: int add(int a = 1, int b = 2) {//参数a b有默认值 return a + b; } int main() { int c= add();//不提供参数 } 下面是mian函数里面的汇编码: ; 4 : int main() { push ebp mov ebp, esp push ecx;为局部变量c分配了4字节的

PHP_零基础学php_3PHP函数、传参函数、默认参数、函数返回值

<?php function say_hello() //无参数 { $name="tang"; echo "hello,".$name; echo "<br />"; echo "<hr />"; } say_hello();//函数调用 function say_helloS($some_name)//有参数 { echo "hello,".$some_name; echo