廖雪峰课程 Python笔记(三)

一、学习网站

摘抄 廖雪峰 Python课程:点击打开

二、模块介绍

在Python中,一个.py文件就称之为一个模块(Module)。使用模块将函数进行分组,更便于维护代码。并且编写代码不必从头开始,一个模块可以到处引用,节省开发时间。
并且相同函数或变量名称在不同模块内部互不影响,但不能与内置模块名称冲突。
Python内置模块:点击查看

模块命名:

文件名称就是模块名称,如 abc.py 文件模块名就是 abc
注意:

模块名要遵循Python变量命名规范,不要使用中文、特殊字符;
模块名不要和系统模块名冲突,最好先查看系统是否已存在该模块,检查方法是在Python交互环境执行import abc,若成功则说明系统存在此模块。

1、使用模块

以内置 sys 模块为例,摘抄 廖雪峰 老师的案例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3                    # 标准注释 让这个hello.py文件直接在Unix/Linux/Mac上运行
# -*- coding: utf-8 -*- # 标准注释 表示.py文件本身使用标准UTF-8编码

" a test module " # 一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

__author__ = "Michael Liao" # __author__变量把作者写进去,公开源代码后别人可知作者;

## 👆以上就是Python模块的标准文件模板,可以不写。

import sys # 导入模块
def test():
args = sys.argv # sys模块内的argv变量,用list存储了命令行的所有参数。
if len(args)==1:
print("Hello, world!")
elif len(args)==2:
print("Hello, %s!" % args[1])
else:
print("Too many arguments!")

if __name__=="__main__":
test()
# 用于做运行测试,原理:运行模块后,Python解释器把一个特殊变量__name__置为__main__
# 而如果在其他地方导入该hello模块时,if判断将失败,后面代码不执行,即可作为运行测试。

注意:
argv至少有一个元素,因为第一个参数永远是该.py文件的名称:

1
2
3
4
>>> python3 hello.py
>>> sys.argv # ["hello.py"]
>>> python3 hello.py Michael
>>> sys.argv # ["hello.py", "Michael]

2、作用域

希望函数和变量仅在内部使用,可通过 _ 前缀来实现。
正常的函数和变量名是公开的(public),可以直接被引用,比如:abcx123PI等。
特殊变量名如 __xxx__ 可以直接引用但是有其他用途,比如之前的 __author____name__就是特殊变量,一般我们直接写不要用这种。
非公开的(private)函数和变量名,如 _xx__xx ,不能直接引用。

外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。

3、安装第三方模块

python需要通过包管理工具 pip 来安装第三方模块。
pip安装教程:点击打开
Windows安装要记得在安装时勾选了 pipAdd python.exe to Path

安装完成,通过下面命令安装:

1
pip install packageName

4、安装常用模块

可以通过命令行安装。
也可以使用 Anaconda 安装。

5、模块搜索路径

默认情况下,Python解释器会搜索 当前目录所有已安装的内置模块第三方模块 ,搜索路径存放在 sys模块path变量 中:

1
2
3
>>> import sys
>>> sys.path
["", "/Library/Frameworks/Python.framework/Versions/3.6/lib/python36.zip", "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6", ..., "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages"]

如果我们要添加自己的搜索目录,有两种方法:
一是直接修改 sys.path ,添加要搜索的目录:

1
2
>>> import sys
>>> sys.path.append("/Users/michael/my_py_scripts")

这种方法是在运行时修改,运行结束后失效。

第二种方法是设置环境变量 PYTHONPATH ,该环境变量的内容会被自动添加到模块搜索路径中。
设置方式与设置 Path环境变量 类似。注意只需要添加你自己的搜索路径,Python自己本身的搜索路径不受影响。

3、常用内建模块

python内置了许多非常有用的模块,无需额外安装和配置,即可直接使用。

a)、datetime

datetime是python处理日期和时间的标准库。

获取当前日期和时间

由于 datetime 模块还包含一个 datetime类 ,所以导入有2种方法。

1
2
3
4
5
6
7
8
# 方法1:
from datetime import datetime
# 方法2:
impot datetime.datetime

now = datetime.now()
print(now) # 2017-12-04 22:29:25.865744
print(type(now)) # <class "datetime.datetime">

datetime.now() 返回当前日期和时间,其类型是 datetime

获取指定日期和时间
1
2
3
from datetime import datetime
dt = datetime(2017,12,4,12,20) # 用指定日期时间创建datetime
print(dt) # 2017-12-04 12:20:00
datetime转换为timestamp

