首页 > 编程知识 正文

python函数默认值的陷阱(python函数常见问题)

时间:2023-12-15 00:36:33 阅读:315847 作者:HLBY

本文目录一览:

Python中定义函数默认参数值的使用注意事项?

4.7.1. 默认参数值

最常用的一种形式是为一个或多个参数指定默认值。这会创建一个可以使用比定义是允许的参数更少的参数调用的函数,例如:

def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):

while True:

ok = input(prompt)

if ok in ('y', 'ye', 'yes'):

return True

if ok in ('n', 'no', 'nop', 'nope'):

return False

retries = retries - 1

if retries 0:

raise OSError('uncooperative user')

print(complaint)

这个函数可以通过几种不同的方式调用:

只给出必要的参数:

ask_ok('Do you really want to quit?')

给出一个可选的参数:

ask_ok('OK to overwrite the file?', 2)

或者给出所有的参数:

ask_ok('OK to overwrite the file?', 2, 'Come on, only yes or no!')

这个例子还介绍了 in 关键字。它测定序列中是否包含某个确定的值。

默认值在函数 定义 作用域被解析,如下所示:

i = 5

def f(arg=i):

print(arg)

i = 6

f()

将会输出 5。

重要警告: 默认值只被赋值一次。这使得当默认值是可变对象时会有所不同,比如列表、字典或者大多数类的实例。例如,下面的函数在后续调用过程中会累积(前面)传给它的参数:

def f(a, L=[]):

L.append(a)

return L

print(f(1))

print(f(2))

print(f(3))

这将输出:

[1]

[1, 2]

[1, 2, 3]

如果你不想让默认值在后续调用中累积,你可以像下面一样定义函数:

def f(a, L=None):

if L is None:

L = []

L.append(a)

return L

python出错,请问是什么问题

要把代码发现来才知道,以下是常见的错误

下面终于要讲到当你用到更多的Python的功能(数据类型,函数,模块,类等等)时可能碰到的问题了。由于篇幅有限,这里尽量精简,尤其是对一些高级的概念。要想了解更多的细节,敬请阅读Learning Python, 2nd Edition的“小贴士”以及“Gotchas”章节。

打开文件的调用不使用模块搜索路径

当你在Python中调用open()来访问一个外部的文件时,Python不会使用模块搜索路径来定位这个目标文件。它会使用你提供的绝对路径,或者假定这个文件是在当前工作目录中。模块搜索路径仅仅为模块加载服务的。

不同的类型对应的方法也不同

列表的方法是不能用在字符串上的,反之亦然。通常情况下,方法的调用是和数据类型有关的,但是内部函数通常在很多类型上都可以使用。举个例子来说,列表的reverse方法仅仅对列表有用,但是len函数对任何具有长度的对象都适用

不能直接改变不可变数据类型

记住你没法直接的改变一个不可变的对象(例如,元组,字符串):

T = (1, 2, 3)

T[2] = 4 # 错误

用切片,联接等构建一个新的对象,并根据需求将原来变量的值赋给它。因为Python会自动回收没有用的内存,因此这没有看起来那么浪费:

T = T[:2] + (4,) # 没问题了: T 变成了 (1, 2, 4)

使用简单的for循环而不是while或者range

当你要从左到右遍历一个有序的对象的所有元素时,用简单的for循环(例如,for x in seq:)相比于基于while-或者range-的计数循环而言会更容易写,通常运行起来也更快。除非你一定需要,尽量避免在一个for循环里使用range:让Python来替你解决标号的问题。在下面的例子中三个循环结构都没有问题,但是第一个通常来说更好;在Python里,简单至上。

S = "lumberjack"

for c in S: print c # 最简单

for i in range(len(S)): print S[i] # 太多了

i = 0 # 太多了

while i len(S): print S[i]; i += 1

不要试图从那些会改变对象的函数得到结果

诸如像方法list.append()和list.sort()一类的直接改变操作会改变一个对象,但不会将它们改变的对象返回出来(它们会返回None);正确的做法是直接调用它们而不要将结果赋值。经常会看见初学者会写诸如此类的代码:

mylist = mylist.append(X)

