栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Python

python基础

Python 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

python基础

编译型与解释型语言的区别:
编译型:开发完成,一次性把所有的代码进行编译成机器能识别的二进制码,在运行。
    代表语言: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'
>>>
>>> 'u4f60u597d'
'你好'

UNICODE标准中以4~6个十六进制数字表示

编码

字符序列(string) -> 字节序列(bytes) -------------编码(encode)

>>> "你好".encode("utf-8")
b'xe4xbdxa0xe5xa5xbd'

字节序列(bytes) -> 字符序列(string) -------------解码(decode)

>>> b
b'xe4xbdxa0xe5xa5xbd'
>>> 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 "", line 1, in 
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 = "<>, <>, <>"
>>> a.split(",")
['<>', ' <>', ' <>']

join(拼接)

>>> b
['<>', ' <>', ' <>']
>>> ",".join(b)
'<>, <>, <>'

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 "", line 1, in 
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

addupdate

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 "", line 1, in 
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 "", line 1, in 
IndexError: list index out of range

KeyError

找不到Key

>>> d = {"a": 1}
>>> d["b"]
Traceback (most recent call last):
  File "", line 1, in 
KeyError: 'b'

ValueError

传入的参数错误

>>> int('a1')
Traceback (most recent call last):
  File "", line 1, in 
ValueError: invalid literal for int() with base 10: 'a1'

TypeError

类型错误, 常见于运算

>>> 1 + '2'
Traceback (most recent call last):
  File "", line 1, in 
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 "", line 1, in 
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
# 返回的是一个匿名函数对象
 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

>>> 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来模拟请求网址

一个模拟请求由以下四个部分组成

urlmethodbodyheaders

模拟请求百度

 没有安装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请求.

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/769215.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号