在计算机中,时间实际上是用 数字表示 的。我们把 1970年1月1日 00:00:00 UTC+00:00时区 的时刻称为 epoch time ,记为0(1970年以前的时间 timestamp 为负数),当前时间就是相对于 epoch time 的秒数,称为 timestamp 。即:

1
2
3
timestamp = 0 = 1970-1-1 00:00:00 UTC+0:00
# 对应的北京时间是(时差8小时):
timestamp = 0 = 1970-1-1 08:00:00 UTC+8:00

可见 timestamp 的值与时区毫无关系,因为 timestamp 一旦确定,其UTC时间就确定了,转换到任意时区的时间也是完全确定的,这就是为什么计算机存储的当前时间是以 timestamp 表示的,因为全球各地的计算机在任意时刻的 timestamp 都是完全相同的(假定时间已校准)。
把一个 datetime类型 转换为 timestamp 只需要简单调用 timestamp() 方法:

1
2
3
from datetime import datetime
dt = datetime(2017,12,04,12,20) # 用指定日期时间创建datetime
dt.timestamp() # 把datetime转换成timestamp

另外:如果转换结果小数位,即小数位表示毫秒数。

timestamp转换为datetime

使用 datetime 提供的 fromtimestamp() 方法即可:

1
2
3
from datetime import datetime
t = 1429417200.0
print(datetime.fromtimestamp(t)) # 2015-04-19 12:20:00

timestamp 也可以直接被转到UTC标准市区的时间。

1
2
3
4
from datetime import datetime
t = 1429417200.0
print(datetime.fromtimestamp(t)) # 本地时间: 2015-04-19 12:20:00
print(datetime.utcfromtimestamp(t))# UTC时间: 2015-04-19 04:20:00
str转换为datetime

通过 datetime.strptime() 实现。

1
2
3
from datetime import datetime
cday = datetime.strptime("2017-12-04 22:51:59","%Y-%m-%d %H:%M:%S")
print(cday) # 2017-12-04 22:51:59

关于时间格式 %Y-%m-%d %H:%M:%S ,可以查看官方介绍:点击打开

datetime转换为str

通过 strftime() 实现。

1
2
3
from datetime import datetime
now = datetime.now()
print(now.strftime("%a,%b,%d %H:%M"))# Mon,Dec,04 22:55
datetime加减

对日期和时间进行加减实际上就是把datetime往后或往前计算,得到新的datetime。
加减可以直接用 +- 运算符,不过需要导入 timedelta 这个类:

1
2
3
4
5
6
from datetime import datetime,timedelta
now = datetime.now()
print(now) # 2017-12-04 23:01:31.882059
print(now + timedelta(hours = 10)) # 2017-12-05 09:01:31.882059
print(now - timedelta(days = 1)) # 2017-12-03 23:01:31.882059
print(now + timedelta(days = 2,hours = 12)) # 2017-12-07 11:01:31.882059
本地时间转换为UTC时间

本地时间是指系统设定时区的时间,例如北京时间是 UTC+8:00时区 的时间,而UTC时间指 UTC+0:00时区 的时间。
一个 datetime类型 有一个时区属性 tzinfo ,但是默认为 None ,所以无法区分这个 datetime 到底是哪个时区,除非强行给 datetime 设置一个时区:

1
2
3
4
5
6
from datetime import datetime, timedelta, timezone
tz_utc_8 = timezone(timedelta(hours=8)) # 创建时区UTC+8:00
now = datetime.now()
print(now) # 2017-12-04 23:05:03.537432
dt = now.replace(tzinfo=tz_utc_8) # 强制设置为UTC+8:00
print(dt)
时区转换

通过 utcnow() 拿到当前的UTC时间,再转换成任意时区的时间:

1
2
3
4
5
6
7
8
9
10
11
12
from datetime import datetime, timedelta, timezone
utc_dt = datetime.utcnow().replace(tzinfo=timezone.utc)
print(utc_dt) # 2017-12-04 15:10:49.949725+00:00
# astimezone()将转换时区为北京时间:
bj_dt = utc_dt.astimezone(timezone(timedelta(hours=8)))
print(bj_dt) # 2017-12-04 23:11:36.684443+08:00
# astimezone()将转换时区为东京时间:
tokyo_dt = utc_dt.astimezone(timezone(timedelta(hours=9)))
print(tokyo_dt) # 2017-12-05 00:12:35.397543+09:00
# astimezone()将bj_dt转换时区为东京时间:
tokyo_dt2 = bj_dt.astimezone(timezone(timedelta(hours=9)))
print(tokyo_dt2) # 2017-12-05 00:12:35.397543+09:00

