Written by Python高效编程
应用
- 大文本查找单词并修改
- 重命名图片或者文件
- 小型数据库
- 特定目的的 GUI
- 简单的小游戏
流程:写程序/编译/测试/重编译 过程漫长
shell 适合移动、修改文件,但不适合用户界面或者游戏
c/c++/Java 耗时过长,即使是初稿
Python 使用起来更简单,跨平台,帮助你更快速地完成工作。
Python 虽然简单易用,但也是通用编程语言。相较于其他脚本语言,Python 提供了多得多的适合于大型项目的结构与支持。作为高级语言,Python 相较于 C 语言提供更多的错误检查,并且有高度封装的数据类型,比如灵活的矩阵和字典。Python 相较于 Awk 甚至是 Perl,事何更加复杂的领域,尽管许多方面使用起来差不多方便。
Python 允许你把程序分割成可以重复使用的模块,你可以将模块用在其他 Python 项目中。你还可以使用 Python 内置的标准模块,或者作为开始学习 Python 的范例。一些模块提供:文件读取写入,系统调用,sockets编程,甚至是用户界面接口工具 TK。
Python 是解释型语言,编译和linking是不必要的,在程序开发的时候,可以节省可观的时间。你可以通过交互式终端执行 Python 程序,因此很容易就可以实验语言的特性,写一些一次性的程序,或者在自下而上开发时测试函数。同时,Python 是容易使用的桌面计算器。
可扩展性强
非正式介绍
数值类型:浮点型,整型,复数(j or J) , 字符串
各种操作符 :// , / , * ,** ,%,+/-
先赋值,再使用
整型 + 浮点数 = 浮点数
交互模式 _ = ans
字符串
- ‘ . . . ’ or “ . . . ” 单引号或者双引号
- \ 可以忽略‘ “ it’s ok!” 或者 ‘ It'ok ’ 或者 ‘ yes “好的呢” ’
- 交互模式,输出字符以引号括起,特殊字符仍保留(\n)
- print 没有引号,没有特殊字符
- 不想
\
被解释为特殊字符,使用原生字符串(raw strings)在第一个引号前 - 字符串可以跨越多行。一种方式是使用三个引号
‘''...'’'
或者·“""...""”
- 也可以使用
\
注释 - +连*倍
-
‘Py’
‘thon'
连 - 当你想断开长字符,这个方法特别又要
- 只有个部分均为字符串文字(literals),而不是变量或者表达式
- 连接变量与文字 请使用
+
- 支持 index 0 起始
- 没有单独的字符类型(char),一个字符就是长度为1的字符串
- 支持负数下标,从右开始 -1
- 切片操作
- 索引获取单个字符,切片获取子字符串
- s = s[:i] + s[i:]
- 省略的第一个下标默认为 0
- 省略的第二个下标默认为字符串长度
- 标上序号记忆
P | y | t | h | o | n | |
---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 |
-6 | -5 | -4 | -3 | -2 | -1 |
- 左开右闭
- 索引不能超过大小
- 而切片却可以
- strings 常量不可修改(immutable:numbers,strings,tuples:(1,2))
- word[0] = ‘J’
- len(s) 返回字符串大小
列表
- 复合数据类型
- 写作方括号与逗号的组合
- 支持索引与切片
- [:] 浅拷贝
- 支持+*
- 可修改 cube(立方)
- append 后插
- 赋值 可以改变列表大小,甚至清空
- letters[2:5] = [‘d’,‘e’] or []
- 支持 len
- 嵌入子列表
更加复杂的任务
# 斐波那契数列
a, b = 0,1
while b < 10:
print(b)
a, b = b, a + b
- 多赋值 a,b = b,a simultaneously
- 表达式顺序
- 左右 = 首先执行
- while 一直执行,只要判断条件成立
- 类似 C,任何非零值都是成立的,0 是 False。
- 字符串、列表、任何不为空的序列。
- 各种比较符 与 C 类似
- 循环的主体是要缩进的,缩进是 Python 组成语句的方式。
- Tab 四个字符缩进
- print 打印多个参数,浮点数,字符串
- 字符串无引号,以逗号隔开的元素,打印时会用空格隔开
- end 参数用于分隔每个 print 打印的内容 ‘,’ 避免换行
- 优先级
if 语句
- 类似其他语言的条件语句
- if x < 0 :
- elif x == 0:
- else:
- elif 可以有 0 个,或是更多,else是可选的
- elif 是 else if 的缩写,避免了不必要的1缩进
- 可替代 switch case
for 语句
- 有些不同 相对于 C 和 Pascal
- C 中既定义了迭代条件和停止条件
- for 迭代 任何序列的元素(列表或者字符串),按照出现顺序
- words
- 如果迭代时你想修改序列值,推荐先做个拷贝
- 迭代并不会拷贝数值
- 切片隐式拷贝[:] 特别方便
- 不拷贝,会无限迭代,创建无穷列表
words = ['cat', 'window', 'defenestrate']
for w in words[:]:
if len(w) > 6:
words.insert(0, w)
range() 函数
- 迭代数字序列
- range(10) 序列大小为 10
- 合理的索引值为 10
- 不包含结束点,默认初始值为 0
- 可以设置间隔或者步长(甚至可以是负数)
- range(-10, -100, -30)
- 为了可以迭代序列的索引,可以同时使用 range 和 len
- enumerate()
- strange: print(range(10))
- range 似乎是个列表,实际上不是
它是一个当你迭代它时,返回迭代序列连续元素的对象。但是它并不生成列表,因此节省了空间。这样的对象是可迭代的,这种结构的特点是:你可以连续从中获取元素,直到没有为止。for 语句是迭代器,函数list()
也是迭代器,它可以从可迭代对象中创建列表
break continue else
- break 跳出最内层循环,只跳出一层循环
- else 子句
- 当 for 循环结束,执行else 或者 while 判断条件为 False
- 与 for 一起使用 类似于与 try 一起使用
- try : else 用于没有异常发生,for : else 用于没有 break 语句
for n in range(2, 10):
for x in range(2, n):
if n % x == 0:
print(n, 'equals', x, '*', n//x)
break
else:
# loop fell through without finding a factor
print(n, 'is a prime number')
- continue 也是从 C 中借用的语句,继续开始下一次的迭代
pass
- pass 语句什么都不做,当语法上需要但是不需要执行的时候
- While True : pass
- class MyEmptyClass : pass
- 函数占位,条件主体,允许你考虑更抽象的水平
定义函数
def fib(n):
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
print()
- 创建斐波那契函数到任意边界
- def 关键字用于定义函数 + 函数名 + (参数)
- 语句形成了函数的主体。必须被缩进
- 函数主体的第一条语句可以是字符串
- 字符串是函数的说明文档(docstring)
- 养成说明的习惯
- 函数的执行引进了新的符号表,局部变量
更准确地说,在函数中分配的变量,储存在局部符号表格中(locak symbol table)
变量引用查找局部变量符号表格,然后是封闭函数的局部符号表格,接着是全局符号表格,最后是内置函数名。因此,全局变量不能直接在函数中直接赋值给全局变量(除非使用 global 语句),尽管我们可以引用全局变量。
函数的实参,在被调用函数的局部符号表格中。因此函数传递参数的引用,而不是参数的值。数值不会被改变(常量),列表会被改变。
当函数调用另一个函数,这次调用会创建新的局部符号表格。
函数定义在当前符号表格中引进了新的函数名。函数名的值的类型被解释器当成用户定义函数。这个值可以分配其他名字,我们可以通过其他名字调用这个函数,这就是重命名机制的原理:
fib
f = fib
f(100)
函数没有 return 语句也会返回值,return None
,None 被解释器抑制了。如果它是唯一写入的值,对于交互模式解释器,使用print
。
- return 语句从函数中返回数值,return 后面没有表达式参数的话,会返回 None。
- ulist.append 被称为 list 对象 ulist 的方法。这个方法是属于一个对象的函数,被命名为 obj.methodname,其中 obj 是某些对象(也可能是表达式)。而 methodname 是由对象类型确定的方法名。不同的对象确定不同的方法。不同对象的方法可能会有相同的方法名,却不会引起歧义。你可以是由 class 定义你自己的对象类型。append 正是 list 对象的方法,它在 list 的末尾增加一个新元素,这等同于 result = result + [a],但是更加高效。
更多关于定义函数的知识
默认参数值
- 设定默认值
- def ask_ok(prompt, retries=4, reminder=‘again’)
- 只给一个强制参数
- 给出一个可选参数
- 给出全部参数
- in 用于判断一个序列是否有某个值
- 默认值在函数定义前给出
i = 5
def f(arg=i):
print(arg)
i = 6
f()
# 打印 5
警告:
初始值只被计算一次。这使得当初始值是可变对象(列表,字典,或者大多数类),会有一些不同。举个例子,下面的函数会累计传递给序列的参数。
def f(a, L=[]):
L.append(a)
return L
print(f(1))
print(f(2))
print(f(3))
输出:
[1]
[1, 2]
[1, 2, 3]
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
关键字(Keyword Arguments)
通过使用关键字参数(kwarg=value),我们可以调用函数。
- 关键字参数 必须在位置参数之后
- 非关键字参数要在关键字参数之前
- 正确:12, state = ‘jump’
- 错误:voltage = 12, ‘jump’
- 每个参数不能重复接受值
*arguments **keywords
- **name 接受字典 keyword = value return dict
- *name 接受元组包含所有的位置参数,超出正式参数列表
- *name 必须在 **name 的前面
def f(*age,**name):
print(age)
print(name)
f(1,4,'sd',key = 'sd',time = '12')
任意参数列表
- 最后,最不常用的的选项可以用任意数目参数,这些参数将会被包裹在元组中,零个或者多个参数都可以。
- 可变参数(variadic),必须在正式参数的最后,因为它们接受剩余所有传递给函数的输入参数
- 任何在 *arg 后面的参数,必须是“keyword-only”参数,也就是说,必须以关键字的形式而不是位置参数形式使用关键字
def concat(*args, sep="/"):
print(sep.join(args))
concat("earth", "mars", "venus", sep=".")
concat("earth", "mars", "venus")
解包函数参数列表
相反的情况,就是参数已经在列表或者元组中了,但是函数调用需要分离的位置参数,这是我们就要将列表或者元组解包。举个例子,内置函数 range 需要 start 和 stop 参数。如果这些参数不能被分别得到,我们就要用到 * 操作符进行解包来获取列表或者元组中的参数。相似的,我们可以使用 **dict 来获取字典中元素。
a = {'name':'xkk', 'age':18, 'hobby': 'read'}
f(**a)
lambda 表达式
小型匿名(anonymous)函数,可以使用关键字 lambda。这个函数返回两个关键字的和:lambda a, b: a+b。lambda 函数可以在需要函数对象的地方使用。在语法上,它被限制为单个表达式。从语义上讲,它们正常定义函数的语法糖(syntactic sugar),类似于内置函数定义,lambda 函数可以引用包含范围内的变量。
>>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
>>> pairs.sort(key=lambda pair: pair[1])
>>> pairs
[(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]
文档说明
下面是一些关于说明文档内容与格式的惯例。
第一行因该是对于对象目标简短而准确的介绍。简单地说,它不应该明确说明对象的名字和类型,因为这些可以通过其他方式介绍,除非对象名碰巧是描述函数操作的动词。这一行应该以大写字母开头,以句号结束。(end with a period)
如果说明文档有多行,那么第二行应该是空白行,视觉上分割总结与其余部分。接下来的内容,应该是一段或者更多段落,描述调用惯例和副作用(side effects)。
Python 解释器并不会从多行字符文字中去除缩进。所以如果需要的话,处理文本的工具必须去除缩进。我们通过以下惯例实现。第一个行之后的第一个非空行确定整个文本的缩进数目。(我们不能使用第一行,因为它通常与字符串的开头引号相邻,所以它的缩进在文本中并不明显)。所有行的字符串的开头被去除与之相同的空白。缺少缩进的行不应该出现,但是所有行领先的空白应该被去除。应在扩展标签后测试空格的有效性。
>>> def my_function():
... """Do nothing, but document it.
...
... No, really, it doesn't do anything.
... """
... pass
...
>>> print(my_function.__doc__)
Do nothing, but document it.
No, really, it doesn't do anything.
功能注释(Function Annotations)
功能注释关于用户定义函数使用的类型的可选元数据信息。
注释作为字典存储在函数的__annotations__
属性中,对函数的其他任何部分没有任何影响。功能注释被参数名后面的冒号所定义,后面可以用表达式表示参数值。返回注释有 –>
定义,后面是表达式,在参数列表与表示 def 语句结束的冒号之间。下面的例子有一个位置参数,一个关键字参数,并且返回值被注释了。
def f(ham: str, eggs: str = 'eggs') -> str:
print("Annotations:", f.__annotations__)
print("Arguments:", ham, eggs)
return ham + ' and ' + eggs
f('spam')
间奏曲(intermezzo):代码风格
- 既然你准备写更长、更复杂的 Python 代码,我们很有必要来讨论代码的风格。
- 大多数语言可以用多种风格表示,更准确地说格式化。
- 一些的可读性较强,使你的代码让别人轻易地读出来总是一个好主意,采用一种比较好地代码风格非常有帮助。
- 对于 Python 来说,PEP 8 已经成为大多数项目接受的指导风格,它提出一种可读性强,赏心悦目的代码风格。每一个 Python 开发者都应该在某个时刻读一下指南,下面是为你归纳出来的最重要的几点:
- 使用 4 个空格的缩进,而不是 tabs
四个空格在大小缩进得到了平衡(缩进短允许更大的嵌套深度,缩进长更易于阅读),Tabs 会导致误解,最好被省略。
- 每一行不超过 79 个字符
这有助于用户使用小型显示器,并且实现了在较大显示器并排显示代码文件
- 使用空行去分隔函数与类和函数中较大的代码块
- 尽可能在每一行写下他们的注释
- 使用说明文档、
- 在操作符和之后之后使用空格,但是不直接包围结构:a = f(1, 2) + g(3, 4)
- 命名你的类与函数要一致,惯例是使用驼峰(CameCase)命名法命名类,小写字母和下标命名函数与方法。总是使用 self 作为方法的第一个参数。
- 不要使用花哨的编码如果你的代码要用在国际环境的话。Python 默认使用 UTF-8 编码,或者纯 ASCII 都能工作得很好。
- 同样,不要在鉴定符使用非 ASCII 字符,如果只有最轻微的可能会有不同语言的人阅读与维护代码的话。
数据结构
列表
- list.append(x)
在列表末尾增加元素。等同于 a[len(a):] = [x]
- list.extend(iterable)
扩展列表通过追加来自可迭代对象中的元素。等同于a[len(a):] = iterable
- list.insert(i, x)
在给定位置插入元素。第一个参数要插入元素的索引。所以a,insert(0, x)
在列表的最前面插入数据,而a.insert(len(a), x)
则等同于 a.append(x)
- list.remove(x)
删除列表中第一次出现的值为 x 的数。如果没有这个元素,会返回错误。
- list.pop([i])
删除列表中给定位置的元素,然后返回这个元素。如果没有说明索引,a.pop()
会删除并返回列表中的最后一个元素。(方法中 i 周围的方括号说明参数是可选参数,不是你应该在那个位置敲上方括号,你将会看到这个记号在 Python 参考库频繁地出现)
- list.clear()
从列表中删除全部的元素,等同于 del a[:]
。a[:]相当于是列表a 的浅拷贝,删除a[:]就是删除列表a中所有元素。如果使用 del a
,就相当于删除列表a。
- list.index(x[,start[,end]])
返回在以零为列表首元素索引的列表中,值为 x 的元素的索引。如果没有该元素,会产生 ValueError。
可选参数 start 和 end 用于限定搜索的子序列的开始与结束点。被返回的索引是相对于完整序列的开头而不是由 start 参数计算出来的。
- list.count(x)
返回列表中 x 出现的次数。0:0
- list.sort(key=None,reverse=False)
给列表中的元素排序。
- list.reverse()
逆序。
- list.copy()
返回 list 的浅拷贝,就相当于 a[:]
你可能注意到了,像insert
,remove
,sort
之类的方法只修改 list 中的元素,不返回打印的值------他们只返回 None
。这是 Python 中所有可变数据结构的设计理念。不支持 list.insert(0, 12).remove() 之类的连续操作。
作为栈使用
list 的方法使得我们非常轻松就可以把列表当作栈使用,栈中最后被添加的元素是第一个出栈的元素。在栈的顶部添加元素,使用 append。从栈的顶部取走元素,使用 pop() 方法
作为队列使用
我们也可以将列表当作队列使用,其中先进先出。但是,对于这种情况,列表并不是很高效。尽管在 list 末尾进行的 append 和 pop 操作是快速的,但是在列表的头部插入和删除数据是缓慢的。因为其他的元素要被一个个的移动。
为了补全队列,使用·collections.deque
,这种方法被设计快速而高效地从两端删除与添加元素。
from collections import deque
queue = deque([1,2,3])
queue.append(0)
queue.appendleft(8)
列表推导式
列表推导式提供了一种更简洁的方式创建列表。这常用于创建怎样的新列表呢?通常是列表中的元素是对其他序列或者可迭代变量的成员进行一系列操作的结果。或者在一定条件下,创建这些元素的子序列。
squares = [x**2 for x in range(10)]
更加简洁,可读性更强
列表推导式方括号中的表达式后面可以加上 for 语句,然后 0 个或者更多的 for 或者 if 语句。结果将会是一个新列表,通过 for 或者 if 语句限定得到的表达式
[(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
表达式是元组必须括起来
# flatten
vec = [[1,2,3], [4,5,6], [7,8,9]]
[num for elem in vec for num in elem]
嵌套列表推导式
列表推导式的第一个表达式可以是任意的表达式,包括另一个列表推导式。
[[row[i] for row in matrix] for i in range(4)]
list(zip(*matrix))
del 语句
- 有一种方式从列表中移除数值基于他的索引而不是基于他的数值
- 这不同于 pop 方法会返回删除的值
- del 语句也被用于从列表中移除切片或者整个列表
- del 也可以删除整个变量,这时再引用变量名 a 就会产生错误(至少直到它被赋予新的值)
元组与序列
我们看到列表与字符串有许多共同的性质,比如索引与切片操作。他们都是序列数据类型。由于 Python 是一种不断发展的语言,其他序列数据类型也可能被增加。Python 中存在其他标准序列数据类型:元组。
元组是由逗号隔开的值,是不可改变量(immutable),但是可以包含列表等可变对象。
正如你看到的,输出元组总是被括号包围,所以嵌套元组总会被正确地解释,尽管通常括号是不必要的(如果元组是更大表达式的一部分)。不可能给元组单个元素赋值,但是我们可以创建包含可变对象的元组。
尽管元组看上去与列表类似,但他们通常被用于不同的场合为了不同的目的。元组是不可更改的,通常包含不同类型的序列,这些序列可以通过解包或者索引来获取。列表是可变对象,列表的元素通常是同类型的,可以通过迭代来获取元素。(元组也可以)
一个特殊的问题是包含一个或者零个元素的元组构造:语法有特定的处理来适应这些。空元组由一对空括号组成,只有一个元素的元组,构造为:数值加上逗号(将一个值括起来是不够的)。很丑,但是有效。
解包:t = 1, 2, 3 x, y, z = t
序列解包,适用于右边所有序列。序列解包要求,左边的变量个数要与右边序列元素个数相同。注意多变量赋值只是先将序列打包再解包的过程。
集合
- 无序性
- 互异性
- 成员资格测试和去重
- 支持数学操作:并,交,补,对等差分(symmetric difference)
- 大括号或者 set 函数用于产生集合。
- 空集,你必须使用 set() 函数,空的大括号{}会创建空子典
- fast membership testing : elem in set
- a - b in a not in b
- a ^ b in a or b but not both
- 和列表推导式类似,集合也支持推导式
a = {x for x in ‘abracadabra’ if x not in ‘abc’ }
字典
Python 内置的另一个有用的数据类型是字典。字典有时在其他语言中被认为是“associative memories” 或者是“associative arrays”。不像序列可以被一些数字索引,字典是通过关键字索引,关键字可以是任何不可变量类型。字符串与数字总是可以作为关键字。元组可以被用作关键字,如果他们只包含字符串、数字或者元组。如果一个元组直接或者间接地包含任何可变对象,那么他不可以用作关键字。你不能使用列表作为关键字,因为我们可以使用索引赋值、切片赋值或者 append、extend 等方式修改列表元素。
我们最好认为字典是键值对的集合,前提条件是关键字都是互异的。空的大括号创建空子典。在大括号中使用逗号隔开键值对可以初始化字典,这也是字典在输出上的写法。
字典的主要操作是存储一些关键字并且基于关键字提取数值。我们也可以通 del 来删除键值对。如果你对一个已经存在的关键字赋值,那么旧的关键字对应的值将会被覆盖。如果你使用不存在的关键字,程序就会报错。
执行 list(d) 会按原有顺序返回字典中的所有关键字(如果你只想排序的话,使用 sorted(d) 代替)。为了检验单个关键字是否在字典中,我们可以使用 in 这个关键字。
dict() 可以从其他包含键值对的序列构造字典。
dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
dict([['sape', 4139], ['guido', 4127], ['jack', 4098]])
字典推导式也支持任意的键值对表达式
{x: x**2 for x in (2, 4, 6)}
如果关键字是简单字符串,有时使用关键字参数来说明键值对更加方便。
循环技巧
当迭代字典时,我们通过 items() 方法可以同时取出关键字与对应值。
迭代序列,使用 enumerate(),我们可以同时得到位置索引和对应值。
为了同时迭代两个或者更多的序列,我们可以使用 zip() 函数。
为了以相反顺序迭代,我们可以使用 reversed() 函数 a[::-1]
为了按照顺序迭代序列,使用 sorted() 函数。该函数返回一个新的有序序列,并保持源头(被拷贝的序列)不变。
当你迭代一个列表时,你很有可能改变了列表,所以更简单、更安全的方式是再新建一个列表。