文档章节

Python中的一些“坑”

AllenOR灵感
 AllenOR灵感
发布于 2017/09/10 01:27
字数 1982
阅读 0
收藏 0

1. 不要使用可变对象作为函数默认值

先来看个例子:

def append_to_list(value, def_list = []):
  def_list.append(value)
  return def_list

my_list = append_to_list(1)
# my_list = [1]

my_other_list = append_to_list(2)
# my_other_list = [1, 2]
# 注意,其实我们只想生成一个 [2] 列表,但是却把第一次的结果带进来了,生成了一个 [1, 2] 列表。

import time
def report_arg(my_default = time.time()):
  print my_default

report_arg()
# 1474782900.9

time.sleep(5)

report_arg()
# 1474782900.9
# 两次执行,时间隔了5秒,但是输出时间都没有改变。

这些例子说明了什么?字典,集合,列表等等对象是不适合作为函数默认值的。因为这个默认值是在函数建立的时候已经生成了,每次调用都是使用了这个对象的“缓存”。

可以这样修改代码,如下:

def append_to_list(element, to = None):
  if to is None:
    to = []
  to.append(element)
  return to

2. 生成器不保留迭代过后的结果

代码如下:

gen = ( i  for i in range(10))

2 in gen
# True

5 in gen
# True

1 in gen
# False
# 为什么 1 不在 gen 里面了?因为在调用 2 in gen 这个命令时, 这个时候 1 已经不在这个迭代器里面了,被按需生成过了。

# 如果你还要保留以前的值,那么可以如下操作
gen = ( i  for i in range(10))

a_list = list(gen)
# 可以转换成列表,也可以转换成元祖。

2 in a_list
# True

5 in a_list
# True

1 in a_list
# True
# 就算循环过, 值还在

3. lambda 在闭包中会保存局部变量

这是问题以前一直想不明白,今天看到了一个比较好的解释,现在整理一下。先看一段代码:

my_list = [ lambda : n for n in range(5) ]
for x in my_list:
  print x()

# output
4
4
4
4
4

如果你写这段代码,本意是想输出0,1,2,3,4,但是结果却输出了4,4,4,4,4。要解决这个问题,我们可以将代码修改如下:

# 坚持修改成 list
my_list = [ lambda n = i: n for n in range(5) ]
for x in my_list:
  print x()

# output
0
1
2
3
4

# 修改成生成器
my_list = ( lambda n = i: n for n in range(5) )
for x in my_list:
  print x()

# output
0
1
2
3
4

这是为什么呢?其实,你可以联想一下函数中关于用 list 作为函数参数默认值的问题,如下:

def add(num, l = []):
  l.append(num)
  return l

l1 = add(1)
l2 = add(2)
print 'l1 = ', l1
print 'l2 = ', l2

# output
l1 = [1, 2]
l2 = [1, 2]

这个修改方案也非常简单,不要使用可变对象(列表)作为函数默认值,修改如下:

def add(num, l = None):
  if l is None:
    l = []
  l.append(num)
  return l

l1 = add(1)
l2 = add(2)
print 'l1 = ', l1
print 'l2 = ', l2

# output
l1 = [1]
l2 = [2]

关于这个问题的解释是函数参数默认值在函数定义的时候就已经被创建了,等到函数运行时我们只是在多次的引用同一个变量。为什么要先说明这个问题呢?因为 lambda 就是一个匿名函数。比如:

def func(x):
  return x

# 等价于
func = lambda x:x

所以在函数中的默认值问题在 lambda 中也是同样存在的,当然也有同样的解决方法。我们再来看最初的问题,我们把问题的形式转换一下,如下:

my_list = [ lambda : n for n in range(5) ]
for x in my_list:
  print x()

# 等价于
my_list = []
for n in range(5):
  my_list.append(lambda : n)
for x in my_list:
  print x()

我们在 my_list.append(lambda : n) 中定义的 lambda,其中的 n 是引用 for n in range(5) 这一句中的,这只是 lambda 的定义阶段,lambda 并没有执行,等这两句执行完之后,n 已经等于 4 了,也就是说,我们定义的这5个lambda全部变成了 lambda : 4,等到执行的时候自然输出就成了4,4,4,4,4。我们把上面利用列表的解决方案再换一种形式写一遍,如下:

