Python 中 9 种奇怪的特性及其解释

Python 是一种优雅的语言,但远非完美。

Python 有一些奇怪的特性,甚至会让资深开发人员感到困惑。

光说不练假把式。本文将向您展示令人困惑的 Python 代码段,并给出精确的解释。

1.有 “else “语句,但没有 “if “语句

许多编程语言都有 “if-else “结构来处理条件语句。

然而,在 Python 中,您甚至可以使用没有 “if “的 “else “语句。

leaders = ["Elon", "Tim", "Warren"]

for i in leaders:
    if i == "Yang":
        print("Yang is a leader!")
        break
else:
    print("Not found Yang!")

# Not found Yang!

例如,上述代码中没有 “if “语句。但 “else “语句下的代码块已成功执行!

这就是 Python 中的 “for-else “语法。

这是一个奇怪的特性,我们在编码中使用它时应该小心谨慎。但它的思想却出乎意料地简单:

当 for 循环没有中断时,”else “代码块就会执行。

为了证明这一点,让我们稍微修改一下列表:

leaders = ["Yang", "Elon", "Tim", "Warren"]

for i in leaders:
    if i == "Yang":
        print("Yang is a leader!")
        break
else:
    print("Not found Yang!")

# Yang is a leader!

如上面的程序所示,此时 leaders列表中包含 “Yang”。因此,for 循环被破坏,else 语句没有执行。

2.不可变的元组被更改

我们知道,元组是不可变的 Python 对象。

但下面的元组是可以更改的:

tp = ([1, 2, 3], 4, 5)
tp[0].append(4)
print(tp)
# ([1, 2, 3, 4], 4, 5)

这是因为嵌套的 Python 对象的可变性取决于每个对象本身。tp 是一个不可变的元组,但 tp 的第一项是一个可变的 list。

3.256 是 256,但 257 不是 257

如果用 Python 比较数字,有时结果会出人意料:

>>> a=256
>>> b=256
>>> a is b
True
>>> x=257
>>> y=257
>>> x is y
False

在底层下,Python 预加载了[-5, 256]范围内的所有小整数,以节省时间和内存成本。因此,当声明这个范围内的整数时,Python 只会引用缓存的整数,而不会创建任何新对象。

换句话说,a 和 b 是同一个对象,但 x 和 y 是两个不同的对象。

我们可以打印每个变量的 id 来证明这一点:

>>> id(a)
1696073345424
>>> id(b)
1696073345424
>>> id(x)
1696122928496
>>> id(y)
1696122928752

这种机制被命名为整数互操作或整数缓存。

但是,下面的代码会打印出什么呢?还是False

>>> 257 is 257

事实上,Python 总是会尽力让事情保持清晰。由于我们没有分别定义两个变量,Python 可以在单行命令中获得足够的上下文,这次将打印 True。

4.Python 中的字符串比较

与整数缓存机制类似,Python 也会缓存小尺寸的字符串,以节省计算资源。

让我们来看一个例子:

>>> a = "Yang"
>>> b = "Yang"
>>> a is b
True
>>> c = "Yang Zhou"
>>> d = "Yang Zhou"
>>> c is d
False

字符串缓存也取决于 Python 的实现。

在本例中,我使用的是 CPython,它的缓存算法是 AST optimizer,最多可以缓存 4096 个字符,但任何包含空格的字符串都不会被置换。

5. 0.1+0.2 is not 0.3

每个人都知道 0.1 + 0.2 等于 0.3,但 Python 并不这么认为:

print(0.1+0.2==0.3)
# False

如果不是 0.3,那么在 Python 中 0.1+0.2 的结果是什么?

print(0.1+0.2)
# 0.30000000000000004

公平地说,这不是 Python 的错。没有计算机能精确计算浮点数值。

计算机只能存储和处理一定精度的浮点数。因此,浮点运算依赖于机器处理器中的硬件实现,没有一种编程语言可以说它的浮点计算总是正确的。

6.”+=”比”=”更快

在 Python 中连接字符串时,”+=”和 “+”操作符可以得到相同的结果,但代价不同:

import timeit

print(timeit.timeit("s1 = s1 + s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100))
# 0.7268792000000001
print(timeit.timeit("s1 += s2 + s3", setup="s1 = ' ' * 100000; s2 = ' ' * 100000; s3 = ' ' * 100000", number=100))
# 0.3451913999999999

在 Python 中,当连接两个以上字符串时,+= 操作符比 + 更快。因为 += 是就地操作,与 + 操作相比,可以节省创建新对象的时间。

这个特性乍一看很奇怪,但它能帮助我们大大加快程序的运行速度,尤其是在处理复杂字符串的情况下。

7.三个点

如果需要一个 Python 函数的占位符,您可能会使用 pass 关键字:

def my_func():
   pass

但还有另一种方法:

def my_func():
   ...

是的,三点或省略号也可以是占位符。

这种语法糖背后的故事非常有趣。Python 之父 Guido van Rossum 使省略号在语法上正确,因为 “有些人认为它很可爱”。

此外,省略号还可用于 Python 中的类型提示。

例如,任意长度的同质元组可以这样表达 – Tuple[int,...]

8.Python 中的 “with “语句是什么?

其他编程语言都没有 “with “语句,因此对于初学者来说,Python 的 “with “语句显得非常奇怪。

with open("test.txt",'w') as f:
    f.write("Yang is writing!")

事实上,”with “语句只是用于上下文管理的语法糖。使用它,你就不需要明确地编写 close() 函数来关闭文件,因为文件会在使用后自动关闭。

示例代码如下

f = open("test.txt",'w')
try:
    f.write("Yang is writing!")
except Exception as e:
    print(f"An error occurred while handling the file: {e}")
finally:
    # Always close a file after using it
    f.close()

哪个片段更整洁?

with “语句是 Pythonic 的一大特色。

9.一个星号和两个星号

如果您阅读开源 Python 项目,下面这种函数定义样式可能会出现很多次:

def func(*args, **kwargs):
    pass

这些奇怪的星号是什么意思?

在定义 Python 函数时,我们可以定义一个前缀为星号的参数来捕捉无限数量的参数。

  • 前缀为一个 * 的参数可以捕获任意数量的位置参数到一个元组中。
  • 以两个 * 为前缀的参数可以将任意数量的关键字参数捕获到一个 dict 中。

此外,在某些情况下,利用 Python 中的星号可以使我们的代码更加优雅。

例如,我们可以借助星号解包iterables:

A = [1, 2, 3]
B = (4, 5, 6)
C = {7, 8, 9}
L = [*A, *B, *C]
print(L)
# [1, 2, 3, 4, 5, 6, 8, 9, 7]

 

本文文字及图片出自 9 Weird Python Features and How To Explain Them

余下全文(1/3)
分享这篇文章:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注