Python 开发指南:关键字 VS 对不可变的理解

2022-09-1811:33:25编程语言入门到精通Comments1,136 views字数 3664阅读模式

Python 关键字

or & and & not

为了提高代码的可读性,Python 分别使用 or 代替了 "或",and 代替了 "与",not 代替了 "非",这些运算符常用于条件判断式。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

print(not False)  # True
print(False or False)  # False
print(False or True)  # True
print(True and False)  # Fale
print(True and True)  # True
print(1 not in [1, 2, 3])  # False

除此之外,orand 还有一个延伸用法。比如,对于两个数值型而言,x or y 可以返回两者中的较小值,而 x and y 可以返回两者中的较大值。如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

print(2 or 3)  # 2
print(3 and 5)  # 5

pass

pass 关键字充当 Python 语法上的占位符。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

if x >= 10:
    pass
else:
    print("x >= 10")

或者将一些仅声明但未给出实现的函数 foo 加上 pass 以保持语法完整。比如:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

def foo():  # TODO waiting for implementing
    pass

None

None 在 Python 中作为一个特殊的常量,它的类型为 NoneType。它代表语义上的空值,但自身既不是 0,也不是 False文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

None 可以用于设计部分函数 ( partial function )。比如说,当某个函数 f 选择不去对某些输入进行处理时,它可以不选择抛出异常或者是其它预设好的默认值,而是简单地以 None 代替。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

def div(x, y):
    if y == 0:
        return None
    else:
        return x / y

print(div(3, 0))

这种设计思想被广泛用于函数式编程。比如:Scala:函数式编程下的异常处理 - 掘金 (juejin.cn)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

*is & ==

is 关键字常和 == 放到一起去讨论。两者的主要区别是:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

  1. == 进行的是值比较,强调相等。
  2. is 进行的是引用比较,强调相同。

在 Python 中,可以通过内置的 id() 函数获得某个对象的全局标识号,该标识号的作用相当于 C 语言的地址。若两者的标识号相同,则认为两者的引用相同。此时使用 is 比较的结果为 True,否则为 False文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

而对象的 == 操作符底层指向 __eq__() 方法,它和 __hash__() 方法 成对出现。同理,还可以为对象定义 >=<= 等运算符重载。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

class Obj:
    # __init__ 相当于其它语言中的对象构造器
    # self.x 表示声明对象的内部属性。
    def __init__(self, v_):
        self.v = v_
        
    def __eq__(self, other): return self.v == other.v
    def __hash__(self): return hash(self.v,)

o1 = Obj(1)
o2 = Obj(1)

print(o1 == o2)  # True
print(o1 is o2)  # False

数值之间的比较应使用 == ,另外,比较一个值是否为 None 时使用 is。因为 None 相当于是一个全局的单例对象,所有被赋值为 None 的变量总会指向同一处引用。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

*in

in 是一个实用的关键字。我们可以快速地利用该关键字验证某个元素是否在可迭代的数据结构,如列表,切片,元组,集合,字典。而查找机制的底层仍然离不开比较,即 相等性判断。如果查找的元素是 对象,Python 会优先尝试调用用户重写的 __eq()__ 方法,否则仍然按照引用进行比较,见下面的例子。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

class Foo:
    def __init__(self, v_):
        self.v = v_

    def __eq__(self, other): return self.v == other.v
    def __hash__(self): return hash(self.v,)


class Goo:
    def __init__(self, v_):
        self.v = v_


cond1 = Foo(1) in [Foo(1)]
print(cond1)  # True

cond2 = Goo(1) in [Goo(1)]
print(cond2)  # False

由此可见,重写 __eq__() 方法对明确类的语义很重要。否则,看似高可读的代码实际上会返回完全相悖的结果。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

yield from*

懒加载是一个偏 Functional 的话题。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

首先,yield 关键字可用于生成一个 懒加载 的数据流,避免一次性将要处理的数据全部读入内存,从而减少资源浪费。比如,下面的 seq() 函数用于生成无限流:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

def seq(start: int = 0):
    while True:
        yield start
        start += 1

gen = seq(0)

一旦某个函数使用 yield 作为返回值,Python 就会将其翻译为生成器 ( Generator )。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