my_list = []
for i in range(5):
  my_list.append(lambda n = i: n)
for x in my_list:
  print x()

其实,函数和lambda的本质是一样的,那么lambda重的参数默认值的效果应该和函数中的参数默认值的效果也是一样的,函数中的参数默认值是在定义的时候创建并保存的,那么lambda中的参数默认值也一定是一样的。所以这5个lambda有了各自不同的参数默认值,而不是去引用同一个。

那么,这又有一个新的问题,就是函数中的变量都是在什么时候分析引用的。先来做一个简单的实验,如下:

def func(num, l = x):
  l.append(num)
  return l

以上函数如果是在交互模式下输入的,应该在函数输入完毕后马上报错,告诉你 x 没有定义,我们再来看看另一种情况,如下:

def func(n):
  print x

这个函数输入完毕之后,同样是 x 没有定义,系统没有马上报错,但是当你调用的时候才会报错。

这样问题就很明显了,函数(包括lambda)中的默认参数会在函数定义的时候创建或者引用,而函数体内的变量则要等到调用这个函数时才会被创建或者引用。

至此,解释完毕。


4. 在循环中修改列表项

代码如下:

a = [1, 2, 3, 4, 5]
for i in a:
  if not i % 2:
    a.remove(i)

# output
a = [1, 3, 5]
# 结果正常

b = [2, 4, 5, 6]
for i in b:
  if not i % 2:
    b.remove(i)

# output
b = [4, 5]
# 本想去除所有偶数,但显然不对

思考一下,为什么不对?因为当你remove的时候,你影响了列表的index。如下代码更让你明白:

b = [2, 4, 5, 6]
for index, item in enumerate(b):
  print index, item
  if not item % 2:
    print item
    b.remove(item)

# output
(0, 2) # 这里没有问题,2 被删除了
(1, 5) # 因为2被删除了,目前的列表是[4, 5, 6],所有索引list[1]直接去找5,忽略了4
(2, 6) # 6 被删除了

所以,在循环中不要随意修改列表项。


5. 重用全局变量

代码如下:

def my_func():
  # 我们先调用一个未定义的变量
  print var

var = 'global' # 函数之后赋值
# 反正只要调用函数时候,变量被定义了就可以了。
my_func()
# output
global

def my_func():
  var = 'local changed'

varr = 'global'
my_func()
print var

# output
global
# 我们发现,局部变量没有影响到全局变量。

def my_func():
  print var # 虽然你全局设置了这个变量,但是局部变量有同名的,python以为你忘记了定义本地变量了
  var = 'local changed'

var = 'global'
my_func()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-67-d82eda95de40> in <module>()
----> 1 my_func()

<ipython-input-65-0ad11d690936> in my_func()
      1 def my_func():
----> 2         print(var)
      3         var = 'locally changed'
      4

UnboundLocalError: local variable 'var' referenced before assignment

def my_func():
  global var # 这个时候就要加上全局了
  print var
  var = 'local changed'

var = 'global'
my_func()
# output
global
print var
# output
local changed # 因为使用了global,就改变了全局变量。

6. 拷贝可变对象

代码如下:

my_list = [[1,2,3]] * 2
# output
my_list = [[1,2,3], [1,2,3]]

my_list[0][0] = 'a' # 我只修改了子列表中的一项
# output
my_list = [['a',2,3], ['a',2,3]] # 但是都影响到了

# 用这种循环生成不同对象的方法就不影响了
my_list = [ [1,2,3] for i in range(2)]
my_list[0][0] = 'a'
# output
my_list = [['a',2,3], [1,2,3]]

7. 列表的 + 和 +=,append和extend

首先说明,id函数可以获得对象的内存地址,如果两个对象的内存地址是一样的,那么这两个对象肯定是一个对象。

list = []
print 'ID: ', id(list)
# output
ID: 1234567890

list += [1]
print 'ID: ', id(list)
# ID: 1234567890
# 使用 += ,还是在原来的列表上操作

