Python 开发指南:重要语法IF、断言、异常捕获、with 关键字实现资源开闭
选择分支
Python 没有 switch
分支,所有的多选择分支都使用 if
作为代替。其中,else if
语法被简化成了 elif
。如:
identify = "Student" if identify is "Student": print("he is a student.") elif identify is "Tutor": print("he is a tutor") elif identify is "Professor": print("he is a Professor") else: print("unknown.")
Python 的 if
语句还有另外一个用途:充当其它编程语言中的三目运算符。逻辑为:若 if
的表达式成立,则赋前值,否则赋后值。如:
# a = if (10 > 1) ? true : false # 如果 10 > 1 成立,则 a = True。否则,a = False。 a = True if 10 > 1 else False print(a)
最值判断
Python 提供了内置的 max()
和 min()
函数简化了查找操作。比如:
seq = [3,6,7,8,1,4,2] max(seq) min(seq)
如果内部的元素是对象,则需要额外传入一个表达式规定比较的字段,比如:
class Foo: def __init__(self,v_): self.v = v_ list = [Foo(1),Foo(2),Foo(3)] # 规定按照 Foo 的 v 值进行比较。 mx = max(list, key=lambda foo: foo.v) print(mx.v)
区间判断
若判断某数值变量的值域,Python 提供了可读性更高的写法。如:
x = 100 # other lang: if(x >= 0 && x 0 else (None, ArithmeticError(f"{x} is a invalid value")) nonNegative = [f(x) for x in xs] right = map(lambda x: x[0], filter(lambda x: x[0] is not None, nonNegative)) left = map(lambda x: x[1], filter(lambda x: x[0] is None, nonNegative)) print(*right) # 输出正常处理之后的数据 print(*left, sep="\n") # 输出数据处理过程中都遇到了哪些异常
通过这种方式,我们可以将数据流处理的逻辑和异常处理的逻辑相互分离。
with 关键字实现资源开闭
把 with 语法看作是更加抽象的 try - catch 模型。
当涉及到打开 IO 流,或者是加锁这类场景时,自动化关闭资源的手段会帮我们省下很多功夫,就像 Go 语言的 defer
机制。
f = open(filePath, mode="r", encoding="UTF-8") f.readline() f.close()
Python 通过 with .. as
关键字提供了 通用的后置通知操作。现在,文件关闭可以改写成下面的逻辑:
with open(filePath, mode="r", encoding="UTF-8") as f: f.readline() pass
with
语句块在底层借助了 __enter__()
和 __exit__()
。换句话说,任何实现了这两个魔法函数的类实例都可以使用 with
语句块。下面是一个简单的例子:
class Daemon: def __enter__(self): # TODO pass def __exit__(self, exc_type, exc_val, exc_tb): r = "success" if exc_type is None else "failure" print(f"end.{r}") d = Daemon() with d: print("do something") pass
在这段代码中,简单表达式 d
指向一个 Daemon
类实例。内部的代码块将被 __exit__()
函数 守护,无论代码块执行成功与否,该函数总被调用。当语句块内部抛出异常时,exc_type
,exc_val
,exc_tb
三个参数将为非 None 值。
d = Daemon() with d: # end.failure print(1 / 0) pass
除此之外,如果 __enter__()
函数返回了有意义的非 None 值,我们可以通过 as
关键字来接收。比如:
class Daemon: def __enter__(self): return 10, 5 def __exit__(self, exc_type, exc_val, exc_tb): r = "success" if exc_type is None else "failure" print(f"end.{r}") d = Daemon() # Daemon 的 __enter__() 函数返回 10, 5 两个值,因此这里使用元组提取到 x1, x2 参数。 with d as (x1, x2): # 2.0 # end.success print(x1 / x2) pass
不难想到,with
语句块还能用来设计隐蔽的 try ... catch ... finally
逻辑,以此屏蔽掉清除资源的各种细节,这可以提升用户代码的可读性。
函数声明的细节
直接存在于模块的函数定义一般被称之为 function,而定义在类的函数一般称之方法 method。
Python 的函数不严格要求定义返回值类型,但是严格要求使用显式的 return
关键字声明返回值。
def add(a, b): return a + b
一个规范参数与返回值类型的函数可以声明为:
def add(a: int, b: int) -> int: return a + b
这种类型规范只有声明的意味,因为 Python 并不是一门编译型的语言。因此,即使传入了不匹配类型的参数,解释器也不会拒绝执行。
如果一个函数不返回任何有意义的值,那么返回值类型相当于 None
。比如:
def println(anything) -> None: print(anything)
在定义函数可以设定参数的默认值。比如:
def f(v1=0, v2=0): return v1 + v2 print(f()) # 0
函数可以声明 可变参数 表示该参数位置接收任意多个值,参数名前面使用一个 *
号作修饰。比如:
# non-keyword arguments def receive(*args): print(type(args)) for i in args: print(i, end=", ") receive(1, 2, 3, 4, 5) # 将列表拆分为可变参数传入 xs = [1, 2, 3, 4, 5] receive(*xs)
其中,输入的多参数 1, 2, ... 5
被包裹为一个元组 tuple
类型。而如果参数名前面使用两个星号 **
修饰,则表示它接收的是任意多个键值对。比如:
# keyword arguments def config(**kwargs): print(type(kwargs)) for k in kwargs: print(kwargs[k], end=", ") config(port=8080, server="tomcat", max_connetion=500)
整个参数列表将会被包裹成一个 dict
字典,这里要求键 key 必须是 str
类型。可以将外部的字典作为可变键值对参数传入函数内。比如:
dictionary = {"a": "abandon", "b": "banana", "c": "clap", "d:": "12"} conf(**dictionary)
为了避免混淆,Python 规定普通参数,可变参数,可变键值对参数的排列顺序依次为:
def foo(parm,*args,**kwargs):pass
有时为了提高代码的可读性,我们也会选择以 **kwargs
的形式传入参数,这在绝大部分情况都没有什么问题。比如:
def f(v1=0, v2=0): return v1 + v2 print(f(v2=3)) # 3
比如字典的
get()
方法是个例外,见:python - TypeError: get() takes no keyword arguments - Stack Overflow。一切 C-level 层次的 Python API 都不支持传入
**kwargs
。
函数内部可以定义函数,且函数自身可以返回另一个函数。比如:
def hof(p1): # 函数 f 只在 hof 定义域内部可用。 def f(p2): return 2 * p2 + p1 # 函数标识符 f 表示返回 f 本身。 return f ff = hof(5) y = ff(1) print(y)
这种特性有很多可引申的话题,见后文设计模式中的面向函数编程。
作者:花花子
来源:稀土掘金