Python避坑指南(续)

在上一篇《Python避坑指南》中,我重点给大家讲了Python可变容器数据类型中的坑。除了这些,Python还有其他一些细小方面的坑,本章为大家讲解Python中这些大家可能会忽视的细节。

在这里插入图片描述-

文章目录

lambda的坑

看下面的代码,思考一下会输出什么?

def m():
    return [lambda x:x*i for i in range(4)]

a = m()
for a in m():
    print(a(1))

你可能以为会输出:

0
1
2
3

正确答案是:

3
3
3
3

这是因为在 [lambda x:x*i for i in range(4)] 列表解析式中,i在lambda声明之外,也就是说i相对lambda来说是个外部变量。当列表解析式运行完后,i的值就定格为3。

我们可以通过简单的方法来验证:

for a in m():
    print(a.__code__.co_code)
    print(a(1))

从上面的代码输出你会发现,对a的内部代码输出都是一样的,这说明列表中的lambda的逻辑都是一样的。所以输出结果也是一样的。

链式or的坑

Python支持简化链式逻辑判断。比如:

if a > 1 and a < 3:

可以简化成:

if 1 < a < 3:

当多个相等判断连接时,比如

扫描二维码关注公众号,回复: 17317498 查看本文章
if a == 3 or b == 3 or c == 3:

可能有人会简化成这样:

if a or b or c == 3: # !这是错的!

注意:上面的代码是错的!因为or==优先级要低。上面表达式的执行顺序是 if (a) or (b) or (c == 3):

替代链式or的更好的方法是用系统内置的any()方法。

if any([a == 3, b == 3, c == 3]): # 正确

如果嫌上面代码太重复,编码不够高效,可以优化成下面这种写法:

if any(x == 3 for x in (a, b, c)): # 正确

如果比较的值都是相同的,还可以进一步简化为:

if 3 in (a, b, c): # 正确

这里我们用in判断要比较的值是否在待比较的变量构成的元组中。

同理,下面这种写法也是不对的:

if a == 1 or 2 or 3:

应该这样写:

if a in (1, 2, 3):

访问字面量属性的坑

Python中一切皆对象,即便是字面量也是对象。例如7,在Python中也是对象。这也就意味着7也有属性和方法。例如bit_length()这个方法,它会返回表示这个值所需的二进制位数。

x = 7
x.bit_length()
# Out: 3

上面的代码是可以正确输出的。应为7的二进制是111,需要3位二进制来表示,所以bit_length()会返回3。你可能直观地感觉7.bit_length()也一样会返回3。但不幸的是你会得到 SyntaxError。为什么会这样?这是因为Python中.有两重含义,即可以是访问对象的属性,也可以是表示浮点数。Python解析器需要区分到底是哪一种含义。7.bit_length()7.2 解析器无法区分,因此会报语法错误。

有两种办法可以直接访问字面量的属性:

(7).bit_length() # 用括号将字面量括起来,告诉解析器这里7不是个浮点数
7 .bit_length()  # 7后面加个空格,告诉解析器这里7不是个浮点数

注意:这里加两个点7..bit_length是不对的。这样写第一个点会被理解为浮点数,第二个点会访问对象的属性。虽然语法上没有歧义,但是浮点数是没有bit_length()方法的。这里如果访问的是浮点数对象有的属性或方法,程序是可以正常运行的。

7..as_integer_ratio()
# Out: (7, 1)

is的坑

编程过程中,整型和字符串是使用最多的数据类型。为了减少整型和字符串频繁创建带来的内存开销,Python会用内部缓存一定范围的整数和字符串。当我们用is判断两个对象是否是同一个对象时,这里的内部缓存机制可能会带来让人迷惑的结果。比如:

>>> -8 is (-7 - 1)
False
>>> -3 is (-2 - 1)
True

再举一个例子:

>>> (255 + 1) is (255 + 1)
True
>>> (256 + 1) is (256 + 1)
False

这里的输出结果着实让人疑惑。-3, 255就返回True,-8, 256就是False。

更具体地说,在[-5, 255]区间内地整型在Python解析器启动时会放入内部缓存。因此用is判断这个区间内的整型是否是同一对象时会返回True。不在这个区间内地整型会在使用时创建,所以即便值相同,但在内存中不是同一对象,因此会返回False。

⚠注意,可能编译器版本不同,内部缓存地范围可能不同。

解决这个问题的方法就是永远用==判断值是否相等,不要用is

⚠注意,在Python交互式运行环境下,用is判断值相等会受到一条警告:

SyntaxWarning: "is" with a literal. Did you mean "=="?

字符串也是同样道理,永远用==判断值相等!

GIL全局锁的坑

GIL全局锁大家可能比较陌生,它跟多线程有关。在处理多线程时,全局锁有时可能会产生疑惑。请看下面这个例子:

import math
from threading import Thread

def calc_fact(num):
	math.factorial(num)

num = 600000
t = Thread(target=calc_fact, daemon=True, args=[num])
print("About to calculate: {}!".format(num))
t.start()
print("Calculating...")
t.join()
print("Calculated")

你可能以为Calculating...会在线程启动后立即打印出来,毕竟我们将calc_fact()这个比较耗时的运算放到了线程中执行。但实际上他会在计算完成后才打印。这是因为math.factorial()背后是C语言实现,线程在执行C语言实现函数时会锁住GIL直到运行结束。

有多种方法可以绕开这个问题。

第一种方法,你可以用纯Python来实现factorial的功能。

def calc_fact(num):
	""" 纯Python实现阶乘 """
	res = 1
	while num >= 1:
		res = res * num
		num -= 1
	return res

这样做的弊端就是运行速度变慢,因为我们不再使用C语言实现的阶乘函数。

第二种方法,你可以在调用C函数前休眠一下。

def calc_fact(num):
	sleep(0.001)
	math.factorial(num)

注意:这里的休眠不会影响C函数的执行,只是让主线程有机会向下执行。

多数据返回的坑

Python允许函数返回多个数据,比如下面的函数xyz就返回了2个值:

def xyz():
	return a, b

Python的这个特性很方便,当我们使用时可以用两个变量承接返回值

a, b = xyz()

但是如果用一个变量来承接多返回值,

t = xyz()

python也是允许的,只不过t的数据类型是个元组(a, b),不是函数返回的第一个值。这里大家要格外注意。

JSON中的坑

JSON是我们日常开发中用到最多的数据类型,也是前后端传输数据最常用的数据类型。但是Python对json的处理跟javascript不同,这会让很多前端转型Python开发的同学不适应。我们看下面这个例子:

my_var = 'bla'
my_key = 'key'

params = {"language": "en", my_var: my_key}

上面的代码如果在javascript中,params的内容为:

{
	"language": "en",
	"my_var": "key"
}

而在python中,param的内容为:

{
    "language": "en",
 	"bla": "key"
}

在Python中,字典中的my_varapi_key会被当做变量来求值。

猜你喜欢

转载自blog.csdn.net/qq_40647372/article/details/135377771