编译型与解释型语言的区别:
编译型:开发完成,一次性把所有的代码进行编译成机器能识别的二进制码,在运行。
代表语言:c,c++
优点:执行速度快
缺点:开发速度慢,调试周期长
解释型:代码从上到下一行一行解释并运行
代表语言:python,php
优点:开发效率快,调试周期短
缺点:执行速度相对较慢
python的解释器
1.Cpython 把python代码转化为C语言能识别的二进制码
2.Jpython 把python代码转化为java语言能识别的二进制码
3.pypy 将所有代码一次性编译成二进制码,加快执行效率(模仿编译型语言)
4.其他语言解释器 把python代码转化为其他语言能识别的二进制码
变量和赋值
-
什么是变量
-
一个实体的指代
- 查看变量的内存地址
id(x)
-
可重新赋值的
-
-
为变量赋值
- 通过=来赋值
x = 3
- 被重新赋值之后的值去哪了?
- 被系统回收了
-
Python常量
- python没有常量这个数据类型
Python的六大基本类型
-
通过
type()
函数来查看当前变量的数据类型 -
int
(整数)int('1')
-
float
(浮点数)float('1.0') float('INF')
- 浮点数和小数的区别:因为计算机内部只认识1和0,所以浮点数强调的是小数的表现形式
-
string
(字符串,字符序列)#转义,将其他类型转为string str(2)
- 有序的
- 如何表示字符串
-
""
-
- 转义字符
告诉python解释器,我们当前要表示的是一个字符或者是特殊字符
通过
\
来转义 -
boolean
True等价于1,false等价于0
-
bytes
(二进制序列):二进制的表示形式 -
None(空)
Python的四大基本数据结构
-
list(列表)
-
用来装载不同数据类型的数据集结构
-
特点
- 有序
- 可以装载任意数据类型
- 可以更改
-
如何表示
list
- 通过
list()
创建一个列表
list("hello world")
- 通过[ ]声明一个列表
a = [1,2,3]
- 通过
-
-
tuple(元组)
-
元组:不可修改的列表,常用来表示记录
-
特点
- 有序
- 可以装载任意数据类型
- 不可更改
-
如何表示
tuple
- 通过
tuple("hello")
tuple("hello")
- 通过( , )来声明一个元组
a = (1,2,3) tuple(a) a # 声明单个元素的元组,要添加`,` a = (a,)
- 通过
-
-
dict(字典)
- 字典也叫
hashtable
,通过hash
(散列)函数将传入的key值生成地址来查找value- key->hash函数->返回了value的地址->通过地址返回value值
- 特点
- 无序的
- 字典中的key必须是可hash的,也就是不可更改的,唯一的
- 可更改的
- 通过
dict()
来创建字典
dict(a=2)//不常用
- 通过{ }来声明一个字典
a = {"a": 2}
- 字典也叫
-
set(集合)
-
set其实是没有value的字典
-
特点
- 无序的
- 集合中的key必须是可hash的
- 集合可以更改的
- 元素是唯一的
-
如何表示set
- 通过set( )来创建集合
set([1,2,2])
- 通过{ }来表示
{1,2,3}
-
Python函数的基本介绍
-
什么是函数?
函数是一段可以直接被另外一段程序或代码引用的程序或代码, 也叫做子程序, 方法.
- 可重复使用
- 可互相调用
-
函数的目的
- 为了代码段的复用
-
在
Python
中如何定义一个函数?def foo(arg): return "Hello " + str(arg)
-
函数的组成
-
参数列表
-
必须参数
当前参数必须按顺序传入
-
关键字参数
根据关键字参数传参可以无视顺序
def foo(arg=None, arg_2=None)
-
默认参数
def foo(arg='tunan', arg_2=None)
-
-
不定长参数
在装饰器中会大量应用
可以接受任意长度的参数.
-
*
代表了省略, 省略了参数
tuple
(元组) -
**
省略了关键字参数
dict
(字典)
-
-
-
函数体
-
返回值
默认返回
None
return None
-
Python的运算符
-
算术运算
-
+
-
-
-
*
乘法
-
/
除法
-
//
整除
-
%
取余数
-
**
x的y次幂
-
开方(没有提供直接的运算符)
x ** (1/2)
-
abs()
取绝对值
-
-
赋值运算
通过
=
赋值a = 1
-
比较运算
比较的是两个对象的字面值, 字面值暂时可以简单地理解为输出值
-
<
-
>
-
<=
-
>=
-
==
等于
-
!=
不等于
-
-
标识号比较运算
比较的是两个变量的内存地址
-
is
-
is not
-
赋值类型为
str
,int
的时候要考虑Python
常量池a = "test_1" b = "test_1" a is b >>> True a = '你好' b = '你好' a is b >>> False
-
-
成员检测运算
判断元素是否在当前序列当中
-
in
a = [1,2,3] 1 in a >>> True b = [1, 2] b in a >>> False
-
not in
-
-
布尔运算
判断当前语句的结果是
True
还是False
-
and
只有两边都是
True
才返回True
-
or
两边表达式有一个
True
返回的结果为True
-
短路
表达式A or 表达式B 当表达式A为True时, 表达式B就不会运行
-
-
逻辑取反
not
-
-
位运算
二进制运算, 未来刷题的时候再说, 有兴趣的同学可以了解一下
~
^
>>
<<
&
|
Python运算符优先级
运算符 | 描述 |
---|---|
or | 布尔运算或 |
and | 布尔运算与 |
not | 布尔运算逻辑取反 |
in, not in, is, is not, <, !=, … | 比较运算, 成员检测运算, 标识号检测 |
+, - | 加法和减法 |
*, /, //, % | 乘法, 除法, 整除, 取余 |
+x, -x | 正负数 |
** | 幂 |
-
自定义优先级
如果不确定优先级, 出于可读性和避免未知的BUG, 我们都应该用()来自定义优先级
-
通过
()
(not b and c) or (d and e)
-
-
用函数实现一个具有加, 减, 乘, 除, 整除, 取余, 开方的计算器
my_calculator.py def add(a, b): ... def sqrt(a, b): ...
字符串(字符序列)和字节序列
-
字符
- 由于历史原因, 将字符定义为
unicode
字符还不够准确, 但是未来字符的定义一定是unicode
字符
- 由于历史原因, 将字符定义为
-
字节
就是字符的二进制表现形式
-
码位
我们计算机显示的实际上是码位
>>> '你好'.encode("unicode_escape").decode() '\\u4f60\\u597d' >>> >>> '\u4f60\u597d' '你好'
UNICODE
标准中以4~6个十六进制数字表示
-
编码
-
字符序列(string) -> 字节序列(bytes) -------------编码(encode)
>>> "你好".encode("utf-8") b'\xe4\xbd\xa0\xe5\xa5\xbd'
-
字节序列(bytes) -> 字符序列(string) -------------解码(decode)
>>> b b'\xe4\xbd\xa0\xe5\xa5\xbd' >>> b.decode("utf") '你好'
-
-
编码错误
-
乱码和混合编码
-
检查编码
没有办法通过字节序列来得出编码格式, 都是统计学来预估当前的编码
# 安装chardet pip install chardet # 导入charet >>> import chardet >>> chardet.detect(b)
-
解决乱码和混合编码
-
忽略错误编码
>>> b_2.decode("utf-8", errors='ignore') '你好'
-
利用鬼符来替换
>>> b_2.decode("utf-8", errors='replace') '你好'
-
-
-
字符串的CRUD操作
通过dir("")可以查看当前字符串的操作方法
-
Create(创建)
-
+
>>> a = "a" >>> id(a) 22951584 >>> a = a + "b" >>> id(a) 60513280 >>> a 'ab'
-
+=
a += "b" 就是 a = a + "b" 省略写法
-
-
Retrieve(检索)
-
根据索引获取字符
在计算机语言当中, 索引值是从0开始数的
>>> a = "hello, world" >>> a[1] 'e'
-
find和index(获取目标字符的索引值)
>>> a.find("e") 1 >>> a.find("!") -1 # 找不到目标字符时, index会报错 >>> a.index("!") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: substring not found
-
startwith和endwith
>>> f = "2020-11-22-xxxxx" >>> f.startswith("2020-11-22") True >>> f = "xxxxx.jpg" >>> f.endswith("jpg") True
-
-
UPDATE(更新)
-
replace(替换)
返回的是一个新的字符串
a.replace("wer", "wor")
-
split(分割)
>>> a = "<<python>>, <<java>>, <<c++>>" >>> a.split(",") ['<<python>>', ' <<java>>', ' <<c++>>']
-
join(拼接)
>>> b ['<<python>>', ' <<java>>', ' <<c++>>'] >>> ",".join(b) '<<python>>, <<java>>, <<c++>>'
-
-
DELETE(删除)
-
strip
>>> a ' hello, world ' >>> a.strip() 'hello, world' >>>
-
lstrip
-
rstrip
-
字符串的输出和输入(文件)
-
保存到文件
# open函数打开一个文件, 没有文件会新建, 但是路劲不对会报错 # 指定文件名, 方法(读, 写, 追加), 编码格式 output = open("output.txt", "w", encoding="utf-8") content = "hello, world" # 正式写入文件 output.write(content) # 关闭文件句柄 output.close()
-
读取文件
input = open("output.txt", "r", encoding="utf-8") # 获取文件中的内容 content = input.read() print(content) # 暂时理解为只能读取一遍 content_2 = input.read() print(content_2)
-
追加文件
output = open("output.txt", "a", encoding="utf-8") content = "\nhello, world" # 正式写入文件 output.write(content) # 关闭文件句柄 output.close()
字符串的格式化输出
-
format
-
按传入参数默认顺序
a = "ping" b = "pong" "play pingpong: {}, {}".format(a, b)
-
按指定参数索引
a = "ping" b = "pong" "play pingpong: {0}, {1}, {0}, {1}".format(a, b)
-
按关键词参数
a = "ping" b = "pong" print("play pingpong: {a}, {b}, {a}, {b}".format(a='ping', b='pong'))
-
按变量(推荐, 但是只有3.6以上才可以使用)
a = "ping" b = "pong" print(f"playing pingpong: {a}, {b}")
-
小数的表示
>>> "{:.2f}".format(3.14159) '3.14' >>>
-
-
%
>>> "playing %s %s" % ("ping", "pong") 'playing ping pong'
了解变量和引用
-
变量简单地说就是指向了一个实体
-
引用简单地说就是指向变量的变量
>>> a = 1 >>> b = a >>> id(a) 1778508560 >>> id(b) 1778508560
基础数据结构的CRUD操作
-
List(列表)
list中存的元素是引用
-
create(增加)
-
append
末尾添加元素
>>> l = [] >>> id(l) 55200584 >>> l.append("a") >>> l ['a'] >>> id(l) 55200584
-
+
和+=
-
+
拼接两个列表, 然后返回一个新列表
-
+=
>>> l = ['a'] >>> id(l) 55200664 >>> l += ['b'] >>> id(l) 55200664 >>> l ['a', 'b']
-
-
*
和*=
>>> a = 'a' >>> id(a) 53622432 >>> l = [a] * 10 >>> l ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] >>> id(l[0]) 53622432 >>> id(l[1]) 53622432 >>> id(l[9]) 53622432 # 赋值语句之后, a已经是一个新的对象了 >>> a = 'b' >>> l ['a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a', 'a'] >>> id(a) 53647264
-
insert
指定位置添加元素
l.insert(0, 'b')
-
-
Retrieve(检索)
-
索引取值
所有序列都支持索引取值
-
切片
your_list[start:end:step] # 取一段区间 your_list[start:end] # 取最后一个值 your_list[-1] # 间隔问题 your_list[1:20:2]
-
index
>>> l ['a', 'b', 'c'] >>> l.index('a') 0
-
-
Update(更新)
-
索引赋值
l[0] = 'a_1'
-
切片赋值
>>> l ['a_1', 'a_2', 'b', 'c'] >>> l[0:2] = "a" >>> l ['a', 'b', 'c'] >>> l[0:2] = 1
-
-
DELETE(删除)
-
pop()
从末尾删除元素并返回
>>> l ['a', 'b', 'c'] >>> x = l.pop() >>> l ['a', 'b'] >>> x 'c'
-
clear()
清楚当前列表的元素, 不会改变列表的内存地址.
-
-
★SORT(排序)
-
sort()
>>> l [1, 3, 2, 6, 4] >>> l.sort() >>> l [1, 2, 3, 4, 6]
-
sorted
排序后返回新列表
>>> l2 = sorted(l) >>> l [1, 3, 2, 6, 4] >>> l2 [1, 2, 3, 4, 6] >>> id(l) 55201384 >>> id(l2) 55200984
-
reverse
>>> l2 [1, 2, 3, 4, 6] >>> l2.reverse() >>> l2 [6, 4, 3, 2, 1]
-
reversed
倒序之后返回新列表
>>> l [1, 3, 2, 6, 4] >>> list(reversed(l)) [4, 6, 2, 3, 1]
-
-
-
tuple
-
Create
无
-
Retrieve
- 索引取值
- index
- 切片
-
Update
无
-
Delete
无
-
-
dict
-
Create
-
键对值赋值
-
update
提供合并字典的功能
>>> d {'a': 1} >>> d2 = {"b":2, "c": 3} >>> d.update(d2) >>> d {'a': 1, 'b': 2, 'c': 3}
-
setdefault
如果字典中没有当前key, 那么就设置默认值
>>> d {'a': 1, 'b': 2, 'c': 3} >>> d.setdefault('b', 0) 2 >>> d.setdefault('d', 0) 0 >>> d {'a': 1, 'b': 2, 'c': 3, 'd': 0}
-
-
Retrieve
-
键对值访问
-
get
键对值访问缺失key会报错, 而get可以指定默认值
>>> d['e'] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'e' >>> d.get('f') >>> d.get('f', 0) 0
-
keys()
返回所有key
d.keys()
-
values()
返回所有value
d.values()
-
items()
返回所有键对值
d.items()
-
-
Update
-
键对值赋值
d['a'] = 100
-
update
>>> d.update({"b": 200, "c": 300}) >>> d {'a': 100, 'b': 200, 'c': 300, 'd': 0}
-
-
Delete
-
pop(key)
删除当前元素并返回value
-
popitem()
对于人来说, 相当于随机返回一个item
-
clear()
-
-
-
set
-
Create
- add
- update
-
Retrieve
-
运算符
in
>>> s {'a'} >>> "a" in s True
-
-
update(作用为增加)
-
union
合并两个set, 并返回一个新的set
-
-
delete
-
remove 和discard
discard缺失元素时不会报错, 而remove会报错
>>> s {'b', 'c', 'a'} >>> s.remove("a") >>> s {'b', 'c'} >>> s.discard("e") >>> s.remove("a") Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'a' >>>
-
pop()
当成无序删除并返回元素
-
-
Python的逻辑控制语句
-
条件判断语句
-
if
-
elif
-
else
a = 50 if a > 100: print("a 超过阈值") elif a == 50: print("a 只有阈值的一半") else: print("a 小于阈值")
-
-
循环语句
-
for
遍历一个可迭代对象(暂时理解为list), 会影响相同作用域当中的变量
l = [1, 2, 3, 4, 5, 6] e = 0 for e in l: print(e) print(f"final e value: {e}")
-
获取索引值和值
l = [1, 2, 3, 4, 5, 6] for i, e in enumerate(l): print(f"index: {i}, value: {e}")
-
-
while
循环一定要有逻辑判断语句来退出
while
循环while 判断语句: 表达式 while True: 判断语句 表达式
-
跳出循环
-
break
停止当前循环
-
continue
跳过当前的执行逻辑, 立即执行下一个循环语句单元;
-
pass
跳过当前条件判断中的执行语句, 后续语句继续执行;
-
-
Python的异常与处理
-
异常
程序遇到严重错误时, 会终止程序的运行并抛出异常
def my_sub(a, b): return a / b my_sub(1, 0)
-
捕获异常
try: 表达式 except [Exception] as e: 表达式 finnaly: 表达式 def my_sub(a, b): try: return a / b except ZeroDivisionError: # print(e) print("分母不可为0") return None finally: print("function my_sub end") my_sub(1, 0)
-
Exception
所有异常的基类, 所有的异常都是Exception的子类
-
处理异常颗粒度要细一点, 尽量不要捕获基类Exception, 尤其是数据处理的时候.
-
常见的异常
-
IndexError
索引值超过了列表长度
>>> l = [1] >>> l[2] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: list index out of range
-
KeyError
找不到Key
>>> d = {"a": 1} >>> d["b"] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'b'
-
ValueError
传入的参数错误
>>> int('a1') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: 'a1'
-
TypeError
类型错误, 常见于运算
>>> 1 + '2' Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unsupported operand type(s) for +: 'int' and 'str'
-
SyntaxError
语法报错, 检查自己的语法有没有写错
-
IndentationError
缩进错误
- 混用tab和space(空格)
- 缩进长度不对
-
-
-
如何处理异常
-
处理
-
抛出新异常
def my_sub(a, b): try: return a / b except ZeroDivisionError: print("分母不可为0") raise Exception("params error") finally: print("function my_sub end")
-
重新抛出
def my_sub(a, b): try: return a / b except ZeroDivisionError: print("分母不可为0") raise ZeroDivisionError finally: print("function my_sub end")
-
忽略(不推荐)
pass
用来指示当前处理语句没有正式写完, 尽量不要忽略异常, 否则代码的健壮度会很差, 造成不可预知的bug.
-
-
自定义异常
class ParamsError(Exception): pass def my_sub(a, b): try: return a / b except ZeroDivisionError: raise ParamsError("分母不可以为0") finally: print("function my_sub end")
课后作业
-
用for循环和while来完成简单的计数
-
用for循环和while循环两种方式来实现斐波那契函数, 限制在100以内
-
斐波那契函数
第N项是N-1, N-2的和
F(n)=F(n - 1)+F(n - 2) [0, 1, 1, 2, 3, 5, 8, 13, 21....]
-
-
在第二周-第一节课我们实现的简单计算器的基础上, 对参数进行检查, 如果报错就抛出我们自定义异常
ParamsError
重新认识函数
-
内置函数
-
认识Python自带的, 可全局调用的函数, 避免我们命名冲突导致了函数性状发生改变
-
查看Python携带的内置函数
from pprint import pprint # 格式化输出的库 pprint(dir(__builtins__))
-
常见的内置函数
-
str
>>> str(1.0) '1.0'
-
int
>>> int(1.0) 1 >>> int("1.0") Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: invalid literal for int() with base 10: '1.0' >>> int("1") 1 >>>
-
float
>>> float("1.0") 1.0 >>> float(1) 1.0 >>> float('1') 1.0
-
bytes
>>> bytes('a'.encode("utf-8")) b'a'
-
bool
>>> bool(0) False >>> bool(1) True >>> bool(2) True >>> bool('0') ***** True >>> bool(0.0) False
-
list
只要是序列都可以转换成
list
>>> list("qwe") ['q', 'w', 'e']
-
tuple
>>> tuple("qwe") ('q', 'w', 'e') >>> tuple([1,2]) (1, 2)
-
dict
>>> dict(a=1) {'a': 1}
-
set
>>> set([1,2,2]) {1, 2} >>> set("qweqweqwe") **** {'q', 'w', 'e'}
-
id
查看当前对象的内存地址
>>> a = "1" >>> id(a) 26114944
-
dir
- 当前对象下的所有方法和属性
- 在Python中一切皆为对象
dir(__builtins__)
-
max
返回一个序列中的最大值
>>> max([2, 4,67,1]) 67
-
min
返回一个序列中的最小值
>>> min([2, 4,67,1]) 1
-
range
返回一组数字区间的可迭代对象
>>> r = range(100) >>> r range(0, 100) >>> for i in range(10): ... print(i)
-
-
-
函数的形参和实参
-
形参
形式参数, 简单地说就是还没接受到实际值的参数. 函数未调用时就是形参
def my_power(a, b): return a ** b
-
实参
实际传入的参数, 函数调用时传入的值就叫实参
print(my_power(2, 3))
-
-
函数的返回值
-
返回值的类型
任意类型, 包括函数本身
-
如何接受返回值
-
接收单个值
-
一个变量接受返回的多个值
实际上返回的是个
tuple
>>> def foo(a, b): ... return a*2, b*2 ... >>> result = foo(1, 2) >>> result (2, 4)
-
多个变量按顺序接收
实现原理是元组解包(unpack)
>>> a,b = foo(1,2) >>> a 2 >>> b 4 # 等同于 >>> result = foo(1,2) >>> a, b = result
-
不定长变量接收
>>> result (1, 2, 3, 4, 5, 6, 7) >>> a, *b, c = result >>> a 1 >>> c 7 >>> b [2, 3, 4, 5, 6]
-
-
匿名函数
顾名思义匿名函数就是没有名字的函数, 一般都是提供给高阶函数调用.
-
通过
lambda
关键字来声明匿名函数>>> lambda x: x **2 # 返回的是一个匿名函数对象 <function <lambda> at 0x018BB660>
-
函数体是纯表达式
-
不能有复杂的逻辑判断语句
-
唯一例外的例子:
lambda x: 返回值 if 纯表达式 else 返回值 lambda x: True if x % 2==0 else False
-
-
不能有循环语句
-
不能有异常捕获
-
不能有赋值语句
-
不能有
return
-
默认表达式运行的结果就是返回值
>>> lambda x: x **2 返回值就是 x**2
-
-
-
例子
l = [[1,2], [2,1], [6,4], [3,5]] l.sort(key=lambda x: x[1]) print(l)
高阶函数
接受函数作为参数, 或者把函数作为结果返回
-
map(映射)
对一个序列每个元素进行相同的操作, 这个过程就叫映射
>>> l = [1,2,3] >>> m = map(lambda x: x**2, [1,2,3]) # 获得返回结果是一个map对象 >>> m <map object at 0x03545C10> >>> l [1, 2, 3] # map对象是一个可迭代对象, 需要驱动可迭代对象返回值, list就有这样的功能. 暂时不要太纠结 >>> list(m) [1, 4, 9] >>> l [1, 2, 3]
-
等同于以下:
def my_powser_2(a): return a ** 2 # 匿名函数只是图方便, 所有的匿名都可以通过正常函数替换 >>> m = map(my_powser_2, [1,2,3]) >>> list(m) [1, 4, 9]
-
多用于和
math
库进行运算操作>>> m = map(math.sqrt, [1, 4, 9, 16, 25]) >>> list(m) [1.0, 2.0, 3.0, 4.0, 5.0]
-
-
filter(过滤)
filter(函数, 可迭代对象) 函数中的表达式返回结果为False, 就会被过滤
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # 过滤偶数 >>> f = filter(lambda x: x%2, l) >>> list(f) [1, 3, 5, 7, 9] # 过滤奇数 >>> f = filter(lambda x: False if x%2 == 1 else True, l) >>> list(f) [0, 2, 4, 6, 8]
递归函数
在函数中调用自身的函数就叫递归函数
-
核心思想
将大的任务拆分为子任务来解决复杂问题, 只要大任务能拆分成子任务, 就可以使用递归
F(n) = F(F(n-1))
-
声明一个递归函数(阶乘)
- 一定要有退出机制
F(n) = n * F(n-1) def fact(n): if n == 1: return 1 return n * fact(n-1)
-
使用递归函数重构斐波那契函数
f(n) = f(n-1) + f(n-2)
def recur_fibo(n): """递归函数 输出斐波那契数列""" if n <= 1: return n else: return (recur_fibo(n - 1) + recur_fibo(n - 2)) # 获取用户输入 nterms = int(input("您要输出几项? ")) # 检查输入的数字是否正确 if nterms <= 0: print("输入正数") else: print("斐波那契数列:") for i in range(nterms): print(recur_fibo(i))
作用域
程序创建, 访问, 改变一个变量时, 都是在一个保存该变量的空间内进行, 这个空间被称为命名空间, 即作用域
-
Built-in
内置- 可以在Python环境中的任何模块, 任意位置访问和调用
-
Global
全局变量-
只作用于当前模块(可以理解为当前文件)
-
可以简单地理解为定以在函数外的变量就是全局变量, 如果在函数体定义那就时局部变量.
-
如何将局部变量变成全局变量?
-
使用
global
关键字a = 1 def foo(): global a a = 2 print(a) foo() print(a)
-
-
-
Enclosed(嵌套)
自由变量在嵌套函数中, 访问函数体之外的非全局变量
-
只作用于嵌套函数体
-
最大的应用就是闭包
-
自由变量是个相对的概念
-
将局部变量变成自由变量
-
使用
nonlocal
关键字def make_averager(): total = 0 count = 0 def averager(value): nonlocal total, count total += value count += 1 return total / count return averager my_avg = make_averager() print(my_avg(1)) print(my_avg(2))
-
-
-
Local
局部变量-
只作用于当前函数体
-
一旦变量在函数体中赋值, 那么该变量相对该函数来说就是局部变量
a = 1 b = [] def foo(): a = 2 b.append(2) # 局部变量会在函数声明的时候就定义好 # 不是按照我们逻辑思维上先执行全局变量b.append(2), 然后再声明一个局部变量b # 而是再函数声明之初就已经定义了b为局部变量 # b = 3 return None foo() print(a) print(b)
-
闭包和装饰器
-
闭包
闭包指延申了作用域的函数, 也就是作用域中的
Enclosed
的概念def make_averager(): series = [] def averager(value): series.append(value) total = sum(series) return total / len(series) return averager # my_avg就是延申了作用域的函数 # series就是被延申作用域的变量 my_avg = make_averager() print(my_avg(1)) print(my_avg(2))
-
装饰器
-
实现原理
就是闭包, 延申了被装饰函数的作用域, 本质是将函数作为参数传递给一个可调用对象(函数或类)
-
目的
增加和扩展可调用对象(函数或类)的行为
-
实现一个装饰器
-
通过
@
关键字装饰函数def clock_it_deco(func): def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) end_time = time.time() print(f"{func.__name__} execute time: {format(end_time - start_time, '.2f')} s") return result return wrapper # @other_deco @clock_it_deco def foo(a, b): count = 1 while True: if count > a ** b: break count += 1 foo(10, 5)
-
等同于
foo = clock_it_deco(foo) foo(10, 5)
-
-
导入第三方模块
-
导包的层级关系
-
模块(module)
以文件为载体, 包含各类对象
-
包(package)
以文件夹为载体, 包含了各类模块
-
库(lib)
包含了各类包
-
-
import 库
-
from 库/模块 import 模块/函数
-
导包的命名冲突
通过
as
这个关键词来给当前模块/函数取个别名from datetime import datetime as p_datetime
时间模块time
调用的都是系统级的接口, 提供时间的访问和转换的功能
-
查看时间
-
获取当前时间
# 有时区的 time.localtime() 返回的是一个time.struct_time对象
-
时间戳
time.time()
-
时间的格式化输出
now = time.localtime() now = time.strftime("%Y-%m-%d %H:%M:%S", now) print(now) # 可以省略时间对象 now = time.strftime("%Y-%m-%d %H:%M:%S")
-
运算
将时间对象转换为
list
, 对相应的时间重新赋值后, 通过time.struct_time
生成一个新的时间对象time_list = list(time.localtime()) time_list[2] = 4 time.struct_time(time_list)
-
时间休眠
当前程序休眠n秒
time.sleep(3)
-
时间模块datetime
封装了time, 提供了更高级和更友好的接口
-
查看时间
# 获取计算机时间, 返回的是一个datetime.datime对象 datetime.datetime.today() # 获取指定时区的时间 datetime.datetime.now(tz=None) # 获取utc时间 datetime.datetime.utcnow()
-
时间格式的转换
-
datetime.datetime
->str
now = datetime.datetime.now(tz=None) now.strftime("%Y-%m-%d %H:%M:%S")
-
str
->datetime.datetime
>>> now '2021-01-03 23:38:26' >>> datetime.datetime.strptime(now, "%Y-%m-%d %H:%M:%S") datetime.datetime(2021, 1, 3, 23, 38, 26)
-
datetime.datetime
->timestamp
>>> now datetime.datetime(2021, 1, 3, 23, 40, 45, 749240) >>> now.timestamp() 1609688445.74924
-
timestamp
->datetime.datetime
>>> ts 1609688445.74924 >>> datetime.datetime.fromtimestamp(ts, tz=None) datetime.datetime(2021, 1, 3, 23, 40, 45, 749240)
-
-
时间运算
-
timedelta
只作用于
datetime.datetime
格式# 选中目标模块 ctrl+B / command+B 跳转到模块源码 def __new__(cls, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0):
>>> from datetime import timedelta >>> now + timedelta(hours=-1) datetime.datetime(2021, 1, 3, 22, 40, 45, 749240)
-
类的创建, 实例化, 初始化
-
什么是类
类就是拥有相同功能或者相同属性的对象集合
-
类的创建
-
object
是所有类的基类class GoGamer(object): subject = 'go' print(GoGamer)
-
-
类的实例化
实例就是抽象概念的具象化
kejie = GoGamer() print(kejie)
-
类的初始化
类创建一个新实例的时候会默认调用
__init__
这样一个特殊方法class GoGamer(object): subject = 'go' def __init__(self, obj): self.p = obj kejie = GoGamer("金勺子") print(f"柯洁含着{kejie.p}出生")
-
关于
self
指代还未实例化的实例
-
面向对象
-
面向过程
程序=数据结构+算法
- 强调的是一个实现的细节
-
面向对象
完成对越来越庞大项目代码以及对外公开接口的归类和重用, 是一种更高级的抽象.
-
通过什么手段来完成上述目的?
-
继承
class ChineseGoGamer(GoGamer): nation = 'cn' class KoreaGoGamer(GoGamer): nation = 'kr'
-
处理多继承冲突
-
查看
MRO(mehotd resolution order)
class A: def __init__(self): print("init A") class B: def __init__(self): print("init B") class C(A, B): pass print(C.__mro__)
-
指定类方法的调用顺序
class C(A, B): def __init__(self): super(A, self).__init__()
-
super
函数源码def super(cls, inst): mro = inst.__class__.mro() return mro[mro.index(cls) + 1] def super(类, 实例): # 获取当前实例的方法解析顺序 mro = 实例.类.mro() return mro[mro.index(类) + 1]
-
-
-
多态
方式为覆盖和重载
-
覆盖(子类和父类之间的, 是垂直的关系)
子类可以继承父类的所有属性和方法, 但是同时子类也可以重写父类的属性和方法, 达到自定义的目的.
class A: def __init__(self): print("init A") class B: def __init__(self): print("init B") class C(A, B): def __init__(self): print("init C")
-
重载(类中的方法和方法之间的, 是水平关系)
Python中式没有重载, 但是可以用装饰器来实现该功能.
-
-
封装
把客观事物封装成抽象的类, 隐藏实现细节, 使得代码模块化.
-
-
-
类属性和实例属性
-
类属性
-
通过类对象可以直接访问的属性
-
抽象概念的固有属性, 要考虑当前抽象概念的普适性
# 贴标签不是一个特别好的抽象, 原因他没有一个普适性 class Developer: programing_language = None busy = True
-
私有属性
不希望外部更改, 只作用于类内部
-
通过
__变量名
来声明私有属性class Lottery: __items = ["mac", "ipad", "iphone"]
-
通过
类._类名__变量名
来访问私有属性print(Lottery._Lottery__items)
-
-
-
-
实例属性
-
绑定在实例上的属性, 只能通过该实例进行访问
-
实例的自有属性
class Developer: programing_language = None busy = True __case = "doing something" d_python = Developer() d_python.programing_language = "python" d_java = Developer() d_java.programing_language = "java" print(d_java.programing_language) print(d_python.programing_language)
- 私有属性
- 通过
self.__变量名
来声明私有属性 - 通过
实例._类名__变量名
来访问私有属性
- 通过
- 私有属性
-
类方法, 静态方法, 实例方法
-
类方法
-
仅供类调用的方法
-
通过
classmethod
装饰器来声明一个类方法 -
自定义类创建
class Developer: programing_language = None busy = True __case = "doing something" def __init__(self, hairs): self.__hairs = hairs @classmethod def __new__(cls, *args, **kwargs): print("init class") return super().__new__(cls) @classmethod def get_case(cls): return cls.__case
-
-
静态方法
-
类可以直接调用的方法
-
通过
staticmethod
装饰器装饰 -
对一类抽象行为的归类
class MyMath: @staticmethod def add(a, b): return a + b
-
-
实例方法
- 仅供实例调用的方法
接口, 协议和抽象基类
-
接口
对象公开方法的子集, 让对象在系统中扮演特定的角色.
list实现了增删改查的接口, 只要有一个接口没有实现那就不属于list tuple只提供了查的接口
-
协议
非正式的接口, 协议与继承没有关系, 一个类可能会实现多个接口, 从而让实例扮演多个角色
list扮演者列表的角色, 但同时也是一个序列, 序列并不是一个实体类.
-
协议的应用
class MyDict(dict): def __iadd__(self, other): self.update(other) return self def __str__(self): return f"My Dict {self.items()}"
-
-
抽象基类
把客观事物封装成抽象的元类, 区分概念和实现.
-
只要有
@abc.abstractmethod
装饰器的类就是抽象基类import abc class Mixin: def sign(self): pass def rank(self): pass class Gamer: @abc.abstractmethod def sign(self): pass class GoGamer(Mixin, Gamer): pass class Swimmer(Mixin, Gamer): pass
-
课后作业
- 将之前封装的
MyMath
类中的实例方法改为静态方法, 体会两者的区别. - 为上节课自定义类添加以下功能:
- 添加类属性
- 添加类私有属性
- 添加类方法或者类的私有属性
- 在
__init__
方法中初始化实例属性 - 在
__init__
方法中绑定私有实例属性 - 在自定义类中实现
__str__
, 自定义输出格式
-
类属性和实例属性
-
类属性
-
通过类对象可以直接访问的属性
-
抽象概念的固有属性, 要考虑当前抽象概念的普适性
# 贴标签不是一个特别好的抽象, 原因他没有一个普适性 class Developer: programing_language = None busy = True
-
私有属性
不希望外部更改, 只作用于类内部
-
通过
__变量名
来声明私有属性class Lottery: __items = ["mac", "ipad", "iphone"]
-
通过
类._类名__变量名
来访问私有属性print(Lottery._Lottery__items)
-
-
-
-
实例属性
-
绑定在实例上的属性, 只能通过该实例进行访问
-
实例的自有属性
class Developer: programing_language = None busy = True __case = "doing something" d_python = Developer() d_python.programing_language = "python" d_java = Developer() d_java.programing_language = "java" print(d_java.programing_language) print(d_python.programing_language)
- 私有属性
- 通过
self.__变量名
来声明私有属性 - 通过
实例._类名__变量名
来访问私有属性
- 通过
- 私有属性
-
类方法, 静态方法, 实例方法
-
类方法
-
仅供类调用的方法
-
通过
classmethod
装饰器来声明一个类方法 -
自定义类创建
class Developer: programing_language = None busy = True __case = "doing something" def __init__(self, hairs): self.__hairs = hairs @classmethod def __new__(cls, *args, **kwargs): print("init class") return super().__new__(cls) @classmethod def get_case(cls): return cls.__case
-
-
静态方法
-
类可以直接调用的方法
-
通过
staticmethod
装饰器装饰 -
对一类抽象行为的归类
class MyMath: @staticmethod def add(a, b): return a + b
-
-
实例方法
- 仅供实例调用的方法
接口, 协议和抽象基类
-
接口
对象公开方法的子集, 让对象在系统中扮演特定的角色.
list实现了增删改查的接口, 只要有一个接口没有实现那就不属于list tuple只提供了查的接口
-
协议
非正式的接口, 协议与继承没有关系, 一个类可能会实现多个接口, 从而让实例扮演多个角色
list扮演者列表的角色, 但同时也是一个序列, 序列并不是一个实体类.
-
协议的应用
class MyDict(dict): def __iadd__(self, other): self.update(other) return self def __str__(self): return f"My Dict {self.items()}"
-
-
抽象基类
把客观事物封装成抽象的元类, 区分概念和实现.
-
只要有
@abc.abstractmethod
装饰器的类就是抽象基类import abc class Mixin: def sign(self): pass def rank(self): pass class Gamer: @abc.abstractmethod def sign(self): pass class GoGamer(Mixin, Gamer): pass class Swimmer(Mixin, Gamer): pass
-
requests模块的介绍
-
requests的作用
通过python来模拟请求网址
-
一个模拟请求由以下四个部分组成
- url
- method
- body
- headers
-
模拟请求百度
没有安装requests库的同学, 在当前python环境下执行以下语句安装第三方库 pip install requests
import requests def request_baidu(): url = "https://www.baidu.com/" # body = "" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36" } response = requests.get(url=url, headers=headers) print(response.text) request_baidu()
理解多线程和多进程
-
什么是进程?什么是线程?
- 进程: 可以简单地认为是一个程序. 进程是操作系统分配资源的最小单位.
- 线程: 一个进程可以有多个线程, 每个线程可以独立完成一些任务. 线程是操作系统进行运算调度的最小单位.
-
多线程demo
from threading import Thread for i in range(10): # 只是创建了线程对象 t = Thread(target=request_baidu) # 启动线程 t.start()
-
多进程demo
from multiprocessing import Process for i in range(10): # 只是创建了进程对象 p = Process(target=request_baidu) # 启动进程 p.start()
-
多线程
-
等待任务完成后回到主进程
通过调用
Thread
对象的join
方法# 保存当前thread对象 thread_array = [] for i in range(10): t = Thread(target=request_baidu, args=(i, )) thread_array.append(t) t.start() # 调用thread对象join接口, 等待任务完成后回到主进程 for t in thread_array: t.join() print("done!")
-
如何拿到返回结果
-
赋值到全局变量当中, 添加到可变对象之中
result = [] def request_baidu(index): ... result.append(response) if __name__ == "__main__": thread_array = [] for i in range(10): t = Thread(target=request_baidu, args=(i, )) thread_array.append(t) t.start() for t in thread_array: t.join() print("done!") print(result)
-
-
-
多进程
-
等待任务完成后回到主进程
通过调用
Process
对象的join
方法 -
如何拿到返回结果
无法通过全局变量存储返回结果.
多进程相当于启动了多个程序, 共同执行了同一份代码, 他们之间的内存地址完全不一样
import requests import time from threading import Thread from multiprocessing import Process result = [] print(f"主进程result内存地址: {id(result)}") def request_baidu(index): time.sleep(2) url = "https://www.baidu.com/" # body = "" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.141 Safari/537.36" } response = requests.get(url=url, headers=headers) print(f"当前请求序号: {index}, 返回结果状态码: {response.status_code}") print(f"子进程result内存地址: {id(result)}") result.append(response) # 如果没有判断入口代码段if __name__ == "__main__", 多进程程序会报错 # 原因是windows和pycharm的进程阻塞带来的问题 if __name__ == "__main__": process_array = [] for i in range(10): p = Process(target=request_baidu, args=(i, )) process_array.append(p) p.start() for p in process_array: p.join() print("done!") print(result)
-
-
多进程和多线程的异同点
-
相同点
-
都是对
cpu
工作时间段的描述, 只是颗粒度不同.简单地说就是多进程和多线程都会调用
cpu
资源的, 但是进程可以启动多个线程去执行. -
在
linux
内核态不区分进程和线程
-
-
不同点
- 进程有自己的独立地址空间, 建立数据表来维护代码段, 堆栈段和数据段, 而线程共享进程中的资源, 使用相同的地址空间, 所以线程间的切换快得多.
- 因为线程共享进程的全局变量, 静态变量等对象, 线程间的通信更为方便, 而进程间的通信更加复杂, 需要以
ipc
的方式进行. - 多进程要比多线程要健壮. 进程之间一般不会相互影响, 而多线程有一条线程崩溃, 会导致整个进程跟着发生崩溃或者无法正常退出等.
-
全局解释器锁(GIL)
-
计算密集型
主要占用
cpu
资源 -
IO密集型
IO
就是input output
, 需要等待的一些任务- 网络请求会有网络延迟
- 和数据库交互需要等待数据库查询事件
- 读写硬盘
-
多进程在处理计算密集型程序的时候比多线程块
由于全局解释器锁的存在, 一个进程下, 只允许一个线程执行
Python
程序的字节码(当前代码文件的二进制表示).简单地说, 创建的10个线程其实在争夺一个
cpu
资源. 但是遇到io
操作会让渡cpu
资源. -
如何绕过GIL?
- 将多线程方法改为多进程
- 将计算密集型任务转移给C扩展.
- 分布式计算引擎
spark
,Apache
- 使用
PyPy
解释器, 工业上几乎没人这么用, 因为PyPy
并不成熟.
进程间通信(IPC)
-
文件
通过读写文件来进行变量, 数据, 信息的传递
-
读写冲突
两个进程同时进行写, 或者一个写一个读, 造成了冲突.
-
解决读写冲突
-
互斥锁
from multiprocessing import Process, Lock def save_to_file(index, lock): with lock: with open("test.log", "a", encoding="utf-8") as f: f.write(str(index) + "\n") if __name__ == "__main__": process_array = [] lock = Lock() for i in range(10): p = Process(target=save_to_file, args=(i, lock)) process_array.append(p) p.start() for p in process_array: p.join() print("done!")
-
-
-
套接字(socket-插座)
通过一个协议, 连接两个进程. 主要就是网络请求.
进程A向百度云上传文件, 进程B向百度云下载文件, 不会有冲突.
-
管道(了解)
用文件的内存缓冲区作为管道, 实现进程间通信
-
匿名管道
主进程和子进程进行交互
-
具名管道
和匿名管道原理是一样的, 不是不相关的进程也可以互相访问
-
-
消息队列
就是一个存在内核内存空间中的列表
redis就是消息队列+socket
from multiprocessing import Queue def save_to_queue(index, my_queue): my_queue.put(index) if __name__ == "__main__": process_array = [] my_queue = Queue() for i in range(10): p = Process(target=save_to_queue, args=(i, my_queue)) process_array.append(p) p.start() for p in process_array: p.join() while True: print(my_queue.get())
-
共享内存(了解)
进程访问内核态同一块内存
from multiprocessing import Queue, Array, Value
-
信号量(了解)
不是用来传递数据的, 是用来传递消息
进程B要等到进程A执行到某一步操作后, 才会启动
进程A->发消息->内核->转发信息->进程B
线程间通信
线程间通信强调的是线程之间传递对象引用
-
共享变量
-
线程安全
线程有GIL锁, 但是拿到GIL锁不代表可以一直执行下去.
现代计算机多线程也是A执行一会儿, B执行一会儿这样交替执行.
import requests import time from threading import Thread zero = 0 def foo(): global zero for i in range(10**7): zero += 1 zero -= 1 if __name__ == "__main__": process_array = [] for i in range(2): p = Thread(target=foo) process_array.append(p) p.start() for p in process_array: p.join() print(zero)
-
解决线程安全
将重要指令包装成原子操作(不可分割的).
- 加互斥锁
import requests import time from threading import Thread,Lock zero = 0 lock = Lock() def foo(): global zero for i in range(10**6): with lock: zero += 1 zero -= 1 if __name__ == "__main__": process_array = [] for i in range(2): p = Thread(target=foo) process_array.append(p) p.start() for p in process_array: p.join() print(zero)
-
迭代器和生成器
-
迭代器
概念上: 迭代器可以用来表示一个数据流, 提供了数据的惰性返回功能(只有我们主动去使用next方法调用, 才会返回值).
实现上: 实现了
__next__
接口的对象传统声明一个列表, 里面的元素会立即写进内存当中, 占用大量内存.
迭代器可以一次只返回一个元素, 占用内存非常小, 在读取大文件和大的数据集合的时候特别有用
-
通过
iter
方法返回一个迭代器对象# 两者实现的功能是一摸一样的 l = list(range(10**7)) l2 = iter(range(10**7))
-
通过
next
方法主动获取迭代器中的值# 当迭代器中没有值了以后, 会抛出StopIteration的异常, 需要大家自行处理一下 l = iter(range(5)) print(next(l)) print(next(l)) print(next(l)) print(next(l)) print(next(l)) print(next(l))
-
-
生成器
生成器是一种特殊的迭代器, 在迭代器惰性返回数据的基础上, 提供了额外的功能, 实现了程序的暂停.
-
声明一个生成器
只要函数体中有
yield
关键词, 它就是一个生成器yield翻译为让渡, 我们可以简单理解为暂停并返回右边的值
def my_range_gen(n): for i in range(n): yield i*i print(f"current index: {i}") my_range = my_range_gen(10) print(my_range) print(next(my_range)) print(next(my_range)) print(next(my_range)) print(next(my_range))
-
-
生成器和迭代器的区别?
同样提供了惰性返回的功能, 迭代器侧重于提供数据的惰性返回功能, 生成器侧重于指令的惰性返回功能
协程
-
协程的原理
协程的实现原理就是生成器的实现原理, 在生成器的基础上又提供了传递值的功能.
-
通过
send
方法向生成器传递值, 以下例子中, b就是通过send方法赋值为2对生成器进行
send
操作一定要调用next
方法预激, 使其停留在第一个yield位置def simple_coro(a): print("初始值 a=", a) b = yield a print("传递值 b=", b) c = yield a + b print("传递值 c=", c) coro = simple_coro(1) print(next(coro)) print(coro.send(2)) print(coro.send(3))
-
用协程实现计算平均数的函数
def coro_avg(): total = 0 length = 0 while True: try: value = yield total/length except ZeroDivisionError: value = yield 0 total += value length += 1 my_avg = coro_avg() print(next(my_avg)) print(my_avg.send(2)) print(my_avg.send(3))
-
yield
和yield from
yield from实现的协程异步程序晦涩难懂, 在python3.4引用asyncio标准库之后被弃用
yield from
用来驱动子程序中的循环并返回最终值def return_triple(): while True: value = yield if value % 3 == 0: return value def triple_recorder(): while True: result = yield from return_triple() triple_array.append(result) triple_array = [] coro = triple_recorder() next(coro) for i in range(100): coro.send(i) print(triple_array)
-
异步I/O
-
asyncio(异步)
Python3.4引入的标准库, 替换yield from实现协程异步IO, 可以更好地实现异步程序
实现原理: 自动维护了一个事件队列, 然后循环访问事件来完成异步的消息维护.
import asyncio import time class Response: staus_code = 200 async def sim_request(index): print(f"模拟发送请求 Index: {index}") response = Response() # 模拟网络延迟 # 当前是单线程运行的, 如果调用的是time.sleep(1), 那么这个线程会被阻塞 # 当前线程被阻塞之后, 不会让渡cpu资源, 异步的效率就不会体现 await asyncio.sleep(1) print(f"request index {index}, response status_code: {response.staus_code}") return response.staus_code # 获取消息队列 loop = asyncio.get_event_loop() # 包装任务 task_array = [] for i in range(100): task_array.append(sim_request(i)) # 循环访问事件来完成异步的消息维护 loop.run_until_complete(asyncio.wait(task_array)) # 关闭事件循环 loop.close()
-
当前异步实际上有没有提高效率, 也关乎到你调用的第三方是不是异步的.
这也是当前python异步的一个痛点, 就是丰富的第三方库不是都支持asyncio的.
-
小技巧: 获取异步完成之后的所有返回值
result = loop.run_until_complete(asyncio.gather(*task_array)) print(result)
-
推荐书籍 <<图解TCP/IP>>
输入网址后发生了什么
-
输入
url
统一资源定位器
uniform resource locator
-
url
组成https://www.baidu.com/ 协议://域名[:端口]/路径 file:///H:/BaiduNetdiskDownload/
-
url
作用定位指定的资源.
url是uri的一个子集, uri是唯一标识符的意思. 身份证可以是uri, 但不是url.
-
-
DNS解析
域名系统
Domain Name System
, 将域名解析为IP
地址-
域名解析流程
域名(www.baidu.com) ->
DNS
服务器->返回真实的IP
地址36.152.44.96:443
-> 通过IP
地址访问服务器
-
-
客户端与服务器建立连接.
客户端和服务端要互相确认身份, 建立连接通道后再发送数据
-
客户端正式向服务端发送请求.
-
服务端处理请求并返回结果
-
浏览器接收到响应后, 做相应的渲染
TCP/IP五层协议
https://www.cnblogs.com/xjtu-lyh/p/12416763.html
-
应用层
为进程(客户端应用)和进程(服务器应用)之间提供服务. 应用层协议定义了应用之间进行数据交互的方式.
浏览网页 网易云 用python模拟请求
- 应用层协议
- HTTP/HTTPS(超文本传输协议)
- DNS(域名系统)
- FTP(文件传输协议)
- SMTP(邮箱传输协议)
- 应用层协议
-
传输层
负责向两个主机应用进程的通信提供服务.
一个主机可以开启不同的因看应用, 同不同的服务器之间进行通信, 但是都是共用一个传输服务来发送和接受信息
进程 <---> 进程
-
传输层协议
-
TCP(传输控制协议)
提供面向连接, (尽可能)可靠的数据传输服务.
一对一
面向连接指的就是, 客户端和服务端进行三次交互验证, 也就是TCP三次握手. 建立连接后才可以发送数据.
- 文件传输(FTP)
- 浏览网页(HTTP)
-
UDP(用户数据协议)
提供无连接的, 不保证数据传输的可靠性
一对多, 一对一, 多对多...
- 直播
- 实况游戏
-
-
-
网络层
决定了数据的转寄和路径选择, 封装和分组运输层产生的报文段/用户数据段.
主机 <---> 主机
-
网络层协议
-
IP协议
-
公网IP
也就是指的传统IP地址, 是唯一的.
-
局域网IP
ipconfig
-
-
-
-
数据链路层
负责两台主机之间的数据传输, 向网路层提供数据传输服务
网卡 <---> 网卡
-
数据链路层的作用
比特流在传输媒介上传输时肯定有误差, 数据链路层的作用就是检错和纠错
- *流量控制
- 差错检测
- 差错控制
-
-
物理层
物理层在局部局域网上传送数据帧, 在设备节点传输比特流.
光纤 <---> 光纤
-
物理层和数据链路层
物理层才是真正传输数据的, 数据链路层是用来检查数据完整性的.
-
理解TCP/IP协议
-
什么是TCP/IP协议
TCP/IP
并不是单个协议, 而是指一组协议的集合, 所以TCP/IP
也叫TCP/IP
协议族. -
TCP/IP的作用
起到了应用和硬件的之间承上启下的作用.
手机的APP应用 -> 路由器 -> 光猫 -> 运营商网络 -> 互联网
TCP/IP三次握手
为了建立可靠的TCP
连接, 尽可能地保证数据传输的正确性.
-
三次握手的过程
- 客户端向服务端发送带有
SYN(同步序列编号)
标识的数据包 --------------------------服务端确认了客户端的发送能力正常 - 服务端向客户端发送了带有
SYN-ACK(确认字符)
标识的数据包-----------------------服务端确认了自己接受能力是正常 - 客户端向服务端返回带有
ACK
标识的数据包-----------------------------------------------服务端确认了自己发送能力, 客户端接受正常
- 客户端向服务端发送带有
-
第2次握手已经传回了
ACK
, 为什么服务端还要返回SYN
?为了告诉客户端, 接收到的信号确实是其发送的信号, 表明了客户端到服务端的通信是正常的.
TCP/IP四次挥手
-
四次挥手的过程
我们以客户端作为主动关闭方来描述四次挥手过程
- 客户端向服务端发送了一个
FIN(finish)
数据包-------------------------------------关闭客户端到服务端的连接通道 - 服务端收到
FIN
后, 返回了ACK
数据包----------------------------------------------------服务端已经知道了客户端到服务端的连接通道已关闭 - 服务端发送
FIN
数据包至客户端, 关闭与客户端的连接------------------------------目的是关闭服务端到客户端的连接通道 - 客户端返回
ACK
数据包确认------------------------------------------------------------------通知服务端客户端已经知道了服务端到客户端之间的连接通道已关闭
- 客户端向服务端发送了一个
HTTPS
-
https加密的过程
-
客户端向服务端发送通信请求
-
服务端返回给客户端证书和密钥
-
客户端通过CA中心验证证书的真实性
-
客户端完成认证之后, 使用公钥对发送数据进行加密, 发送给服务端.
-
非对称加密
16 = 2* 8 也可以是 4 * 4 公钥就是拿到了16这个结果 私钥就是某个因数2 通过这样的方式才可以得出唯一解8
-
-
服务端收到加密后的请求数据后, 使用私钥进行解密.
-
服务器和客户端使用对称加密进行通信
-
-
中间人攻击
插入到客户端和服务端之间的通信, 对服务端伪造客户都安, 对客户端伪造服务端, 拦截通信产生的数据.
-
产生的条件
我们的客户端要主动信任中间人的证书
-
抓包
抓包其实就是中间人攻击, 只是我们会主动信任像fiddler这样的代理软件.
对于服务端, 它伪装成客户端. 对于客户端, 它伪装成服务端.
-
抓包软件
-
Fiddler
https://www.telerik.com/fiddler
-
Charles
-
wireshark
-
-
web端抓包
现代互联网环境几乎都是https协议的网站
-
信任证书
TOOLs -> Options -> HTTPS - 勾选Decrypt HTTPS traffic - 右上角点击Actions - Trust Root Certificates
-
-
App端抓包
下载夜神模拟器
-
打开远程终端连接
Rules -> Options -> Connections -> Allow remote computes to connect
-
把手机/模拟器的代理指向fiddler
- wifi调出设置的时候要长按 - 查看当前fiddler所在pc本地局域网ip - ipconfig/ifconfig
-
在代理项中填写ip地址和fiddler端口, 默认是8888
-
信任证书
-
App有一定的反爬措施, 第一件事就是修改请求协议
-
双向验证
需要客户端也带上证书
-
-
解决请求协议上的反爬措施
- 安装VirtualXposed_0.18.2, JustTrustMe
-
模拟请求
-
PostMan简单使用
-
GET
-
POST
-
form_data
参数表单
-
x-www-form-urlencoded
如果headers中content-type为
x-www-form-urlencoded
, 那么我们需要在当前选项下填写参数 -
raw
请求的真实body内容.
-
-
课后作业
- 学会用fiddler抓包https请求
- 学会用fiddler抓包手机app中的请求
- 学会使用postman模拟GET, POST请求.