目的是要得到append的结果,但是事实上这样做会将None赋值给mylist,而不是改变后的列表。更加特别的一个例子是想通过用排序后的键值来遍历一个字典里的各个元素,请看下面的例子:

D = {...}

for k in D.keys().sort(): print D[k]

差一点儿就成功了——keys方法会创建一个keys的列表,然后用sort方法来将这个列表排序——但是因为sort方法会返回None,这个循环会失败,因为它实际上是要遍历None(这可不是一个序列)。要改正这段代码,将方法的调用分离出来,放在不同的语句中,如下:

Ks = D.keys()

Ks.sort()

for k in Ks: print D[k]

只有在数字类型中才存在类型转换

在Python中,一个诸如123+3.145的表达式是可以工作的——它会自动将整数型转换为浮点型,然后用浮点运算。但是下面的代码就会出错了:

S = "42"

I = 1

X = S + I # 类型错误

这同样也是有意而为的,因为这是不明确的:究竟是将字符串转换为数字(进行相加)呢,还是将数字转换为字符串(进行联接)呢?在Python中,我们认为“明确比含糊好”(即,EIBTI(Explicit is better than implicit)),因此你得手动转换类型:

X = int(S) + I # 做加法: 43

X = S + str(I) # 字符串联接: "421"

循环的数据结构会导致循环

尽管这在实际情况中很少见,但是如果一个对象的集合包含了到它自己的引用,这被称为循环对象(cyclic object)。如果在一个对象中发现一个循环,Python会输出一个[…],以避免在无限循环中卡住:

L = ['grail'] # 在 L中又引用L自身会

L.append(L) # 在对象中创造一个循环

L

['grail', [...]]

除了知道这三个点在对象中表示循环以外,这个例子也是很值得借鉴的。因为你可能无意间在你的代码中出现这样的循环的结构而导致你的代码出错。如果有必要的话,维护一个列表或者字典来表示已经访问过的对象,然后通过检查它来确认你是否碰到了循环。

赋值语句不会创建对象的副本,仅仅创建引用

这是Python的一个核心理念,有时候当行为不对时会带来错误。在下面的例子中,一个列表对象被赋给了名为L的变量,然后L又在列表M中被引用。内部改变L的话,同时也会改变M所引用的对象,因为它们俩都指向同一个对象。

L = [1, 2, 3] # 共用的列表对象

M = ['X', L, 'Y'] # 嵌入一个到L的引用

M

['X', [1, 2, 3], 'Y']

L[1] = 0 # 也改变了M

M

['X', [1, 0, 3], 'Y']

通常情况下只有在稍大一点的程序里这就显得很重要了,而且这些共用的引用通常确实是你需要的。如果不是的话,你可以明确的给他们创建一个副本来避免共用的引用;对于列表来说,你可以通过使用一个空列表的切片来创建一个顶层的副本:

L = [1, 2, 3]

M = ['X', L[:], 'Y'] # 嵌入一个L的副本

L[1] = 0 # 仅仅改变了L,但是不影响M

L

[1, 0, 3]

M

['X', [1, 2, 3], 'Y']

切片的范围起始从默认的0到被切片的序列的最大长度。如果两者都省略掉了,那么切片会抽取该序列中的所有元素,并创造一个顶层的副本(一个新的,不被公用的对象)。对于字典来说,使用字典的dict.copy()方法。

静态识别本地域的变量名

Python默认将一个函数中赋值的变量名视作是本地域的,它们存在于该函数的作用域中并且仅仅在函数运行的时候才存在。从技术上讲,Python是在编译def代码时,去静态的识别本地变量,而不是在运行时碰到赋值的时候才识别到的。如果不理解这点的话,会引起人们的误解。比如,看看下面的例子,当你在一个引用之后给一个变量赋值会怎么样:

X = 99

def func():

... print X # 这个时候还不存在

... X = 88 # 在整个def中将X视作本地变量

...

func( ) # 出错了!

你会得到一个“未定义变量名”的错误,但是其原因是很微妙的。当编译这则代码时,Python碰到给X赋值的语句时认为在这个函数中的任何地方X会被视作一个本地变量名。但是之后当真正运行这个函数时,执行print语句的时候,赋值语句还没有发生,这样Python便会报告一个“未定义变量名”的错误。

