Python拾珍
Python 提供了不少并不是完全必需的功能,使用这些功能可以写出更简洁、更可读或者更高效的代码,甚至有时候三者兼得。当然,不使用这些功能,我们依然可以写出好代码。
阅读一些开源项目(github上很常见),经常可以看到这种简洁写法。作为一名资深码农,用简洁代码呈现出应有的功能,也算是业内人士的一种高逼格的体现吧!为此,非常有必要了解一下。
条件表达式
- 条件语句通常是两个值中选择一个。例如:
# 通常写法
x =-1
if x > 0:
y = "right"
else:
y = float('nan')
# 将其用条件表达式更简洁地写出来
Y = 'right' if x > 0 else float('nan')
这条语句几乎可以直接用英语读出来: “Y gets ‘right’ if x is greater than 0; otherwise it gets ‘nan’"(当x大于零时,Y取‘right’,否则Y取‘nan’。常说:python语言入门容易,看着就像读英文,大概就是这样吧!
- 递归函数有时候可以用条件表达式重写。例如,求一个数的阶乘:
# factorial(阶乘)的一个递归版本
def factorial(n):
if n == 0:
return 1
else:
return n *factorial(n-1)
# 将其重写为条件表达式:
def factorial(n):
return 1 if n == 0 else n * factorial(n - 1)
- 条件语句的另一个用途是用来处理可选参数。如:
def __init__ (self, name, contents = None):
self.name = name
if contents == None:
self.pouch_contents = []
self.pouch_contents = contents
#将其重写
def __init__(self, name, contents = None):
self.name = name
self.pouch_contents = [] if contents == None else contents
一般来说,如果条件语句的两个条件分支都只包含简单的返回或对同一变量进行赋值的表达式,那么这个语句可以转换为条件表达式。
列表理解
- 定义函数接收一个字符串列表,将每个元素通过字符串方法 capitalize 进行映射,并返回一个新的字符串列表。
def capitalize_all(t):
res = []
for s in t:
res.append(s.capitalize())
return res
>>> capitalize_all('abcdEFG')
Out[14]: ['A', 'B', 'C', 'D', 'E', 'F', 'G']
# 可以用列表理解将其写的更紧凑
def capitalize_all(t):
return [s.capitalize() for s in t]
>>> capitalize_all("ABCDefgh")
Out[16]: ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']
上面方括号操作符说明我们要构建一个新列表。方括号之内的表达式指定了列表的元素,而for语句则表示我们要遍历的列表。
- 列表解决用于过滤操作。如:定义函数选择列表中的大写元素,并返回一个新列表:
def only_upper(t):
res = []
for s in t:
if s.isupper():
res.append(s)
return res
>>> list1 = ['A','b', 'C', 'D', 'e', 'f', 'G']
>>> only_upper(list1)
Out[20]: ['A', 'C', 'D', 'G']
# 使用列表理解将其重写
def only_upper(t):
return [s for s in t if s.isupper()]
>>> only_upper(list1)
Out[22]: ['A', 'C', 'D', 'G']
对于简单表达式来说,列表理解更紧凑、更容易阅读,并且他们通常都比实现相同功能的循环更快。但是,列表理解难以调试,无法再循环内添加打印语句。所以,还是慎用。
生成器表达式
生成器表达式和列表理解类似,但是它使用圆括弧;
>>> g = ( x ** 2 for x in range(5))
>>> g
Out[24]: <generator object <genexpr> at 0x0000021FDC299360>
结果是一个生成器对象,他知道该如何遍历值的序列。但它又和列表理解不同,它不会一次把结果都计算出来,而是等待请求。内置函数 next 会从生成器中获取下一个值:
>>> next (g)
Out[25]: 0
>>> next (g)
Out[26]: 1
>>> next (g)
Out[27]: 4
可以使用for循环来遍历所有值:
for val in g:
print(val)
9
16
生成器对象会跟踪记录访问序列的位置,所以for 循环会从上一个 next 所在的位置继续。一旦生成器遍历结束,在访问就会抛出 StopException 异常。
>>> next(g)
Traceback (most recent call last):
File "E:\Users\Administrator\Anaconda3\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
exec(code_obj, self.user_global_ns, self.user_ns)
File "<ipython-input-30-e734f8aca5ac>", line 1, in <module>
next(g)
StopIteration
生成器表达式经常和 sum、max、和min之类的函数配合使用,如:
sum(x **2 for x in range(5))
Out[31]: 30
any 和 all
- Python 提供了一个内置函数 any, 它接收一个由布尔值组成的序列,并在其中任何值是 True 时返回 True。将它用于列表:
>>> any ([False,False, True])
Out[32]: True
上面这个例子用处不大,因为它所做的事情和in表达式一样。但是我们可以用any在重写一个搜索函数,判断两个字符串是否有相同的字符。如:
# 搜索函数
def avoids(word, forbidden):
for letter in word:
if letter in forbidden:
return False
return True
# 将其重写
def avoids(word, forbidden):
return not any(letter in forbidden for letter in word)
- Python还提供了另一种内置函数 all, 它在序列中所有元素都是 True 时返回 True。
集合
Python 还提供了另一个内置类型,称为集合(set),他表现的和只使用键集合的字典类似。像一个集合添加元素很快,检查集合成员也很快。集合还提供方法和操作符来进行常见的集合操作。
集合减法使用方法 difference 或者操作符 ‘-’ 来实现。
定义函数 uses_only(word, avaliable) 判断word 中所有字符是不是在avaliable中出现。
def uses_only(word, available):
for letter in word:
if letter not in available:
return False
return True
>>> uses_only('abc', 'abcdefs')
Out[43]: True
>>> `uses_only('abch', 'abcdefs')
Out[44]: False
# 将其重写
def uses_only(word, available):
return set(word) <= set(available)
操作符 <= 检查一个集合是否是另一个集合的子集,包括两个集合相等的情况。
计数器
命名元组
很多简单的对象其实都可以看做是几个相关值的集合。例如,定义Point对象,包含两个数字,即 x和y。定义一个这样的类时,通常会从 init 方法和 str 方法开始:
class Point:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return '(%g ,%g)' % (self.x, self.y)
这里用了较多代码来传达很少的信息。Python 提供了一个更简洁的方式来表达同一个意思:
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
其中第一参数是你想要创建的类名。第二个参数是Point对象应当包含的属性的列表,以字符串表示。namedtuple 的返回值是一个类对象:
>>> Point
Out[78]: __main__.Point
# 这里Point会自动提供 __init__ 和 __str__ 这样的方法,所以不需要写他们。
>>> p = Point(1, 2)
>>> p
Out[80]: Point(x=1, y=2)
命名元组提供了快速简单类的方法,但其缺点是简单的类并不会总保持简单。可能之后需要给命名元组添加方法。这样,可以定义一个新类,继承当前的命名元组:
class Pointier(Point)
收集关键词参数
之前我们见过如何编写函数将其参数收集成一个元组:
def printall(*agrs):
print(agrs)
可以使用任意个数的按位参数(不带名称的实参)来调用这个函数:
>>> printall(1, 2.0, '3')
(1, 2.0, '3')
# 但*号操作符并不会收集关键词实参:
>>> printall(1, 2.0, third = '3')
......
TypeError: printall() got an unexpected keyword argument 'third'
要收集关键词实参,可以使用 **操作符:
def printall(*args, **kwargs):
print(args, kwargs)
# 将关键词映射到值的字典
>>> printall(1, 2.0, third='3')
(1, 2.0) {'third': '3'}
如果有一个关键词到值的字典,就可以使用分散操作符 ** 来调用函数:
>>> d = dict(x=1, y=2)
>>> Point(**d)
Out[89]: Point(x=1, y=2)
# 若没有用分散操作符,函数会把d当做一个单独的按位实参,所以它把 d 赋值给x,并没有提供 y 的赋值而报错:
>>> Point(d)
Traceback (most recent call last):
......
TypeError: __new__() missing 1 required positional argument: 'y'
在处理较多的函数时,创建和传递字典来指定常用的选项是非常有用的。
本文大量参考:像计算机科学家一样思考Python
从一名不羁的码农开始,欢迎关注我的微信公众号