学习笔记之盘一盘 Python 系列 1 & 2 - 入门篇
盘一盘 Python 系列 1 & 2 - 入门篇
- https://mp.weixin.qq.com/s?__biz=MzIzMjY0MjE1MA==&mid=2247486473&idx=1&sn=e9228958bb5c425b8981261f14bd2f8c&chksm=e8908f00dfe70616fb3b87db7fcd054738322acbbacb1a08dbd81d36ab2c41cc56b7cc146394&scene=21#wechat_redirect
- https://mp.weixin.qq.com/s?__biz=MzIzMjY0MjE1MA==&mid=2247486498&idx=1&sn=b5409de0fe394c49359eac9b67fb9ce4&chksm=e8908f2bdfe7063d2bb71587d621ff46f541f556e8a1abe828509bf3fef9144afd74580b47f8&scene=21#wechat_redirect
- 基础知识查漏补缺。
- 通过 dir( X ) 和help( X ) 可看出 X 对应的对象里可用的属性和方法。
- dir(int)
- getcontext() 显示了 Decimal 对象的默认精度值是 28 位 (prec=28)
- decimal.getcontext()
- 那保留 4 位呢?用 getcontext().prec 来调整精度哦。
- decimal.getcontext().prec = 4
- 高精度的 float 加上低精度的 float,保持了高精度,没毛病。
- 确定bool(X) 的值是 True 还是 False,就看 X 是不是空,空的话就是 False,不空的话就是 True。
- 对于数值变量,0, 0.0 都可认为是空的。
- 对于容器变量,里面没元素就是空的。
- 字符中常见的内置方法 (可以用 dir(str) 来查) 有
- capitalize():大写句首的字母
- split():把句子分成单词
- find(x):找到给定词 x 在句中的索引,找不到返回 -1
- replace(x, y):把句中 x 替代成 y
- strip(x):删除句首或句末含 x 的部分
- pattern = re.compile("'[0-9/:\s]+'")
- 这个抽象模式表达式 '[0-9/:\s]+',里面符号的意思如下:
- 最外面的两个单引号 ' 代表该模式以它们开始和结束
- 中括号 [] 用来概括该模式涵盖的所有类型的字节
- 0-9 代表数字类的字节
- / 代表正斜线
- : 代表分号
- \s 代表空格
- [] 外面的加号 + 代表 [] 里面的字节出现至少 1 次
- 这个抽象模式表达式 '[0-9/:\s]+',里面符号的意思如下:
- pattern.findall(input)
- 创建元组可以用小括号 (),也可以什么都不用,为了可读性,建议还是用 ()。此外对于含单个元素的元组,务必记住要多加一个逗号,举例如下:
- print( type( ('OK') ) ) # 没有逗号 , <class 'str'>
- print( type( ('OK',) ) ) # 有逗号 , <class 'tuple'>
- 看看,没加逗号来创建含单元素的元组,Python 认为它是字符。
- 当然也可以创建二维元组:
- nested = (1, 10.31, 'python'), ('data', 11) # ((1, 10.31, 'python'), ('data', 11))
- 元组有不可更改 (immutable) 的性质,因此不能直接给元组的元素赋值
- t = ('OK', [1, 2], True)
- t[2] = False # TypeError: 'tuple' object does not support item assignment
- 但是只要元组中的元素可更改 (mutable),那么我们可以直接更改其元素,注意这跟赋值其元素不同。
- t[1].append(3) # ('OK', [1, 2, 3], True)
- 元组大小和内容都不可更改,因此只有 count 和 index 两种方法。
- 元组拼接 (concatenate) 有两种方式,用「加号 +」和「乘号 *」,前者首尾拼接,后者复制拼接。
- (1, 10.31, 'python') + ('data', 11) + ('OK',) # (1, 10.31, 'python', 'data', 11, 'OK')
- (1, 10.31, 'python') * 2 # (1, 10.31, 'python', 1, 10.31, 'python')
- 解压 (unpack) 一维元组 (有几个元素左边括号定义几个变量)
- t = (1, 10.31, 'python')
- (a, b, c) = t
- print( a, b, c ) # 1 10.31 python
- 解压二维元组 (按照元组里的元组结构来定义变量)
- t = (1, 10.31, ('OK','python'))
- (a, b, (c,d)) = t
- print( a, b, c, d ) # 1 10.31 OK python
- 如果你只想要元组其中几个元素,用通配符「*」,英文叫 wildcard,在计算机语言中代表一个或多个元素。下例就是把多个元素丢给了 rest 变量。
- t = 1, 2, 3, 4, 5
- a, b, *rest, c = t
- print( a, b, c ) # 1 2 5
- print( rest ) # [3, 4]
如果你根本不在乎 rest 变量,那么就用通配符「*」加上下划线「_」,例子如下:
- a, b, *_ = t
- print( a, b ) # 1 2
- tuple优点缺点
- 优点:占内存小,安全,创建遍历速度比列表快,可一赋多值。
- 缺点:不能添加和更改元素。
- immutable 的好处实在是太多了:性能优化,多线程安全,不需要锁,不担心被恶意修改或者不小心修改。
- tuple v.s. list
- 创建速度,元组 (12.9ns) 碾压列表 (62ns)。
- 遍历速度两者相当,元组 (498 µs) 险胜列表 (507 µs)。
- 列表比元组稍微废点内存空间。
- 不像元组,列表内容可更改 (mutable),因此附加 (append, extend)、插入 (insert)、删除 (remove, pop) 这些操作都可以用在它身上。
- l.append([4, 3])
- print( l ) # [1, 10.31, 'python', [4, 3]]
- l.extend([1.5, 2.0, 'OK'])
- print( l ) # [1, 10.31, 'python', [4, 3], 1.5, 2.0, 'OK']
- 严格来说 append 是追加,把一个东西整体添加在列表后,而 extend 是扩展,把一个东西里的所有元素添加在列表后。对着上面结果感受一下区别。
- insert(i, x) 在编号 i 位置前插入 x。
- remove 和 pop 都可以删除元素
- 前者是指定具体要删除的元素,比如 'python'
- 后者是指定一个编号位置,比如 3,删除 l[3] 并返回出来
- print( l[::-1] )
- 注意最后把 step 设为 -1,相当于将列表反向排列。
- 和元组拼接一样, 列表拼接也有两种方式,用「加号 +」和「乘号 *」,前者首尾拼接,后者复制拼接。
- list优点缺点
- 优点:灵活好用,可索引、可切片、可更改、可附加、可插入、可删除。
- 缺点:相比 tuple 创建和遍历速度慢,占内存。此外查找和插入时间较慢。
- 字典里最常用的三个内置方法就是 keys(), values() 和 items(),分别是获取字典的键、值、对。
- 此外在字典上也有添加、获取、更新、删除等操作。
- 比如想看看腾讯的股价是多少 (两种方法都可以)
- print( d['Price'] )
- print( d.get('Price') )
- 比如去掉股票代码 (code)
- del d['Code']
- 或像列表里的 pop() 函数,删除行业 (industry) 并返回出来。
- print( d.pop('Industry') )
- 比如想看看腾讯的股价是多少 (两种方法都可以)
- 字典里的键是不可更改的,因此只有那些不可更改的数据类型才能当键,比如整数 (虽然怪怪的)、浮点数 (虽然怪怪的)、布尔 (虽然怪怪的)、字符、元组 (虽然怪怪的),而列表却不行,因为它可更改。
- 虽然怪怪的,但这些 2, 10.31, True, ('OK', 3) 确实能当键。有个地方要注意下,True 其实和整数 1 是一样的,由于键不能重复,当你把 2 该成 1时,你会发现字典只会取其中一个键,示例如下:
- d = {
- 1 : 'integer key',
- 10.31 : 'float key',
- True : 'boolean key',
- ('OK',3) : 'tuple key'
- }
- # d
- {1: 'boolean key',
- 10.31: 'float key',
- ('OK', 3): 'tuple key'}
- 那么如何快速判断一个数据类型 X 是不是可更改的呢?两种方法:
- 麻烦方法:用 id(X) 函数,对 X 进行某种操作,比较操作前后的 id,如果不一样,则 X 不可更改,如果一样,则 X 可更改。
- 便捷方法:用 hash(X),只要不报错,证明 X 可被哈希,即不可更改,反过来不可被哈希,即可更改。
- dictionary优点缺点
- 优点:查找和插入速度快
- 缺点:占内存大
- 集合有两种定义语法
- 第一种是
- {元素1, 元素2, ..., 元素n}
- 第二种是用 set() 函数,把列表或元组转换成集合。
- set( 列表 或 元组 )
- 第一种是
- 从A的结果发现集合的两个特点:无序(unordered)和唯一(unique)。由于set存储的是无序集合,所以我们没法通过索引来访问,但是可以判断一个元素是否在集合中。
- 用 set 的内置方法就把它当成是数学上的集,那么并集、交集、差集都可以玩通了。
- 并集 OR
- print( A.union(B) ) # All unique elements in A or B
- print( A | B ) # A OR B
- 交集 AND
- print( A.intersection(B) ) # All elements in both A and B
- print( A & B ) # A AND B
- 差集 A - B
- print( A.difference(B) ) # Elements in A but not in B
- print( A - B ) # A MINUS B
- 差集 B - A
- print( B.difference(A) ) # Elements in B but not in A
- print( B - A ) # B MINUS A
- 对称差集 XOR
- print( A.symmetric_difference(B) ) # All elements in either A or B, but not both
- print( A ^ B ) # A XOR B
- 并集 OR
- set优点缺点
- 优点:不用判断重复的元素
- 缺点:不能存储可变对象
- if-elif-else 语句
- 对于迭代循环,Python 里面有「while 循环」和「for 循环」,没有「do-while 循环」。
- 通用形式的 for loop 如下
- for a in A
- do something with a
- 其中 for 和 in 是关键词,A 是个可迭代数据 (list, tuple, dic, set),a 是 A 里面的每个元素
- for a in A
- 最后介绍一个稍微有点特殊的函数 enumerate(),和 for loop 一起用的语法如下
- for i, a in enumerate(A)
- do something with a
- 发现区别了没?用 enumerate(A) 不仅返回了 A 中的元素,还顺便给该元素一个索引值 (默认从 0 开始)。此外,用 enumerate(A, j) 还可以确定索引起始值为 j。
- languages = ['Python', 'R', 'Matlab', 'C++']
- for i, language in enumerate(languages, 1):
- print( i, 'I love', language )
- print( 'Done!' )
- # 1 I love Python
- 2 I love R
- 3 I love Matlab
- 4 I love C++
- Done!
- for i, a in enumerate(A)
- 数据类型分两种:
- 单独类型:整型、浮点型、布尔型
- 容器类型:字符、元组、列表、字典、集合
- Python 的函数具有非常灵活多样的参数形态。从简到繁的参数形态如下:
- 位置参数 (positional argument)
- 默认参数 (default argument)
- 可变参数 (variable argument)
- 关键字参数 (keyword argument)
- 命名关键字参数 (name keyword argument)
- 参数组合
- 位置参数
- arg1 - 位置参数 ,这些参数在调用函数 (call function) 时位置要固定。
- 默认参数
- arg2 = v - 默认参数 = 默认值,调用函数的时候,默认参数已经有值,就不用再传值了。
- 默认函数一定要放在位置参数后面,不然程序会报错。
- 在调用参数把它的「关键字」也带上,我们就可以随便调换参数的顺序。
- 可变参数
- *args - 可变参数,可以是从零个到任意个,自动组装成元组。
- 可变参数用两种方式传入
- 直接传入,func(1, 2, 3)
- 先组装列表或元组,再通过 *args 传入,func(*[1, 2, 3]) 或 func(*(1, 2, 3))
- 关键字参数
- **kw - 关键字参数,可以是从零个到任意个,自动组装成字典。
- 「可变参数」和「关键字参数」的同异总结如下:
- 可变参数允许传入零个到任意个参数,它们在函数调用时自动组装为一个元组 (tuple)
- 关键字参数允许传入零个到任意个参数,它们在函数内部自动组装为一个字典 (dict)
- 如果不传入任何「关键字参数」,kw 为空集。
- 除了直接传入多个参数之外,还可以将所有参数先组装成字典 Conv,用以「**Conv」的形式传入函数 (Conv 是个字典,前面加个通配符 ** 是拆散字典,把字典的键值对传入函数中)
1 # ============================================================================= 2 # id: MM1001 3 # notional: 10 4 # reporting currency: EUR 5 # present value: 150 6 # keyword: {'dc': 'act/365', 'bdc': 'following'} 7 # ============================================================================= 8 DCF = (1, 2, 3, 4, 5) 9 Conv = {'dc':'act/365', 'bdc':'following'} 10 instrument5( 'MM1001', 10, 'EUR', *DCF, **Conv )
- 命名关键字参数
- *, nkw - 命名关键字参数,用户想要输入的关键字参数,定义方式是在nkw 前面加个分隔符 *。
- 如果要限制关键字参数的名字,就可以用「命名关键字参数」
- 使用命名关键字参数时,要特别注意不能缺少参数名。
1 def instrument6( id, ntl=1, curR='CNY', *, ctp, **kw ): 2 print( 'id:', id ) 3 print( 'notional:', ntl ) 4 print( 'reporting currency:', curR ) 5 print( 'counterparty:', ctp ) 6 print( 'keyword:', kw) 7 8 # ============================================================================= 9 # id: MM1001 10 # notional: 100 11 # reporting currency: EUR 12 # counterparty: GS 13 # keyword: {'dc': 'act/365'} 14 # ============================================================================= 15 instrument6( 'MM1001', 100, 'EUR', 16 dc='act/365', ctp='GS' ) 17 18 # ============================================================================= 19 # TypeError: instrument6() takes from 1 to 3 20 # positional arguments but 4 were given 21 # ============================================================================= 22 instrument6( 'MM1001', 100, 'EUR', 23 'GS', dc='act/365' )
- 参数组合
- 在 Python 中定义函数,可以用位置参数、默认参数、可变参数、命名关键字参数和关键字参数,这 5 种参数中的 4 个都可以一起使用,但是注意,参数定义的顺序必须是:
- 位置参数、默认参数、可变参数和关键字参数。
- 位置参数、默认参数、命名关键字参数和关键字参数。
- 要注意定义可变参数和关键字参数的语法:
- *args 是可变参数,args 接收的是一个 tuple
- **kw 是关键字参数,kw 接收的是一个 dict
- 命名关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。定义命名关键字参数不要忘了写分隔符 *,否则定义的是位置参数。
- 警告:虽然可以组合多达 5 种参数,但不要同时使用太多的组合,否则函数很难懂。
- 在 Python 中定义函数,可以用位置参数、默认参数、可变参数、命名关键字参数和关键字参数,这 5 种参数中的 4 个都可以一起使用,但是注意,参数定义的顺序必须是:
- 匿名函数
- 在 Python 里有两种函数
- 用 def 关键词的正规函数
- 用 lambda 关键词的匿名函数
- lambda *args: sum(args);输入是任意个数的参数,输出是它们的和
- lambda **kwargs: 1;输入是任意键值对参数,输出是 1
- 对于 lambda 函数,有时我们会过用 (overuse) 它或误用 (misuse) 它。
- Misuse
- 误用情况:如果用 lambda 函数只是为了赋值给一个变量,用 def 的正规函数。
- <function <lambda> at 0x000001997AA721E0>
- <function sqr at 0x000001997AA72268>
- lbd_sqr 的返回值是以 <lambda> 标识的函数,而 sqr 的返回时是以 sqr 为标识的函数,明显后者一看就知道该函数是「计算平方」用的。
- 误用情况:如果用 lambda 函数只是为了赋值给一个变量,用 def 的正规函数。
- Overuse
- 过用情况:如果一个函数很重要,它需要一个正规的名字。
- sorted(colors, key=lambda c: (len(c), c.casefold()))
- 坦白的说,这样用 lambda 函数看起来是很酷,但是增加了使用者的「思考成本」,用 def 显性定义个函数可读性会好很多。
- 用正规函数还能加个函数说明 (docstring),再起个描述性强的函数名,让人一看就知道该函数做什么。
- 过用情况:如果一个函数很重要,它需要一个正规的名字。
- 在 Python 里有两种函数
- 高阶函数
- 高阶函数 (high-order function) 在函数化编程 (functional programming) 很常见,主要有两种形式:
- 参数是函数 (map, filter, reduce)
- 返回值是函数 (closure, partial, currying)
- Map, Filter, Reduce
- Python 里面的 map, filter 和 reduce 属于第一种高阶函数,参数是函数。
- map(函数 f, 序列 x):对序列 x 中每个元素依次执行函数 f,将 f(x) 组成一个「map 对象」返回 (可以将其转换成 list 或 set)
- filter(函数 f, 序列 x):对序列 x 中每个元素依次执行函数 f,将 f(x) 为 True 的结果组成一个「filter 对象」返回 (可以将其转换成 list 或 set)
- reduce(函数 f, 序列 x):对序列 x 的第一个和第二个元素执行函数 f,得到的结果和序列 x 的下一个元素执行函数 f,一直遍历完的序列 x 所有元素。
- map_iter = map( lambda x: x**2, lst ) # <map object at 0x0000018C83E72390>
- 注意 map_iter 是 map 函数的返回对象 (它是一个迭代器),想要将其内容显示出来,需要用 list 将其转换成「列表」形式。
- 惰性求值 (lazy evaluation) 也称为传需求调用 (call-by-need),目的是最小化计算机要做的工作。
- 在上例中,map 函数作用到列表,并不会立即进行求平方,而是当你用到其中某些元素时才去求平方。惰性是指,你不主动去遍历它,就不会计算其中元素的值。
- 为什么要有 「惰性求值」呢?在本例看起来毫无必要,但试想大规模数据时,一次性处理往往抵消而且不方便,而惰性求值解决了这个问题,它把计算的具体步骤延迟到了要实际用该数据的时候。
- 惰性序列可以看作是一个流 (flow),需要的时候从其中取一滴水。
- filter_iter = filter(lambda n: n % 2 == 1, lst)
- 同样,filter_iter 作为 filter 函数的返回对象,也是一个迭代器,想要将其内容显示出来,需要用 list 将其转换成「列表」形式。
- 最后来看看 reduce 函数,顾名思义就是累积函数,把一组数减少 (reduce) 到一个数。
- 在 reduce 函数的第三个参数还可以赋予一个初始值,这是累积从 100 和列表 lst = [1,2,3,4,5] 的第一个元素 1 开始,一直加到整个 lst 元素遍历完,因此最后求和为 115。
- 高阶函数 (high-order function) 在函数化编程 (functional programming) 很常见,主要有两种形式:
1 from functools import reduce 2 3 lst = [1, 2, 3, 4, 5] 4 5 reduce( lambda x,y: x+y, lst ) # 15 6 7 reduce( lambda x,y: x+y, lst, 100 ) # 115
-
-
- 小结一下,对于 map, filter 和 reduce,好消息是,Python 支持这些基本的操作;而坏消息是,Python 不建议你使用它们。下节的「解析式」可以优雅的替代 map 和 filter。
- 除了 Python 这些内置函数,我们也可以自己定义高阶函数,如下:
-
1 def apply_to_list( fun, some_list ): 2 return fun(some_list) 3 4 lst = [1, 2, 3, 4, 5] 5 6 print( apply_to_list( sum, lst ) ) # 15 7 8 print( apply_to_list( len, lst ) ) # 5 9 10 print( apply_to_list( lambda x:sum(x)/len(x), lst ) ) # 3.0
- 闭包
- Python 里面的闭包 (closure) 属于第二种高阶函数,返回值是函数。下面是一个闭包函数。
- 此函数的作用是做一个计数器,可以
- 用增加子函数 inc() 续一秒
- 用减少子函数 dec() 废一秒
- 用获取子函数 get() 看秒数
- 用重置子函数 reset() 回原点
- 属于第二类 (返回值是函数) 的高阶函数还有「偏函数」和「柯里化」
- Python 里面的闭包 (closure) 属于第二种高阶函数,返回值是函数。下面是一个闭包函数。
1 def make_counter(init): 2 counter = [init] 3 4 def inc(): counter[0] += 1 5 def dec(): counter[0] -= 1 6 def get(): return counter[0] 7 def reset(): counter[0] = init 8 9 return inc, dec, get, reset 10 11 inc, dec, get, reset = make_counter(0) 12 13 inc() 14 inc() 15 inc() 16 17 get() # 3
- 偏函数
- 偏函数 (paritial function) 主要是把一个函数的参数 (一个或多个) 固定下来,用于专门的应用上 (specialized application)。要用偏函数用从 functools 中导入 partial 包
- 我们知道 sort 函数默认是按升序排列,假设在你的应用中是按降序排列,你可以把函数里的 reverse 参数设置为 True。这样每次设定参数很麻烦,你可以专门为「降序排列」的应用定义一个函数,比如叫 sorted_dec,用偏函数 partial 把内置的 sort 函数里的 reverse 固定住,代码如下:
1 from functools import partial 2 3 lst = [3, 1, 2, 5, 4] 4 5 sorted( lst ) # [1, 2, 3, 4, 5] 6 7 sorted( lst, reverse=True ) # [5, 4, 3, 2, 1] 8 9 sorted_dec = partial( sorted, reverse=True ) 10 11 sorted_dec # functools.partial(<built-in function sorted>, reverse=True) 12 13 sorted_dec( lst ) # [5, 4, 3, 2, 1]
-
- 小结,当函数的参数个数太多,需要简化时,使用 functools.partial 可以创建一个新的函数,即偏函数,它可以固定住原函数的部分参数,从而在调用时更简单。
- 柯里化
- 最简单的柯里化 (currying) 指的是将原来接收 2 个参数的函数 f(x, y) 变成新的接收 1 个参数的函数 g(x) 的过程,其中新函数 g = f(y)。
- 通过嵌套函数可以把函数 add1 转换成柯里化函数 add2。
- 仔细看看函数 add1 和 add2 的参数
- add1:参数是 x 和 y,输出 x + y
- add2:参数是 x,输出 x + y
- g = add2(2):参数是 y,输出 2 + y
- 比较「普通函数 add1」和「柯里化函数 add2」的调用,结果都一样。
1 def add1(x, y): 2 return x + y 3 4 def add2(x): 5 def add(y): 6 return x + y 7 return add 8 9 add1 # <function __main__.add1(x, y)> 10 11 add2 # <function __main__.add2(x)> 12 13 g = add2(2) 14 g # <function __main__.add2.<locals>.add(y)> 15 16 print( add1(2, 3) ) # 5 17 18 print( add2(2)(3) ) # 5 19 20 print( g(3) ) # 5
- 解析式
- 大框架
- 解析式 (comprehension) 是将一个可迭代对象转换成另一个可迭代对象的工具。
- 上面出现了两个可迭代对象 (iterable),不严谨地说,容器类型数据 (str, tuple, list, dict, set) 都是可迭代对象。
- 第一个可迭代对象:可以是任何容器类型数据。
- 第二个可迭代对象:看是什么类型解析式:
- 列表解析式:可迭代对象是 list
- 字典解析式:可迭代对象是 dict
- 集合解析式:可迭代对象是 set
- 大框架
1 # list comprehension 2 [值 for 元素 in 可迭代对象 if 条件] 3 4 # dict comprehension 5 {键值对 for 元素 in 可迭代对象 if 条件} 6 7 # set comprehension 8 {值 for 元素 in 可迭代对象 if 条件}
-
- 列表解析式
-
- 其他解析式
-
-
- 再回顾下三种解析式,我们发现其实它们都可以实现上节提到的 filter 和 map 函数的功能,用专业计算机的语言说,解析式可以看成是 filter 和 map 函数的语法糖。
- 语法糖 (syntactic sugar):指计算机语言中添加的某种语法,对语言的功能没有影响,但是让程序员更方便地使用。
- 语法盐 (syntactic salt):指计算机语言中添加的某种语法,使得程序员更难写出坏的代码。
- 语法糖浆 (syntactic syrup):指计算机语言中添加的某种语法,没能让编程更加方便。
- 首先发现两者都是把原列表根据某些条件转换成新列表,再者
- 「列表解析式」用 if 条件来做筛选得到 item,再用 f 函数作用到 item 上。
- 「map/filter」用 filter 函数来做筛选,再用 map 函数作用在筛选出来的元素。
- 为了达到相同目的,明显「列表解析式」是种更简洁的方式。
- 再回顾下三种解析式,我们发现其实它们都可以实现上节提到的 filter 和 map 函数的功能,用专业计算机的语言说,解析式可以看成是 filter 和 map 函数的语法糖。
- 小例子
- 问题:用解析式将二维元组里每个元素提取出来并存储到一个列表中。如果我们想把上面「二维元组」转换成「二维列表」呢?
-
1 tup = ((1, 2, 3), (4, 5, 6), (7, 8, 9)) 2 3 flattened = [x for t in tup for x in t] 4 5 flattened # [1, 2, 3, 4, 5, 6, 7, 8, 9] 6 7 [ [x for x in t] for t in tup ] # [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
-
-
- 问题:用解析式把以下这个不规则的列表 a 打平 (flatten)?
- 用解析式一步到位解决上面问题有点难,特别是列表 a 不规则,每个元素还可以是 n 层列表,因此我们需要递推函数 (recursive function),即一个函数里面又调用自己。
- 正规 (递推) 函数写好了,把它写成匿名函数也很简单了。
- 问题:用解析式把以下这个不规则的列表 a 打平 (flatten)?
-
1 a = [1, 2, [3, 4], [[5, 6], [7, 8]]] 2 3 def f(x): 4 if type(x) is list: 5 return [y for l in x for y in f(l)] 6 else: 7 return [x] 8 9 f(a) # [1, 2, 3, 4, 5, 6, 7, 8] 10 11 12 a = [1, 2, [3, 4], [[5, 6], [7, 8]]] 13 14 f = lambda x: [y for l in x for y in f(l)] if type(y) is list else [x] 15 16 f(a) # [1, 2, 3, 4, 5, 6, 7, 8]
-
-
- 优雅清晰是 python 的核心价值观,高阶函数和解析式都符合这个价值观。
- 函数包括正规函数 (用 def) 和匿名函数 (用 lambda),函数的参数形态也多种多样,有位置参数、默认参数、可变参数、关键字参数、命名关键字参数。匿名函数主要用在高阶函数中,高阶函数的参数可以是函数 (Python 里面内置 map/filter/reduce 函数),返回值也可以是参数 (闭包、偏函数、柯里化函数)。
- 解析式并没有解决新的问题,只是以一种更加简洁,可读性更高的方式解决老的问题。解析式可以把「带 if 条件的 for 循环」用一行程序表达出来,也可以实现 map 加 filter 的功能。
- 最后用 Tim Peters 的 The Zen of Python 结尾。
- Beautiful is better than ugly.
- Explicit is better than implicit.
- Simple is better than complex.
- Complex is better than complicated.
- Flat is better than nested.
- Sparse is better than dense.
- Readability counts.
- Special cases aren't special enough to break the rules.
- Although practicality beats purity.
- Errors should never pass silently.
- Unless explicitly silenced.
- In the face of ambiguity, refuse the temptation to guess.
- There should be one -- and preferably only one -- obvious way to do it.
- Although that way may not be obvious at first unless you're Dutch.
- Now is better than never.
- Although never is often better than right now.
- If the implementation is hard to explain, it's a bad idea.
- If the implementation is easy to explain, it may be a good idea.
- Namespaces are one honking great idea -- let's do more of those!
-