上述的代码通过调用 seq(0) 创建了一个生成器实例并赋值给了 gennext() 函数能够调用一次生成器并得到一个返回值。生成器每被调用一次,就会执行函数体到 下一条 yield 语句,产生出一个值返回给外界,随后停下来等待被下一次调用,直到执行最后一条 yield 之后退出。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

如你所见,yield 可以使函数在运行到某一段代码处后被 "暂停"。这种特性可以被用来设计协程。感兴趣的同学可以参考:Python 的关键字 yield 有哪些用法和用途? - 知乎 (zhihu.com)文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

比如,利用上面的生成器 gen,我们可以不断地生成递增的连续序列:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

"""
    这里的 for 循环是为了反复调用 next(gen) 生成 10 个连续自然数
    xs = [0, 1, 2, ... , 9]
    ys = [10, 11, 12, ... , 19]
"""
n = 10

xs = [next(gen) for _ in range(n)]
ys = [next(gen) for _ in range(n)]

print(*xs)
print(*ys)

由于 seq() 函数本身是一个死循环,因此 gen 总是能够源源不断地返回值。下面是一个更容易被理解的简单生成器,它没有包含任何循环语句:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

def finite_seq():
    yield 1
    yield 3
    yield 5

finite_gen = finite_seq()

print(next(finite_gen))  # 返回第一个 yield 值 1
print(next(finite_gen))  # 返回第二个 yield 值 3
print(next(finite_gen))  # 返回第一个 yield 值 5

print(next(finite_gen))  # StopIteration

生成器 finite_gen 会在依次产生数据 1 3 5 之后关闭。如果此时企图再生成更多的数据,则程序会抛出 StopIteration 异常。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

生成器也是可遍历对象。在这个例子中,可以直接使用 for 循环将流内的元素全部提取出来,因为 finite_gen 不会无休止地生成元素。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

def finite_seq():
    yield 1
    yield 3
    yield 5

finite_gen = finite_seq()

for x in finite_gen:
    print(x,end=", ")

不要在无限流中这么做,否则程序会陷入死循环。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

有些高阶的生成器会依赖其它生成器 ( 或者递归调用自身 ) 生成元素,此时需要引入 yield from 关键字。回到最开始的例子:我们现在能够以递归的形式定义一个不断累增的无限流:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

# 这种无限流也称之为共递归。
def seq(start):
    yield start
    yield from seq(start+1)

gen = seq(0)
xs = [next(gen) for _ in range(10)]
print(*xs)

下面是一个稍稍复杂的案例:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

def flatten(xs: list):
    for i in range(len(xs)):
        if isinstance(xs[i], list):
            yield from flatten(xs[i])
        else:
            yield xs[i]

xxs = [1,[2,3,[4,5]],6,[7,8]]
xs = [x for x in flatten(xxs)]
print(*xs) # 1 2 3 4 5 6 7 8

flattten 生成器会检测 xs 的元素是否还包含列表。若是,则递归地创建一个子生成器提取该子列表的元素。因此,flatten 可以将任意复杂的列表展平成一维列表。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

*小结

在 Python 的设计理念中,对象的相等性是重复性的子问题:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

  1. __eq__() 定义了相等性,这决定了 ==in 操作符的结果。
  2. __eq__()__hash__() 定义了哈希计算中的重复性,这进一步决定了它是否可作为集合 set 的元素,或者是字典 dict 的 key。

其次,在遍历列表或切片时,避免意外的引用共享,抑或无意中破坏了它,导致设计出的程序与预期不符。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

最后是对不可变的理解。数值和字符串的不可变,相等性,重复性都是直观的,而元组的不可变指引用不可变,但元素内部的状态仍然是可变的。为了避免意外的麻烦,如果要将某个元组作为字典的 key,则需使内部所有对象元素都是 hashable type。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

额外地,和 Java 不同,Python 的引用相等性,是靠 id() 全局标识决定的,这决定了 is 操作符的结果。它和哈希 __hash__() 函数是两回事。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

作者:花花子
来源:稀土掘金文章源自菜鸟学院-https://www.cainiaoxueyuan.com/ymba/27837.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/ymba/27837.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定