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 <= 100){...}
if 0 <= x <= 100:
print("x in [0,100]")
else:
print("x not in [0,100]")
断言
断言是一种严格的条件判断。使用 assert
关键字创建一个断言,并附带由条件式 cond
和 msg
组成的二元组。当条件式判别为 False
时,程序会抛出一个 AssertionError
异常,并将 msg
消息输出到控制台。比如:
x = 100
y = 0
assert y != 0, "y should not be 0."
# 下方的代码是不可达的。
z = x / y
异常捕获
Python 使用 try
,except
,finally
( 可缺省 ) 来守护一段代码块,并在代码块抛出异常时捕捉,避免程序中断退出。
try:
100 / 0
except ZeroDivisionError as e: # 将捕获到的异常赋值给 e
print(f"error! => {e}")
finally: # finally 是可选的
print("done.")
如果要捕获多个异常,可以将 except
写成:exception (ErrorType1, ErrorType2, ...) as e:
。
可以通过 raise
关键字主动抛出异常。比如:
raise Exception("throw a new Exception by user code")
在函数式风格的数据流处理中,函数会一般会将异常值收集起来,并统一抛给上层代码。比如:
def f(x: int): return (x, None) if 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)
这种特性有很多可引申的话题,见后文设计模式中的面向函数编程。
作者:花花子
来源:稀土掘金