简介
python 是一种简单优雅的脚本语言,包含非常完善的基础代码库, 覆盖网络、文件、GUI、 数据库、文本等, 除了内置库以外还有大量第三方库。
python 的适用范围: 网络应用、脚本任务和其它自动化工具
安装
python 是跨平台的,解释型的脚本语言(似乎脚本语言都是解释型和跨平台的)
python 有 2.x 和 3.x 两个版本,较多的应用都是基于 2.x 开发的; 安装 python 就是安装 python 的解释器, 得到一个命令行交互环境.
- mac
brew install python3
- ubuntu
sodu apt-get install python3
- window 直接到官网下载对应安装包
安装后将 python 安装目录,添加到环境变量, 然后打开命令行,输入 python , 进去交互环境
注意 windows 用户在 git bash 下,输入 python,不能进入到 python 交互环境, 执行 winpty python 进入交互环境
在命令行中输入 python 进入交互模式
1 | python |
执行 python 脚本文件, 命令行下python hello.py
开始菜单可以找到 python 专用 CLI
python 解释器
python 代码文件以.py
为后缀,由 python 解释器解释执行。
python 有多种解释器:
- CPython 官方解释器,C 语言开发的,所以叫 CPython, 命令行下输入 python 就是进入 CPython 解释器环境
- IPython 增强版的 CPython
- PyPy 执行速度较快
- Jython 把 python 编译成 java 字节码执行
- IronPython 运行在.NET 平台的 pyton 解释器
命令行交互环境
可在命令行交互环境执行,或保存为 hello.py 文件
1 | python |
输出
1 | print('hello python') |
输入
1 | name = input('input your name:') # 然后输入 'sindy' python3 |
语法
python 的语法比较简单:
- 用缩进表示代码块; 不用括号,花括号划分代码块
- 不用分号表示语句结束
- 标识符大小写敏感
- 声明变量不需要 var 等关键字
1 | # print absolute value |
数据类型和变量
python 的数据类型包括: 整数、浮点数、字符串、字节型、布尔值、空值、列表、字典和自定义类型
1 | 1, 100, -80, 0xfa # 整数 |
python 是弱类型(动态类型)语言
1 | a = 100 # 整数 |
python 中常量习惯用全大些字母表示, 如 PI=3.1415
, 并非真正意义的常量(同 JS)
python 的两种除法:
1 | print(10 / 3) #3.33.. 普通除法 结果为浮点数 |
python 的整数和浮点数没有大小限制
字符串和编码
字符编码
因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才能处理。最早的计算机在设计时采用 8 个比特(bit)作为一个字节(byte)
ASCII 1 个字节,最大 255
由于计算机是美国人发明的,因此,最早只有 127 个字符被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为 ASCII 编码,比如大写字母 A 的编码是 65,小写字母 z 的编码是 122
GB2312
处理中文显然一个字节是不够的,至少需要两个字节,而且还不能和 ASCII 编码冲突,所以,中国制定了 GB2312 编码,用来把中文编进去
Unicode 最常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要 4 个字节)
全世界有上百种语言,日本把日文编到 Shift_JIS 里,韩国把韩文编到 Euc-kr 里,各国有各国的标准,就会不可避免地出现冲突,结果就是,在多语言混合的文本中,显示出来会有乱码 因此,Unicode 应运而生。Unicode 把所有语言都统一到一套编码里,这样就不会再有乱码问题了
UTF-8 用于存储和传输 英文 1 个字节 中文 3 个字节
统一成 Unicode 编码,解决了乱码问题;但是,如果你写的文本基本上全部是英文的话,用 Unicode 编码比 ASCII 编码需要多一倍的存储空间,在存储和传输上就十分不划算。所以,本着节约的精神,又出现了把 Unicode 编码转化为“可变长编码”的 UTF-8 编码。UTF-8 编码把一个 Unicode 字符根据不同的数字大小编码成 1-6 个字节,常用的英文字母被编码成 1 个字节,汉字通常是 3 个字节,只有很生僻的字符才会被编码成 4-6 个字节。如果你要传输的文本包含大量英文字符,用 UTF-8 编码就能节省空间:
在计算机内存中,统一使用 Unicode 编码,当需要保存到硬盘或者需要传输的时候,就转换为 UTF-8 编码。
用记事本编辑的时候,从文件读取的 UTF-8 字符被转换为 Unicode 字符到内存里,编辑完成后,保存的时候再把 Unicode 转换为 UTF-8 保存到文件
浏览网页的时候,服务器会把动态生成的 Unicode 内容转换为 UTF-8 再传输到浏览器, 所以你看到很多网页的源码上会有类似<meta charset="UTF-8" />
的信息,表示该网页正是用的 UTF-8 编码。
Python 的字符串
在最新的 Python 3 版本中,字符串是以 Unicode 编码的,也就是说,Python 的字符串支持多语言
1 | print('包含中文的str') |
由于 Python 的字符串,在内存中以 Unicode 表示,一个字符对应若干个字节。如果要在网络上传输,或者保存到磁盘上,就需要把 str 变为以字节为单位的 bytes
用带 b 前缀的单引号或双引号表示
bytes
类型
1 | strVal = 'Hello' |
以 Unicode 表示的 str 通过 encode()方法可以编码为指定的 bytes
1 | print('ABC'.encode('ascii')) # b'ABC' |
反过来,如果我们从网络或磁盘上读取了字节流,那么读到的数据就是 bytes。要把 bytes 变为 str,就需要用 decode()方法:
1 | b'ABC'.decode('ascii') |
len()函数计算的是 str 的字符数,不是字符串占用的字节数,如果换成 bytes,len()函数就计算字节数
1 | len('ABC') |
可见,1 个中文字符经过 UTF-8 编码后通常会占用 3 个字节,而 1 个英文字符只占用 1 个字节
由于 Python 源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为 UTF-8 编码。当 Python 解释器读取源代码时,为了让它按 UTF-8 编码读取,我们通常在文件开头写上这两行:
1 | #!/usr/bin/env python3 ## 告诉Linux/OS X系统,这是一个Python可执行程序,Windows系统会忽略这个注释; |
格式化字符串%s
表示用字符串替换,%d
表示用整数替换,有几个%?
占位符,后面就跟几个变量或者值,顺序要对应好。如果只有一个%?
,括号可以省略。
在 Python 中,采用的格式化方式和 C 语言是一致的,用%实现
常见的占位符有:
占位符 | 替换内容 |
---|---|
%d | 整数 |
%f | 浮点数 |
%s | 字符串 |
%x | 十六进制整数 |
1 | # %d 整数 %f浮点数 %g 整数或科学计数法显示 |
format()
它会用传入的参数依次替换字符串内的占位符{0}、{1}……
1 | 'Hello, {0}, 成绩提升了 {1:.1f}%'.format('小明', 17.125) |
列表和元组
列表是元素的有序集合,可增删元素 等同 js 的数组, 且元素不要求数据类型相同
1 | roles = ['nami', 'zoro', 'lufy'] |
tuple 和 list 很相似,不过元组的元素是不可修改的, 所以元组没有append()
, insert()
之类的方法
1 | colors = ('yellow', 'green', 'red') |
条件判断
1 | age = 3 |
循环
1 | names = ['Michael', 'Bob', 'Tracy'] |
字典 dict
dict 全称 dictionary,在其他语言中也称为 map,使用键-值(key-value)存储,具有极快的查找速度。 同 js 的对象
1 | role = {'name': 'rufy', 'age': 18, 'skill': 'stretch'} # 属性名的引号不能省略 |
在 Python 代码中几乎无处不在,正确使用 dict 非常重要,需要牢记的第一条就是 dict 的 key 必须是不可变对象。
这是因为 dict 根据 key 来计算 value 的存储位置,如果每次计算相同的 key 得出的结果不同,那 dict 内部就完全混乱了。这个通过 key 计算位置的算法称为哈希算法(Hash)。
要保证 hash 的正确性,作为 key 的对象就不能变。在 Python 中,字符串、整数等都是不可变的,因此,可以放心地作为 key。而 list 是可变的,就不能作为 key
集合 set
set 是无序不重复的元素集合
1 | s = set([1,2,3]) |
set 和 dict 的唯一区别仅在于没有存储对应的 value,但是,set 的原理和 dict 一样,所以,同样不可以放入可变对象,因为无法判断两个可变对象是否相等,也就无法保证 set 内部“不会有重复元素”。试试把 list 放入 set,看看是否会报错。
函数
Python 内置了很多有用的函数,我们可以直接调用
1 | >>>help(abs) # 查看函数帮助 |
参数组合
在 Python 中定义函数,可以用必选参数、默认参数、可变参数、关键字参数和命名关键字参数,这 5 种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数、默认参数、可变参数、命名关键字参数和关键字参数。
1 | def f1(a, b, c=0, *args, **kw): |
Python 的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
要注意定义可变参数和关键字参数的语法:
*args 是可变参数,args 接收的是一个 tuple;
**kw 是关键字参数,kw 接收的是一个 dict。
以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3),又可以先组装 list 或 tuple,再通过args 传入:func((1, 2, 3));
关键字参数既可以直接传入:func(a=1, b=2),又可以先组装 dict,再通过kw 传入:func({‘a’: 1, ‘b’: 2})。
使用*args 和**kw 是 Python 的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。
递归函数
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出
1 | def fact(n): |
解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。
尾递归是指,在函数返回的时候,调用自身本身,并且,return 语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。
上面的 fact(n)函数由于 return n * fact(n - 1)引入了乘法表达式,所以就不是尾递归了。要改成尾递归方式,需要多一点代码,主要是要把每一步的乘积传入到递归函数中:
1 | def fact(n): |
遗憾的是,大多数编程语言没有针对尾递归做优化,Python 解释器也没有做优化,所以,即使把上面的 fact(n)函数改成尾递归方式,也会导致栈溢出。
尾递归事实上和循环是等价的,没有循环语句的编程语言只能通过尾递归实现循环。
1 | # |
高级特性
切片
1 | L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'] |
迭代
如果给定一个 list 或 tuple,我们可以通过 for 循环来遍历这个 list 或 tuple,这种遍历我们称为迭代(Iteration)。
Python 的 for 循环抽象程度要高于 C 的 for 循环,因为 Python 的 for 循环不仅可以用在 list 或 tuple 上,还可以作用在其他可迭代对象上。
1 | d = {'a': 1, 'b': 2, 'c': 3} |
当我们使用 for 循环时,只要作用于一个可迭代对象,for 循环就可以正常运行,而我们不太关心该对象究竟是 list 还是其他数据类型
1 | # 判断对象是否可迭代 |
列表生成式
列表生成式即 List Comprehensions,是 Python 内置的非常简单却强大的可以用来创建 list 的生成式
1 | list(range(1, 11)) # [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] |
生成器
要创建一个 generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个 generator: 同 es6 的 generator
通过 next()函数获得 generator 的下一个返回值
1 | L = [x * x for x in range(10)] |
generator 是非常强大的工具,在 Python 中,可以简单地把列表生成式改成 generator,也可以通过函数实现复杂逻辑的 generator。
迭代器
用于 for 循环的数据类型有以下几种:
- 一类是集合数据类型,如 list、tuple、dict、set、str 等;
- 一类是 generator,包括生成器和带 yield 的 generator function。
1 | from collections import Iterable |
生成器都是 Iterator 对象,但 list、dict、str 虽然是 Iterable,却不是 Iterator。
把 list、dict、str 等 Iterable 变成 Iterator 可以使用 iter()函数:
1 | isinstance(iter([]), Iterator) // True |
你可能会问,为什么 list、dict、str 等数据类型不是 Iterator?
这是因为 Python 的 Iterator 对象表示的是一个数据流,Iterator 对象可以被 next()函数调用并不断返回下一个数据,直到没有数据时抛出 StopIteration 错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过 next()函数实现按需计算下一个数据,所以 Iterator 的计算是惰性的,只有在需要返回下一个数据时它才会计算。
凡是可作用于 for 循环的对象都是 Iterable 类型;
凡是可作用于 next()函数的对象都是 Iterator 类型,它们表示一个惰性计算的序列;
集合数据类型如 list、dict、str 等是 Iterable 但不是 Iterator,不过可以通过 iter()函数获得一个 Iterator 对象。
Python 的 for 循环本质上就是通过不断调用 next()函数实现的
1 | for x in [1, 2, 3, 4, 5]: |
函数式编程
我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元
而函数式编程(请注意多了一个“式”字)——Functional Programming,虽然也可以归结到面向过程的程序设计,但其思想更接近数学计算
对应到编程语言,就是越低级的语言,越贴近计算机,抽象程度低,执行效率高,比如 C 语言;越高级的语言,越贴近计算,抽象程度高,执行效率低,比如 Lisp 语言。
函数式编程就是一种抽象程度很高的编程范式, 函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数!
Python 对函数式编程提供部分支持。由于 Python 允许使用变量,因此,Python 不是纯函数式编程语言。
纯函数: 相同的输入,就会得到相同的输出,对外部没有副作用的函数
高级函数
变量可以指向函数abs(-10)
是函数调用,而abs
是函数本身; 函数本身也可以赋值给变量,即:变量可以指向函数。
1 | f = abs |
函数名也是变量
函数名其实就是指向函数的变量
1 | abs = 10 |
注:由于 abs 函数实际上是定义在 import builtins 模块中的,所以要让修改 abs 变量的指向在其它模块也生效,要用 import builtins; builtins.abs = 10。
传入函数
可以接受函数作为参数的函数,称为高阶函数
1 | def add(x, y, f): |
函数作为返回值
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
1 | def lazy_sum(*args): |
当 lazy_sum 返回函数 sum 时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力
闭包
当一个函数返回了一个函数后,其内部的局部变量还被新函数引用
1 | def count(): |
匿名函数
在 Python 中,对匿名函数提供了有限支持, 匿名函数有个限制,就是只能有一个表达式,不用写 return,返回值就是该表达式的结果 比 js 的匿名函数弱
1 | list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])) |
装饰器
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数; 函数对象有一个name属性,可以拿到函数的名字
1 | def now(): |
不修改 now()函数的定义,又可以代码运行期间动态增加功能,称之为“装饰器”(Decorator)。
本质上,decorator 就是一个返回函数的高阶函数
1 | def log(func): |
把@log
放到now()
函数的定义处,相当于执行了语句 now=log(now)
1 | # 可接受参数的装饰器 |
在面向对象(OOP)的设计模式中,decorator 被称为装饰模式。OOP 的装饰模式需要通过继承和组合来实现,而 Python 除了能支持 OOP 的 decorator 外,直接从语法层次支持 decorator。Python 的 decorator 可以用函数实现,也可以用类实现。
decorator 可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。
偏函数
在介绍函数参数的时候,我们讲到,通过设定参数的默认值,可以降低函数调用的难度。而偏函数也可以做到这一点
1 | # int(num, base) |
当函数的参数个数太多,需要简化时,使用functools.partial
可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。
模块
为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少,很多编程语言都采用这种组织代码的方式。在 Python 中,一个.py 文件就称之为一个模块(Module)。
模块化有什么好处?
- 最大的好处是大大提高了代码的可维护性。
- 其次,编写代码不必从零开始。当一个模块编写完毕,就可以被其他地方引用。我们在编写程序的时候,也经常引用其他模块,包括 Python 内置的模块和来自第三方的模块。
- 使用模块还可以避免函数名和变量名冲突。
你也许还想到,如果不同的人编写的模块名相同怎么办?为了避免模块名冲突,Python 又引入了按目录来组织模块的方法,称为包(Package)
包作为模块的命名空间,避免模块名冲突
请注意,每一个包目录下面都会有一个__init__.py
的文件,这个文件是必须存在的,否则,Python 就把这个目录当成普通目录,而不是一个包。__init__.py
可以是空文件,也可以有 Python 代码,因为__init__.py
本身就是一个模块
包也可以有多级结构,如mycompanry.web.util
使用模块
Python 本身就内置了很多非常有用的模块
1 | #!/usr/bin/env python3 |
作用域
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用(_约定用_
前缀标识_)。
正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI 等;
__author__
,__name__
等是特殊变量,如: __doc__
可访问文档注释,自定义的变量一般不要用__xx__
格式;
实际上模块内的所有东西都是可访问的,_
私有前缀也只是习惯约定
1 | def _private_1(name): |
安装第三方模块
在 Python 中,安装第三方模块,是通过包管理工具 pip 完成的。
pip install Pillow