事实上,之前的这个例子想要做的事情是很模糊的:你是想要先输出那个全局的X,然后创建一个本地的X呢,还是说这是个程序的错误?如果你真的是想要输出这个全局的X,你需要将它在一个全局语句中声明它,或者通过包络模块的名字来引用它。

默认参数和可变对象

在执行def语句时,默认参数的值只被解析并保存一次,而不是每次在调用函数的时候。这通常是你想要的那样,但是因为默认值需要在每次调用时都保持同样对象,你在试图改变可变的默认值(mutable defaults)的时候可要小心了。例如,下面的函数中使用一个空的列表作为默认值,然后在之后每一次函数调用的时候改变它的值:

def saver(x=[]): # 保存一个列表对象

... x.append(1) # 并每次调用的时候

... print x # 改变它的值

...

saver([2]) # 未使用默认值

[2, 1]

saver() # 使用默认值

[1]

saver() # 每次调用都会增加!

[1, 1]

saver()

[1, 1, 1]

有的人将这个视作Python的一个特点——因为可变的默认参数在每次函数调用时保持了它们的状态,它们能提供像C语言中静态本地函数变量的类似的一些功能。但是,当你第一次碰到它时会觉得这很奇怪,并且在Python中有更加简单的办法来在不同的调用之间保存状态(比如说类)。

要摆脱这样的行为,在函数开始的地方用切片或者方法来创建默认参数的副本,或者将默认值的表达式移到函数里面;只要每次函数调用时这些值在函数里,就会每次都得到一个新的对象:

def saver(x=None):

... if x is None: x = [] # 没有传入参数?

... x.append(1) # 改变新的列表

... print x

...

saver([2]) # 没有使用默认值

[2, 1]

saver() # 这次不会变了

[1]

saver()

[1]

其他常见的编程陷阱

下面列举了其他的一些在这里没法详述的陷阱:

在顶层文件中语句的顺序是有讲究的:因为运行或者加载一个文件会从上到下运行它的语句,所以请确保将你未嵌套的函数调用或者类的调用放在函数或者类的定义之后。

reload不影响用from加载的名字:reload最好和import语句一起使用。如果你使用from语句,记得在reload之后重新运行一遍from,否则你仍然使用之前老的名字。

在多重继承中混合的顺序是有讲究的:这是因为对superclass的搜索是从左到右的,在类定义的头部,在多重superclass中如果出现重复的名字,则以最左边的类名为准。

在try语句中空的except子句可能会比你预想的捕捉到更多的错误。在try语句中空的except子句表示捕捉所有的错误,即便是真正的程序错误,和sys.exit()调用,也会被捕捉到。

Python面试基础题十大陷阱,你中招了吗

我们在会碰到各种各样的面试,有的甚至是HR专门为你设置的障碍,在python面试中也是,无论你是应聘Python web开发,爬虫工程师,或是数据分析,还是自动化运维,这些python面试基础题十大陷阱也许你会遇到,今天的python培训总结出来给你以防万一:

问题1:请问如何修改以下Python代码,使得下面的代码调用类A的show方法?

class A(object)

def show(self):

print 'derived show'

class B(A)

def show(self):

print 'derived show'

obj=B()

obj.show()

答:这道题的考点是类继承,只要通过__class__ 方法指定类对象就可以了。补充的代码如下:

obj._class_=A

obj.show()

问题2:请问如何修改以下Python代码,使得代码能够运行?

class A(object):

def _init_ (self,a,b):

self._a = a

self._b = b

def myprint(self):

print 'a=',self._a,'b=',self._b

a1=A(10,20)

a1.myprint()

a1=(80)

答:此题考察得是方法对象,为了能让对象实例能被直接调用,需要实现 __call__ 方法,补充代码如下:

class A(object):

def _init_ (self,a,b):

self._a = a

self._b = b

def myprint(self):

print 'a=',self._a,'b=',self._b

def_call_(self,num):

print'call:',num+self._a

问题3:下面这段代码的输出是什么?

class B(object):

def fn(self):

print"B fn"

def_init_(self):

print"B INIT"

class A(object):

def fn(self):

print"A fn"

def_new_(cls,a):

print"NEW",a

if a10:

return super(A,cls)._new_(cls)

return B()

def_init_(self,a):

