条件表达式
if x > 0:
y = math.log(x)
else:
y = float('nan')
y = math.log(x) if x > 0 else float('nan')
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:
contents = []
self.pouch_contents = contents
我们可以像这样重写:
def __init__(self, name, contents=None):
self.name = name
self.pouch_contents = [] if contents == None else contents
列表推导式
def capitalize_all(t):
res = []
for s in t:
res.append(s.capitalize())
return res
def capitalize_all(t):
return [s.capitalize() for s in t]
方括号操作符表示,我们正在构造一个新列表。方括号中的表达式指定列表中的元素,for 子句表示我们要遍历的序列。
列表推导式的语法有点奇怪,因为此例中的循环变量 s 在定义之前就出现了。
列表推导式也可以用于筛选。例如,这个函数只选择 t 中为大写的元素,并返回一个新列表:
def only_upper(t):
res = []
for s in t:
if s.isupper():
res.append(s)
return res
我们可以使用列表推导式重写这个函数:
def only_upper(t):
return [s for s in t if s.isupper()]
列表推导式非常简洁、易读,至少对简单的表达式是这样的。而且通常比对应的 for 循环要更快,有时要快很多。
列表推导式的调试难度更大,因为你不能在循环中添加打印语句。建议只在计算足够简单、第一次就能写出正确代码的前提下使用。
生成器表达式
生成器表达式与列表推导式类似,但是使用的是圆括号,而不是方括号:
>>> g = (x**2 for x in range(5))
>>> g
<generator object <genexpr> at 0x7f4c45a786c0>
结果是一个表达式对象,该对象知道如何遍历一个值序列。但与列举推导式不同的是,它不会一次性计算出所有的值;而是等待求值请求。内建函数 next 从生成器获取下一个值:
>>> next(g)
0
>>> next(g)
1
抵达序列的末尾时,next 会抛出 StopIteration 异常。你还可以使用 for 循环遍历这些值:
>>> for val in g:
... print(val)
4
9
16
生成器对象会记录其在序列中的位置,因此 for 循环是从 next 结束的地方开始的。一旦生成器被消耗完,它会抛出 StopException 。
>>> next(g)
StopIteration
生成器表达式常与 sum 、max 和 min 等函数一起使用:
>>> sum(x**2 for x in range(5))
30
any 和 all
Python提供了一个内建函数 `any`,它接受一个布尔值序列,如果其中有任意一个值为 `True` 则返回 `True` 。它也适用于列表:
>>> any([False, False, True])
True
但是它通常用于生成器表达式:
>>> any(letter == 't' for letter in 'monty')
True
上面这个例子不是很有用,因为它的功能和 in 操作符一样。
可以像这样编写 `avoids` 函数:
def avoids(word, forbidden):
return not any(letter in forbidden for letter in word)
上面的函数读取来和英语没什么区别:“word avoids forbidden if there are not any forbidden letters in word.”(如果某个词中没有任何禁用字母,那么该词就算避免了使用禁用词。)
将 `any` 与生成器表达式结合使用的效率较高,因为它只要一遇到真值就会终止,所以不会对整个序列进行计算。
Python还提供了另一个内建函数 `all`,如果序列中的每个元素均为 `True` 才会返回 `True` 。
集合
uses_only 函数:
def uses_only(word, available):
for letter in word:
if letter not in available:
return False
return True
uses_only 检查 word 中的所有字符也在 available 中。我们可以像这样重写该函数:
def uses_only(word, available):
return set(word) <= set(available)
操作符 <= 检查某个集合是否是另一个集合的子集或本身,包括了二者相等的可能性。如果 word 中所有的字符都出现在 available 中,则返回 True 。
计数器
计数器(Counter)类似集合,区别在于如果某个元素出现次数超过一次,计数器就会记录其出现次数。
计数器定义在叫做 collections 的标准模块中,因此你必须首先导入该模块。你可以通过字符串、列表或任何支持迭代的数据结构来初始化计数器:
>>> from collections import Counter
>>> count = Counter('parrot')
>>> count
Counter({'r': 2, 't': 1, 'o': 1, 'p': 1, 'a': 1})
计数器的行为与字典有很多相似的地方:它们将每个键映射至其出现的次数。与字典一样,键必须是可哈希的。
与字典不同的是,如果你访问一个没有出现过的元素,计数器不会抛出异常,而只是返回 0 :
>>> count['d']
0
如果两个单词是变位词,那么它们会包含相同的字符,而且字符的计数也相同,因此它们的计数器也是等价的。
计数器提供了执行类似集合操作的方法和操作符,包括集合添加、差集、并集和交集。另外,还提供了一个通常非常有用的方法 most_common ,返回一个由值-频率对组成的列表,按照频率高低排序:
>>> count = Counter('parrot')
>>> for val, freq in count.most_common(3):
... print(val, freq)
r 2
p 1
a 1
defaultdict
collections 模块中还提供了一个 defaultdict ,它类似字典,但是如果你访问一个不存在的键,它会临时生成一个新值。
在创建 defaultdict 时,你提供一个用于创建新值的函数。这个用于创建对象的函数有时也被称为 工厂 。用于创建列表、集合和其他类型的内建函数也可以用作工厂:
>>> from collections import defaultdict
>>> d = defaultdict(list)
请注意,这里的实参是 list ,它是一个类对象,而不是 list() ,后者是一个新列表。你提供的函数只有在访问不存在的键时,才会被调用。
>>> t = d['new key']
>>> t
[]
新列表 t 也被添加至字典中。因此如果我们修改 t ,改动也会出现在 d 中。
>>> t.append('new value')
>>> d
defaultdict(<class 'list'>, {'new key': ['new value']})
如果你要创建一个列表组成的字典,通常你可以使用 defaultdict 来简化代码。
命名元组
当你像下面这样定义类时,你通常先开始定义 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
<class '__main__.Point'>
这里的 Point 自动提供了像 __init__ 和 __str__ 这样的方法,你没有必须再自己编写。
如果想创建一个 Point 对象,你可以将 Point 类当作函数使用:
>>> p = Point(1, 2)
>>> p
Point(x=1, y=2)
init 方法将实参赋值给你提供的属性。str 方法打印 Point 对象的字符串呈现及其属性。
你可以通过名称访问命令元组的元素:
>>> p.x, p.y
(1, 2)
但是你也可以把命名元组当作元组使用:
>>> p[0], p[1]
(1, 2)
>>> x, y = p
>>> x, y
(1, 2)
命名元组是定义简单类的一种便捷方式。缺点是这些简单类不会一成不变。之后你可能会发现想要给命名元组添加更多的方法。在这种情况下,你可以定义一个继承自命名元组的新类:
class Pointier(Point):
# add more methods here
或者使用传统的类定义方式。
汇集关键字实参
在可变长度参数元组中,我们学习了如何编写一个将实参汇集到元组的函数:
def printall(*args):
print(args)
你可以使用任意数量的位置实参(即不带关键字的参数)调用该函数:
>>> 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)
可以给关键字汇集形参取任意的名称,但是 `kwargs` 是常用名。上面函数的结果是一个将关键字映射至值的字典:
>>> printall(1, 2.0, third='3')
(1, 2.0) {'third': '3'}
如果你有一个有关键字和值组成的字典,可以使用分散操作符(scatter operator) `**` 调用函数:
>>> d = dict(x=1, y=2)
>>> Point(**d)
Point(x=1, y=2)
如果没有分散操作符,函数会将 `d` 视为一个位置实参,因此会将 `d` 赋值给 `x` 并报错,因为没有给 `y` 赋值:
>>> d = dict(x=1, y=2)
>>> Point(d)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: __new__() missing 1 required positional argument: 'y'
在处理有大量形参的函数时,通常可以创建指定了常用选项的字典,并将其传入函数。
网友评论