• 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))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    你可能以为会输出:

    0
    1
    2
    3
    
    • 1
    • 2
    • 3
    • 4

    正确答案是:

    3
    3
    3
    3
    
    • 1
    • 2
    • 3
    • 4

    这是因为在 [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))
    
    • 1
    • 2
    • 3

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

    链式or的坑

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

    if a > 1 and a < 3:
    
    • 1

    可以简化成:

    if 1 < a < 3:
    
    • 1

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

    if a == 3 or b == 3 or c == 3:
    
    • 1

    可能有人会简化成这样:

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

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

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

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

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

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

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

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

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

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

    if a == 1 or 2 or 3:
    
    • 1

    应该这样写:

    if a in (1, 2, 3):
    
    • 1

    访问字面量属性的坑

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

    x = 7
    x.bit_length()
    # Out: 3
    
    • 1
    • 2
    • 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不是个浮点数
    
    • 1
    • 2

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

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

    is的坑

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

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

    再举一个例子:

    >>> (255 + 1) is (255 + 1)
    True
    >>> (256 + 1) is (256 + 1)
    False
    
    • 1
    • 2
    • 3
    • 4

    这里的输出结果着实让人疑惑。-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")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    你可能以为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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

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

    def calc_fact(num):
    	sleep(0.001)
    	math.factorial(num)
    
    • 1
    • 2
    • 3

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

    多数据返回的坑

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

    def xyz():
    	return a, b
    
    • 1
    • 2

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

    a, b = xyz()
    
    • 1

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

    t = xyz()
    
    • 1

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

    JSON中的坑

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

    my_var = 'bla'
    my_key = 'key'
    
    params = {"language": "en", my_var: my_key}
    
    • 1
    • 2
    • 3
    • 4

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

    {
    	"language": "en",
    	"my_var": "key"
    }
    
    • 1
    • 2
    • 3
    • 4

    而在python中,param的内容为:

    {
        "language": "en",
     	"bla": "key"
    }
    
    • 1
    • 2
    • 3
    • 4

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

  • 相关阅读:
    shell脚本踩坑,source中文properties。
    登录网页优化与最佳做法
    STC51单片机32——液晶1602显示
    【QT】对话框dialog
    WebGIS开发教程:切片地图服务和动态地图服务的区别以及加载方式
    20、商品微服务-web层实现
    【考研数学】概率论与数理统计 —— 第三章 | 二维随机变量及其分布(3,二维随机变量函数的分布)
    好家伙,分布式配置中心这种组件真的是神器
    USB协议学习(二)设备枚举过程分析
    Flutter SDK 自带的 10 个最有用的 Widget
  • 原文地址:https://blog.csdn.net/jarodyv/article/details/127861364