Python Tip 3: Python lambda表达式

Python中的Lambda表达式很可能让人迷惑,我们还是从一段官方解释文字开始:

Small anonymous functions can be created with the lambda keyword. This function returns the sum of its two arguments: lambda a, b: a+b. Lambda functions can be used wherever function objects are required. They are syntactically restricted to a single expression. Semantically, they are just syntactic sugar for a normal function definition.

理解lambda表达式的本质需要回答两个问题:为什么需要lambda?lambda的好处在哪里?

首先为什么需要lambda? 让我们看几个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> add_one = lambda x: x + 1
>>> add_one(1)
2
>>> add_one(2)
3
>>> add_one(100)
101
>>> def add_one_fun(x):
...     return x + 1
... 
>>> add_one_fun(1)
2
>>> add_one_fun(100)
101
>>> x_sum_y = lambda x, y: x + y
>>> x_sum_y(1, 1)
2
>>> x_sum_y(1, 10)
11
>>> def sum_fun(x, y):
...     return x + y
... 
>>> sum_fun(1, 1)
2
>>> sum_fun(1, 2)
3
>>> sum_fun(1, 10)
11

很遗憾,lambda并非必须,每个lambda都可以用函数替代。那么我们为什么需要lambda表达式?或者lambda表达式有什么优点?其实,官方文档里已经说得很明确了:首先,它是一个小的匿名函数,可以用在任何需要函数对象的地方,它的语法被严格限制在一行表达式(并且不应该太复杂);从语义上来讲,它们只是正常的函数定义的语法糖。

Python不是一个纯函数式编程语言,但是引入了sorted, map, reduce, filter等函数式编程的一些特性, 可以将函数作为参数传给这些函数,这里,lambda表达式就派上用场了。我们熟悉的大概是sorted函数了:

1
2
3
4
5
>>> test_list = [('two', 2), ('three', 3), ('one', 1), ('five', 5), ('four', 4)]
>>> sorted(test_list, key=lambda x: x[1])
[('one', 1), ('two', 2), ('three', 3), ('four', 4), ('five', 5)]
>>> sorted(test_list, key=lambda x: x[0])
[('five', 5), ('four', 4), ('one', 1), ('three', 3), ('two', 2)]

map() 接收一个 function 和一个 iterable 作为参数,该函数会返回一个迭代器,该迭代器将 function 应用于 iterable 的所有元素,并产生结果。下述示例,对列表中所有元素求平方和立方:

1
2
3
4
5
6
7
>>> test_list = [2, 3, 5, 7, 11, 13, 19, 17]
>>> map_list = list(map(lambda x: x ** 2, test_list))
>>> map_list
[4, 9, 25, 49, 121, 169, 361, 289]
>>> map_list = list(map(lambda x: x ** 3, test_list))
>>> map_list
[8, 27, 125, 343, 1331, 2197, 6859, 4913]

reduce() 函数会对参数序列中元素进行累积。函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。注意在python3中reduce已经从全局函数中移除,需要从functools中import,我们来看一个1到100整数求和的例子:

1
2
3
4
5
6
>>> test_list = list(range(1, 101))
>>> test_list
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
>>> from functools import reduce
>>> reduce(lambda x, y: x + y, test_list)
5050

filter() 提供了一种优雅的方法来过滤 iterable(可迭代对象,例如:列表)中的所有元素,过滤条件是 function 返回 True:

1
2
3
4
5
6
7
>>> test_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
>>> filter_list = list(filter(lambda x: x % 3 == 0, test_list))
>>> filter_list
[3, 6, 9, 12, 15]
>>> filter_list = list(filter(lambda x: x % 5 == 0, test_list))
>>> filter_list
[5, 10, 15]

注:原创文章,转载请注明出处及保留链接“Python时代”:http://www.pythonage.com/

本文链接地址:Python Tip 3: Python lambda表达式 http://www.pythonage.com/?p=47

Python Tip 2: Python *args **kwargs 用法

关于Python中 *args 和 **kwargs的用法,网上有很多介绍文章,还是先看一段官方的解释:

When a final formal parameter of the form **name is present, it receives a dictionary (see Mapping Types — dict) containing all keyword arguments except for those corresponding to a formal parameter. This may be combined with a formal parameter of the form *name (described in the next subsection) which receives a tuple containing the positional arguments beyond the formal parameter list. (*name must occur before **name.)

首先需要了解Python函数传递参数的方式有两种:

位置参数(positional argument)
关键词参数(keyword argument)

然后再来看*args(*name)与**kwargs(**name)的区别,两者都是python中的可变参数:

*args表示任何多个无名参数,它本质是一个tuple;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> def args_one(narg, *args):
...     print("Normal arg:", narg)
...     for arg in args:
...         print("other args:", arg)
... 
>>> args_one(1, 2, 3, 4, 5)
Normal arg: 1
other args: 2
other args: 3
other args: 4
other args: 5
>>> args_one('a', 'b', 'c', 'd', 'e')
Normal arg: a
other args: b
other args: c
other args: d
other args: e
>>> tuple_args = (1, 2, 3, 4, 5)
>>> args_one(1, tuple_args)
Normal arg: 1
other args: (1, 2, 3, 4, 5)
>>> args_one(1, *tuple_args)
Normal arg: 1
other args: 1
other args: 2
other args: 3
other args: 4
other args: 5