list = list + [2]
print 'ID: ', id(list)
# ID: 9876543210
# 使用 + ,其实已经改变了原有列表

list = []
print 'ID: ', id(list)
# output
# 'ID'1212121212

list.append(1)
print 'ID: ', id(list)
# output
# ID: 1212121212
# append 是在原来列表上面添加

list.extend([2])
print 'ID: ', id(list)
# output
# ID: 1212121212
# extend 也是在原来列表上面添加

8. bool 其实是 int 的子类

代码如下:

True + True
# output
2

3 * True
# output
3

True << 10
# output
1024

本文转载自:http://www.jianshu.com/p/245770b9378d

共有 人打赏支持
AllenOR灵感
粉丝 10
博文 2634
码字总数 82983
作品 0
程序员
Python读取大文件的"坑“与内存占用检测

python读写文件的api都很简单,一不留神就容易踩”坑“。笔者记录一次踩坑历程,并且给了一些总结,希望到大家在使用python的过程之中,能够避免一些可能产生隐患的代码。 1.read()与readlin...

LeeHappen
08/24
0
0
转行零基础该如何学Python?这些一定要明白!

转行零基础学Python编程开发难度大吗?从哪学起?近期很多小伙伴问我,如果自己转行学习Python,完全0基础能否学会呢?Python的难度到底有多大?今天,小编就来为大家详细解读一下这个问题。...

猫咪编程
07/21
0
0
Python修炼之路-循序渐进

Python已经演变为一个庞大的生态系统,由于其底层全部用c写成,而且全部开放源码,因此几乎可以完成其它任何编程语言能干的事情。python虽然简单,但要成为高手也非一日之功。如何通过日常的...

openthings
2015/01/21
0
2
别人Python都玩腻了,而你却连安装工具库都搞不清楚?

9:00 你打开电脑,双击各部门交上来的周报,轻车熟路地开始了crtlC,ctrlV工作,把表格统计在一起。 15:00 你发现投资部的表格里多了一个字段,导致你表格结构全错了,你很恼火…… 16:30 重新...

tw6cy6ukydea86z
04/26
0
0
基于python3在nose测试框架的基础上添加测试数据驱动工具

[本文出自天外归云的博客园] Python3下一些nose插件经过2to3的转换后失效了 Python的nose测试框架是通过python2编写的,通过pip3install的方式安装的nose和相关生成报表的插件,执行测试时会...

天外归云
2017/08/07
0
0

没有更多内容

加载失败,请刷新页面

加载更多

[Python进阶] Python命令行参数

Python 获得命令行参数的方法 需要模块:sys 参数个数:len(sys.argv) 脚本名: sys.argv[0] 参数1: sys.argv[1] 参数2: sys.argv[2] 解析命令行参数 Python提供了一个getopt模块,可用于解...

Eappo_Geng
13分钟前
0
0
add docker api url to jenkins

add docker api url to jenkins add jenkins to dockergroup gpasswd -a $USER docker gpasswd -a jenkins docker DOCKER_OPTS="-H tcp://0.0.0.0:4243 -H unix:///var/run/docker.sock" in ......

kewei_zhang
17分钟前
0
0
Scala入门篇

1、定义变量 var 可变 val 不可变,相当于Java中的final Unit相当于Java中的void,以()表示 scala> val a = println("ddd") ddd a: Unit = () 2,声明数组 scala> val arr = Array(1,2,3,4,5) ......

算法之名
18分钟前
6
0
利用redis统计分布式集群中接口缓存命中情况

接口使用了缓存,想看看缓存命中率,到底提升了多少了?固想到做个统计方法,单机情况下使用 AtomicImteger,考虑到分布式集群中多台服务器调用,所以考虑使用redis进行统计 原来的想法很简单用分布...

计算机的小二青年
18分钟前
0
0
前端加密JS库--CryptoJS 使用指南

有时候项目涉及到的敏感数据比较多,为了信息安全,我们常常需要对一些数据进行接口加密处理,如编码、将明文转化为暗文、加密比对、AES + BASE64 算法加密等。 Base64 编码 为什么要编...

舒龙虎
20分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部