时区转换的关键在于,拿到一个 datetime 时,要获知其正确的时区,然后强制设置时区,作为基准时间。
利用带时区的 datetime,通过 astimezone() 方法,可以转换到任意时区。

注:不是必须从UTC+0:00时区转换到其他时区,任何带时区的datetime都可以正确转换,例如上述bj_dt到tokyo_dt的转换。

小结

datetime 表示的时间需要时区信息才能确定一个特定的时间,否则只能视为 本地时间
如果要存储 datetime ,最佳方法是将其转换为 timestamp 再存储,因为 timestamp 的值与时区完全无关。

b)、collections模块

collections是Python内建的一个集合模块,提供了许多有用的集合类。

namedtuple

如用 tuple 表示一个二维坐标:

1
p = (1,2)

这个 tuple 很难看出是个坐标,即可以使用 namedtulpe 表示:

1
2
3
4
5
from collections import namedtuple
Point = namedtuple("Point",["x","y"])
p = Point(1,2)
print(p.x) # 1
print(p.y) # 2

其格式如:

1
namedtuple("名称", [属性list]):

namedtuple 是一个函数,创建一个自定义 tuple 对象,并规定 tuple 元素个数,我们可以用属性而不是索引来引用 tuple 的某个元素。
可以验证创建的 Point对象tuple 的一种子类:

1
2
print(isinstance(p,Point))  # True
print(isinstance(p,tuple)) # True

然后创建一个圆:

1
Circle = nametuple("Circle",["x","y","r"])
deque

使用 list 存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为 list 是线性存储,数据量大的时候,插入和删除 效率很低
deque 是为了高效实现插入和删除操作的双向列表,适合用于 队列和栈

1
2
3
4
5
from collections import deque
q = deque(["a","b","c"])
q.append("x") # 从后面入栈
q.appendleft("y") # 从前面入栈
print(q) # deque(["y", "a", "b", "c", "x"])
defaultdict

使用 dict 时,如果引用的 Key 不存在,就会抛出 KeyError 。如果希望 key 不存在时,返回一个 默认值 ,就可以用 defaultdict

1
2
3
4
5
from collections import defaultdict
dd = defaultdict(lambda:"N/A")
dd["key1"] = ["abc"]
print(dd["key1"]) # "abc"
print(dd["key2"]) # "N/A"
OrderedDict

使用 dict 时,Key 是无序的。在对 dict 做迭代时,我们无法确定 Key 的顺序。如果要保持 key 的顺序,可以用 OrderedDict :

1
2
3
4
5
from collections import OrderedDict
d = dict([("a",1),("b",2),("c",3)])
print(d) # dict的Key是无序的 {"a": 1, "b": 2, "c": 3}
od = OrderedDict([("a",1),("b",2),("c",3)])
print(od) # OrderedDict的Key是有序的 OrderedDict([("a", 1), ("b", 2), ("c", 3)])

注意:OrderedDictKey 会按照插入的顺序排列,不是 Key 本身排序:

1
2
3
4
5
od = OrderedDict()
od["z"] = 1
od["y"] = 2
od["x"] = 3
print(list(od.keys())) # 按照插入的Key的顺序返回 ["z", "y", "x"]
Counter

Counter 是一个简单的计算器,比如计算字符出现的个数:

1
2
3
4
5
from collections import Counter
c = Counter()
for ch in "programing":
c[ch] = c[ch]+1
print(c) # Counter({"r": 2, "g": 2, "p": 1, "o": 1, "a": 1, "m": 1, "i": 1, "n": 1})

Counter 实际上也是 dict 的一个子类,上面的结果可以看出,字符"g""m""r"各出现了两次,其他字符各出现了一次。

c)、base64
1
2
3
4
5
6
import base64
base64.b64encode(b"binary\x00string") # 编码 b"YmluYXJ5AHN0cmluZw=="
base64.b64decode(b"YmluYXJ5AHN0cmluZw==") # 解码 b"binary\x00string"
base64.b64encode(b"i\xb7\x1d\xfb\xef\xff") # 编码 b"abcd++//"
base64.urlsafe_b64encode(b"i\xb7\x1d\xfb\xef\xff") # 编码 b"abcd--__"
base64.urlsafe_b64decode("abcd--__") # 解码 b"i\xb7\x1d\xfb\xef\xff"