print "INIT",a

a1=A(5)

a1,fn()

a2=A(20)

a2,fn()

答:

NEW 5

B INIT

B fn

NEW 20

INIT 20

A fn

此题考察的是new和init的用法,使用 __new__ 方法,可以决定返回那个对象,也就是创建对象之前调用的,这个常见于于设计模式的单例、工厂模式。__init__ 是创建对象是调用的。

问题4:下面这段代码输出什么?

1s=[1,2,3,4]

list1 =[i for i in ls if i2

print list1

list2 =[1*2 for i in ls if 12

print list2

dicl={x: x**2 for x in(2, 4, 6)}

print dic1

dic2={x: ' item'+ str(x**2)for x in (2, 4, 6)}

print dic2

setl ={x for x in 'hello world' if x not in 'low level'}

print set1

答:

[3,4]

[6,8]

{2:4,4:16,6:36}

{2:'item4',4:'item16’,6:'item36"}set(["h",'r','d"])

此题考察的是列表和字典的生成。

问题5:下面这段代码输出什么?

num= 9

def f1():

um=20

def f2():

print num

f2()

f1()

f2()

答:

9

9

此题考察全局变量和局部变量。num 不是个全局变量,所以每个函数都得到了自己的 num 拷贝,如果你想修改 num ,则必须用 global 关键字声明。比如下面这样

num=9

def f1():

global num

um=20

def f2():

print num

f2()

f1()

f2()

#prints:

#9

#20

问题6:如何使用一行代码交换两个变量值?

a=8

b=9

答:

(a,b)=(b,a)

问题7:如何添加代码,使得没有定义的方法都调用mydefault方法?

class A(object):

def_init_(self,a,b):

self.a1=a

self.b1=b

print'init'

def mydefault(self):

print'default'

a1=A(10,20)

a1.fn1()

a1.fn2()

a1.fn3()

答:

class A(object):

def_init_(self,a,b):

self.a1=a

self.b1=b

print'init'

def mydefault(self):

print'default'

def_getattr_(self,name):

return self.mydefault

a1=A(10,20)

a1.fn1()

a1.fn2()

a1.fn3()

此题的考的是Python的默认方法, 只有当没有定义的方法调用时,才会调用方法 __getattr__。当 fn1 方法传入参数时,我们可以给 mydefault 方法增加一个 *args 不定参数来兼容。

class A(object):

def_init_(self,a,b):

self.a1=a

self.b1=b

print'init'

def mydefault(self,*args):

print'default:'+str(args[0])

def_getattr_(self,name):

print"other fn:",name

return self.mydefault

a1=A(10,20)

a1.fn1(33)

a1.fn2('hello')

a1.fn3(10)

问题8:一个包里有三个模块,mod1.py , mod2.py , mod3.py ,但使用 from demopack import * 导入模块时,如何保证只有 mod1 、 mod3 被导入了。

答:在包中增加 __init__.py 文件,并在文件中增加:

_all_=['mod1','mod3']

问题9:写一个函数,接收整数参数 n ,返回一个函数,函数返回n和参数的积。

答:

def mulby(num):

def gn(val):

return num*val

return gn

zw=mulby(7)

print(zw(9));

问题10:请问下面的代码有什么隐患?(Python2中)

def strtest1(num):

str='first'

for i in range(num):

str+="X"

return str

答:由于变量str是个不可变对象,每次迭代,python都会生成新的str对象来存储新的字符串,num越大,创建的str对象越多,内存消耗越大。

python的简单问题?

要把代码发现来才知道,以下是常见的错误下面终于要讲到当你用到更多的Python的功能(数据类型,函数,模块,类等等)时可能碰到的问题了。由于篇幅有限,这里尽量精简,尤其是对一些高级的概念。要想了解更多的细节,敬请阅读Learning Python, 2nd Edition的逗小贴士地以及逗Gotchas地章节。 打开文件的调用不使用模块搜索路径当你在Python中调用open()来访问一个外部的文件时,Python不会使用模块搜索路径来定位这个目标文件。它会使用你提供的绝对路径,或者假定这个文件是在当前工作目录中。模块搜索路径仅仅为模块加载服务的。不同的类型对应的方法也不同列表的方法是不能用在字符串上的,反之亦然。通常情况下,方法的调用是和数据类型有关的,但是内部函数通常在很多类型上都可以使用。举个例子来说,列表的reverse方法仅仅对列表有用,但是len函数对任何具有长度的对象都适用不能直接改变不可变数据类型记住你没法直接的改变一个不可变的对象(例如,元组,字符串): T = (1, 2, 3) T[2] = 4 # 错误 用切片,联接等构建一个新的对象,并根据需求将原来变量的值赋给它。因为Python会自动回收没有用的内存,因此这没有看起来那么浪费: T = T[:2] + (4,) # 没问题了: T 变成了 (1, 2, 4) 使用简单的for循环而不是while或者range 当你要从左到右遍历一个有序的对象的所有元素时,用简单的for循环(例如,for x in seq:)相比于基于while-或者range-的计数循环而言会更容易写,通常运行起来也更快。除非你一定需要,尽量避免在一个for循环里使用range:让Python来替你解决标号的问题。在下面的例子中三个循环结构都没有问题,但是第一个通常来说更好;在Python里,简单至上。 S = "lumberjack" for c in S: print c # 最简单 for i in range(len(S)): print S[i] # 太多了 i = 0 # 太多了 while i len(S): print S[i]; i += 1 不要试图从那些会改变对象的函数得到结果诸如像方法list.append()和list.sort()一类的直接改变操作会改变一个对象,但不会将它们改变的对象返回出来(它们会返回None);正确的做法是直接调用它们而不要将结果赋值。经常会看见初学者会写诸如此类的代码: mylist = mylist.append(X) 目的是要得到append的结果,但是事实上这样做会将None赋值给mylist,而不是改变后的列表。更加特别的一个例子是想通过用排序后的键值来遍历一个字典里的各个元素,请看下面的例子: D = {...} for k in D.keys().sort(): print D[k] 差一点儿就成功了——keys方法会创建一个keys的列表,然后用sort方法来将这个列表排序——但是因为sort方法会返回None,这个循环会失败,因为它实际上是要遍历None(这可不是一个序列)。要改正这段代码,将方法的调用分离出来,放在不同的语句中,如下: Ks = D.keys() Ks.sort() for k in Ks: print D[k] 只有在数字类型中才存在类型转换在Python中,一个诸如123+3.145的表达式是可以工作的——它会自动将整数型转换为浮点型,然后用浮点运算。但是下面的代码就会出错了: S = "42" I = 1 X = S + I # 类型错误 这同样也是有意而为的,因为这是不明确的:究竟是将字符串转换为数字(进行相加)呢,还是将数字转换为字符串(进行联接)呢看在Python中,我们认为逗明确比含糊好地(即,EIBTI(Explicit is better than implicit)),因此你得手动转换类型: X = int(S) + I # 做加法: 43 X = S + str(I) # 字符串联接: "421" 循环的数据结构会导致循环尽管这在实际情况中很少见,但是如果一个对象的集合包含了到它自己的引用,这被称为循环对象(cyclic object)。如果在一个对象中发现一个循环,Python会输出一个[…],以避免在无限循环中卡住: L = ['grail'] # 在 L中又引用L自身会 L.append(L) # 在对象中创造一个循环 L ['grail', [...]] 除了知道这三个点在对象中表示循环以外,这个例子也是很值得借鉴的。因为你可能无意间在你的代码中出现这样的循环的结构而导致你的代码出错。如果有必要的话,维护一个列表或者字典来表示已经访问过的对象,然后通过检查它来确认你是否碰到了循环。赋值语句不会创建对象的副本,仅仅创建引用这是Python的一个核心理念,有时候当行为不对时会带来错误。在下面的例子中,一个列表对象被赋给了名为L的变量,然后L又在列表M中被引用。内部改变L的话,同时也会改变M所引用的对象,因为它们俩都指向同一个对象。 L = [1, 2, 3] # 共用的列表对象 M = ['X', L, 'Y'] # 嵌入一个到L的引用 M ['X', [1, 2, 3], 'Y'] L[1] = 0 # 也改变了M M ['X', [1, 0, 3], 'Y'] 通常情况下只有在稍大一点的程序里这就显得很重要了,而且这些共用的引用通常确实是你需要的。如果不是的话,你可以明确的给他们创建一个副本来避免共用的引用;对于列表来说,你可以通过使用一个空列表的切片来创建一个顶层的副本: L = [1, 2, 3] M = ['X', L[:], 'Y'] # 嵌入一个L的副本 L[1] = 0 # 仅仅改变了L,但是不影响M L [1, 0, 3] M ['X', [1, 2, 3], 'Y'] 切片的范围起始从默认的0到被切片的序列的最大长度。如果两者都省略掉了,那么切片会抽取该序列中的所有元素,并创造一个顶层的副本(一个新的,不被公用的对象)。对于字典来说,使用字典的dict.copy()方法。静态识别本地域的变量名 Python默认将一个函数中赋值的变量名视作是本地域的,它们存在于该函数的作用域中并且仅仅在函数运行的时候才存在。从技术上讲,Python是在编译def代码时,去静态的识别本地变量,而不是在运行时碰到赋值的时候才识别到的。如果不理解这点的话,会引起人们的误解。比如,看看下面的例子,当你在一个引用之后给一个变量赋值会怎么样: X = 99 def func(): ... print X # 这个时候还不存在 ... X = 88 # 在整个def中将X视作本地变量 ... func( ) # 出错了! 你会得到一个逗未定义变量名地的错误,但是其原因是很微妙的。当编译这则代码时,Python碰到给X赋值的语句时认为在这个函数中的任何地方X会被视作一个本地变量名。但是之后当真正运行这个函数时,执行print语句的时候,赋值语句还没有发生,这样Python便会报告一个逗未定义变量名地的错误。事实上,之前的这个例子想要做的事情是很模糊的:你是想要先输出那个全局的X,然后创建一个本地的X呢,还是说这是个程序的错误看如果你真的是想要输出这个全局的X,你需要将它在一个全局语句中声明它,或者通过包络模块的名字来引用它。默认参数和可变对象在执行def语句时,默认参数的值只被解析并保存一次,而不是每次在调用函数的时候。这通常是你想要的那样,但是因为默认值需要在每次调用时都保持同样对象,你在试图改变可变的默认值(mutable defaults)的时候可要小心了。例如,下面的函数中使用一个空的列表作为默认值,然后在之后每一次函数调用的时候改变它的值: def saver(x=[]): # 保存一个列表对象 ... x.append(1) # 并每次调用的时候 ... print x # 改变它的值 ... saver([2]) # 未使用默认值 [2, 1] saver() # 使用默认值 [1] saver() # 每次调用都会增加! [1, 1] saver() [1, 1, 1] 有的人将这个视作Python的一个特点——因为可变的默认参数在每次函数调用时保持了它们的状态,它们能提供像C语言中静态本地函数变量的类似的一些功能。但是,当你第一次碰到它时会觉得这很奇怪,并且在Python中有更加简单的办法来在不同的调用之间保存状态(比如说类)。要摆脱这样的行为,在函数开始的地方用切片或者方法来创建默认参数的副本,或者将默认值的表达式移到函数里面;只要每次函数调用时这些值在函数里,就会每次都得到一个新的对象: def saver(x=None): ... if x is None: x = [] # 没有传入参数看 ... x.append(1) # 改变新的列表 ... print x ... saver([2]) # 没有使用默认值 [2, 1] saver() # 这次不会变了 [1] saver() [1] 其他常见的编程陷阱下面列举了其他的一些在这里没法详述的陷阱:在顶层文件中语句的顺序是有讲究的:因为运行或者加载一个文件会从上到下运行它的语句,所以请确保将你未嵌套的函数调用或者类的调用放在函数或者类的定义之后。 reload不影响用from加载的名字:reload最好和import语句一起使用。如果你使用from语句,记得在reload之后重新运行一遍from,否则你仍然使用之前老的名字。在多重继承中混合的顺序是有讲究的:这是因为对superclass的搜索是从左到右的,在类定义的头部,在多重superclass中如果出现重复的名字,则以最左边的类名为准。在try语句中空的except子句可能会比你预想的捕捉到更多的错误。在try语句中空的except子句表示捕捉所有的错误,即便是真正的程序错误,和sys.exit()调用,也会被捕捉到。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。