**kwargs表示关键字参数,它本质上是一个dict:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
>>> def args_two(narg, **kwargs):
...     print("Normal arg:", narg)
...     for key, value in kwargs.items():
...         print("other keyword arg: %s: %s" % (key, value))
... 
>>> args_two(narg=1, karg1="one", karg2="two")
Normal arg: 1
other keyword arg: karg1: one
other keyword arg: karg2: two
>>> key_args = {"a": "A", "b": "B", "c": "C"}
# 以下两个是错误的调用示范
>>> args_two('a', key_args)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: args_two() takes 1 positional argument but 2 were given
>>> args_two('a', *key_args)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: args_two() takes 1 positional argument but 4 were given
# 正确做法
>>> args_two('a', **key_args)
Normal arg: a
other keyword arg: a: A
other keyword arg: b: B
other keyword arg: c: C

同时使用*args和**kwargs时,必须*args参数列要在**kwargs前, 让我们最后来看一个综合例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> def args_fun(normal, *args, **kwargs):
...     print("narmal=", normal)
...     print("*args=", args)
...     print("**args=", kwargs)
... 
>>> args_fun(1)
narmal= 1
*args= ()
**args= {}
>>> args_fun(1, 2)
narmal= 1
*args= (2,)
**args= {}
>>> args_fun(1, 2, 3)
narmal= 1
*args= (2, 3)
**args= {}
>>> args_fun(1, a=1, b=2, c=3)
narmal= 1
*args= ()
**args= {'a': 1, 'b': 2, 'c': 3}
>>> args_fun(1, 2, 3, a=1, b=2, c=3)
narmal= 1
*args= (2, 3)
**args= {'a': 1, 'b': 2, 'c': 3}
>>> args_fun(1, a=1, b=2, c=3, 2, 3)
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument

注:原创文章,转载请注明出处及保留链接“Python时代”:http://www.pythonage.com/

本文链接地址:Python Tip 2: python *args **kwargs 用法 http://www.pythonage.com/?p=15

Python Tip 1:Python循环语句中的else语法(for else, while else)

在Python的学习过程中,for...else..., while...else...语法可能是比较令人困惑的一个Python知识点。先来看一段Python官方的说明:

"Loop statements may have an else clause; it is executed when the loop terminates through exhaustion of the list (with for) or when the condition becomes false (with while), but not when the loop is terminated by a break statement."

Python循环语句(for, while)有可能带一个else分支,当一个for循环正常执行完毕时或者当一个while循环正常执行完毕(循环条件变为false)时它被触发执行,但是如果这个循环被break语句非正常中止时,则这个else分支不执行。让我们来看几个例子:

1)for循环正常执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> for i in range(7):
...     print(i)
... else:
...     print("No abnormal interruption")
... 
0
1
2
3
4
5
6
No abnormal interruption

2) for循环“非正常”break中断:

1
2
3
4
5
6
7
8
9
10
11
>>> for i in range(7):
...     print(i)
...     if i == 3:
...         break
... else:
...     print("No abnormal interruption")
... 
0
1
2
3

3) while循环正常执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> i = 0
>>> while i < 7:
...     print(i)
...     i += 1
... else:
...     print("No abnormal interruption")
... 
0
1
2
3
4
5
6
No abnormal interruption

4) while循环“非正常”break中断:

1
2
3
4
5
6
7
8
9
10
11
12
>>> i = 0
>>> while i < 7:
...     print(i)
...     i += 1
...     if i == 3:
...         break
... else:
...     print("No abnormal interruption")
... 
0
1
2

总结起来就是:当循环中没有break时,else被执行,当循环中有break并且被触发时,else不被执行,类似这个case,即使循环语句中有break,但是如果没有触发break正常执行完毕,也会执行else:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> for i in range(7):
...     print(i)
...     if i > 7:
...         break
... else:
...     print("No abnormal interruption")
... 
0
1
2
3
4
5
6
No abnormal interruption

那么,这个有点绕口的Python语法糖有什么好处呢?让我们再看一个Case,这个Case参考自Python官方的一个寻找素数的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> for n in range(2, 20):
...     for x in range(2, n):
...         if n % x == 0:
...            print(n, 'equals', x, '*', n//x)
...            break
...     else:
...         print(n, 'is a prime number')
... 
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
10 equals 2 * 5
11 is a prime number
12 equals 2 * 6
13 is a prime number
14 equals 2 * 7
15 equals 3 * 5
16 equals 2 * 8
17 is a prime number
18 equals 2 * 9
19 is a prime number

想一想,如果没有else语句,如何实现?我是这样实现的,加一个flag标志来判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
>>> for n in range(2, 20):
...     flag = 0
...     for x in range(2, n):
...         if n % x == 0:
...             print(n, 'equals', x, '*', n//x)
...             flag = 1
...             break
...     if flag == 0:
...         print(n, 'is a prime number')
... 
2 is a prime number
3 is a prime number
4 equals 2 * 2
5 is a prime number
6 equals 2 * 3
7 is a prime number
8 equals 2 * 4
9 equals 3 * 3
10 equals 2 * 5
11 is a prime number
12 equals 2 * 6
13 is a prime number
14 equals 2 * 7
15 equals 3 * 5
16 equals 2 * 8
17 is a prime number
18 equals 2 * 9
19 is a prime number

这样看来,是不是觉得前者更 pythonic 一些?

注:原创文章,转载请注明出处及保留链接“Python时代”:http://www.pythonage.com/

本文链接地址:Python Tip 1:Python循环语句中的else语法(for else, while else)