文档章节

Python中的一些“坑”

AllenOR灵感
 AllenOR灵感
发布于 2017/09/10 01:27
字数 1982
阅读 1
收藏 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灵感
粉丝 11
博文 2635
码字总数 83001
作品 0
程序员
私信 提问
Python读取大文件的"坑“与内存占用检测

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

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

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

猫咪编程
07/21
0
0
基于python3在nose测试框架的基础上添加测试数据驱动工具

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

天外归云
2017/08/07
0
0
行,Python 终于玩大了!

Python玩大了! 自2017年国务院印发《新一代人工智能发展规划》,明确指出在中小学阶段设置人工智能相关课程后,Python一路逆袭, 作为人工智能时代最合适的语言,Python无疑被越来越多人追捧...

CSDN资讯
10/10
0
0
别人Python都玩腻了,而你却连安装工具库都搞不清楚?

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

tw6cy6ukydea86z
04/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

码云项目100,水一发

简单回顾一下: 早期构想最多的,是希望能将PHP一些类和编码分区做得更细,所以很多尝试。但不得不说,PHP的功能过于单一,是的,也许写C/C++扩展,可以解决问题,那我为什么不用C#或者Golan...

曾建凯
今天
3
0
Spring应用学习——AOP

1. AOP 1. AOP:即面向切面编程,采用横向抽取机制,取代了传统的继承体系的重复代码问题,如下图所示,性能监控、日志记录等代码围绕业务逻辑代码,而这部分代码是一个高度重复的代码,也就...

江左煤郎
今天
4
0
eclipse的版本

Eclipse各版本代号一览表 Eclipse的设计思想是:一切皆插件。Eclipse核心很小,其它所有功能都以插件的形式附加于Eclipse核心之上。 Eclipse基本内核包括:图形API(SWT/Jface),Java开发环...

mdoo
今天
3
0
SpringBoot源码:启动过程分析(一)

本文主要分析 SpringBoot 的启动过程。 SpringBoot的版本为:2.1.0 release,最新版本。 一.时序图 还是老套路,先把分析过程的时序图摆出来:时序图-SpringBoot2.10启动分析 二.源码分析 首...

Jacktanger
今天
6
0
小白带你认识netty(二)之netty服务端启动(上)

上一章 中的标准netty启动代码中,ServerBootstrap到底是如何启动的呢?这一章我们来瞅下。 server.group(bossGroup, workGroup);server.channel(NioServerSocketChannel.class).optio...

天空小小
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部