前言
这篇博客我特意避开了晦涩的底层源码和学术化表述,用大量生活化的类比+可运行的实例,把这三个知识点的原理、常见坑、实战用法全部讲透,哪怕你是刚学完基础语法的新手,看完也能直接用到项目里,甚至轻松应对面试考点。
python闭包装饰器与深浅拷贝
闭包
闭包的作用
闭包可以保存函数内的变量,而不会随着调用完函数而被销毁
闭包的语法
在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,这种:使用外部函数变量的内部函数称为闭包。
python
# 外部函数
def 外部函数名(外部参数):
# 内部函数
def 内部函数名(内部参数):
...[使用外部函数的变量]
return 内部函数名 # 闭包
闭包的构成条件
- 有嵌套:在函数嵌套(函数里面再定义函数)的前提下;
- 有引用:内部函数使用了外部函数的变量(还包括外部函数的参数);
- 有返回:外部函数返回了内部函数名。
定义一个用于求和的闭包。
其中,外部函数有参数num1,内部函数有参数num2,然后调用,并用于求解两数之和
python
# 在函数内嵌套
def func_out(num1):
def func_inner(num2):
# 内部函数使用外部函数变量
num = num1 + num2
print(f"两数的和为:{num}")
# 外部函数返回内部函数
return func_inner
# 创建闭包实例
f = func_out(5)
f(8)

nonlocal关键字
global: 声明全局变量
nonlocal: 声明能够让内部函数去修改外部函数的变量
python
# 在函数内嵌套
def func_out():
num = 100
def func_inner():
#声明变量
nonlocal num
num = num + 10;
print(f"修改后的结果:{num}")
return func_inner
inner = func_out()
inner()

装饰器
装饰器的作用是不改变原有函数的基础上,给原有函数增加额外功能。
装饰器本质上就是一个闭包函数。
装饰器的构成条件
- 有嵌套:在函数嵌套(函数里面再定义函数)的前提下;
- 有引用:内部函数使用了外部函数的变量(还包括外部函数的参数);
- 有返回:外部函数返回了内部函数名;
- 有额外功能:给需要装饰的原有函数增加额外功能。
装饰器语法
方式1: 传统方式:
python
变量名 = 装饰器名(原有函数名)
变量名()
发表评论前,都是需要先登录的。
先定义有发表评论的功能函数,然后在不改变原有函数的基础上,需要提示用户要先登录
python
# 定义一个装饰器
def check(function):
def inner():
print("先登录验证")
function()
return inner
# 需要被装饰的函数
def comment():
print("发表评论")
#使用装饰器装饰函数 (增加一个登录功能)
cmment = check(comment)
cmment()

方式2:
python
语法糖: @装饰器名 (这种方式最常见也最常用)
python
# 定义一个装饰器
def check(function):
def inner():
print("先登录验证")
function()
return inner
# 需要被装饰的函数
@check
def comment():
print("发表评论")
comment()

装饰器的使用
函数分类
python
无参无返回值的函数
定义: def 函数名(): ...
调用: 函数名()
有参无返回值的函数
定义: def 函数名(形参): ...
调用: 函数名(实参)
无参有返回值的函数
定义: def 函数名(): ... return 返回值
调用: 用变量接收返回值 = 函数名()
有参有返回值的函数
定义: def 函数名(形参): ... return 返回值
调用: 用变量接收返回值 = 函数名(实参)
装饰器装饰无参无返回值的函数
在无参无返回值的原有函数求和计算结果之前,添加一个友好提示(注意:不能改变源码):正在努力计算中...。
python
def add(function):
def inner():
print("【友好提示】正在努力计算中...")
function()
return inner
@add
def get_sum():
a = 20
b = 30
print(f"两数之和为:{a+b}")
get_sum()

装饰器装饰有参无返回值的函数
python
def add(function):
def inner(a,b):
print("【友好提示】正在努力计算中...")
function(a,b)
return inner
@add
def get_sum(a,b):
print(f"两数之和为:{a+b}")
get_sum(2,3)

装饰器装饰有参有返回值的函数
python
def add(function):
def inner(a,b):
print("【友好提示】正在努力计算中...")
result = function(a,b)
return result
return inner
@add
def get_sum(a,b):
return a+b
print(get_sum(5,5))

通用装饰器的使用(函数参数是不定长的,有返回值)
python
def add(function):
def inner(*args,**kwargs):
print("【友好提示】正在努力计算中...")
result = function(*args,**kwargs)
return result
return inner
@add
# args元组 kwargs字典
def get_sum(*args,**kwargs):
result = 0
for value in args:
result += value
for value in kwargs.values():
result += value
return result
print(get_sum(5,5,5,a=12,b=13,c=10))

多个装饰器的使用
多个装饰器装饰一个函数
多个装饰器的装饰过程是: 离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程
python
def login(function):
def inner():
print("输入用户名和密码")
function()
return inner
def code(function):
def inner():
print("验证码")
function()
return inner
@login
@code
def comment():
print("发表评论")
comment()

带有参数的装饰器
定义一个既能装饰减法运算,又能装饰加法运算的装饰器,即带有参数的装饰器。
装饰器只能接收一个参数
python
def decorator(function,flag):
def inner(num1, num2):
if flag == '+':
print("正在努力进行加法运算")
elif flag == '-':
print("正在努力进行减法运算")
return function(num1, num2)
return inner
@decorator("+")
def add(a,b):
result = a + b
return result
print(add(2,3))

使用带有参数的装饰器,其实是在装饰器外面又包裹了一个函数,使用该函数接收参数,返回装饰器。
python
def out(flag):
def decorator(function):
def inner(num1, num2):
if flag == '+':
print("正在努力进行加法运算")
elif flag == '-':
print("正在努力进行减法运算")
return function(num1, num2)
return inner
return decorator
@out("+")
def add(a,b):
result = a + b
return result
print(add(2,3))

深浅拷贝
可变类型与不可变类型
不可变对象:一旦创建就不可修改的对象,包括字符串、元组、数值类型(整型、浮点型)
该对象所指向的内存中的值不能被改变。当改变某个变量时候,由于其所指的值不能被改变,相当于把原来的值复制一份后再改变,这会开辟一个新的地址,变量再指向这个新的地址。
可变对象:可以修改的对象,包括列表、字典、集合
该对象所指向的内存中的值可以被改变。变量(准确的说是引用)改变后,实际上是其所指的值直接发生改变,并没有发生复制行为,也没有开辟新的地址,通俗点说就是原地改变。
变量赋值执行原理
python
a = "python"
Python解释器干的事情:
① 创建变量a
② 创建一个对象(分配一块内存),来存储值 'python'
③ 将变量与对象,通过指针连接起来,从变量到对象的连接称之为引用(变量引用对象)

浅拷贝概念与原始数据准备
浅拷贝: 创建新对象,其内容是原对象的引用。
浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已

可变类型浅拷贝

不可变类型浅拷贝

浅拷贝总结
1)当浅复制的值是不可变对象(字符串、元组、数值类型)时和"赋值"的情况一样,对象的id值(id()函数用于获取对象的内存地址)与浅复制原来的值相同。
2)当浅复制的值是可变对象(列表、字典、集合)时会产生一个"不是那么独立的对象"存在。
有两种情况:
第一种情况:复制的对象中无复杂子对象,原来值的改变并不会影响浅复制的值,同时浅复制的值改变也并不会影响原来的值。原来值的id值与浅复制原来的值不同。
第二种情况:复制的对象中有复杂子对象(例如列表中的一个子元素是一个列表),如果不改变其中复杂子对象,浅复制的值改变并不会影响原来的值。 但是改变原来的值中的复杂子对象的值会影响浅复制的值。
深拷贝
深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。所以改变原有被复制对象不会对已经复制出来的新对象产生影响。只有一种形式,copy模块中的deepcopy函数。
可变类型深拷贝

不可变类型深拷贝
不可变类型深拷贝:不可变类型进行深拷贝不会给拷贝的对象开辟新的内存空间,而只是拷贝了这个对象的引用。

网络编程
网络编程三要素
网络
将具有独立功能的多台计算机通过通信线路和通信设备连接起来,在网络管理软件及网络通信协议下,实现资源共享和信息传递的虚拟平台。

能够编写基于网络通信的软件或程序,通常来说就是网络编程。
要使用编程语言实现多台计算机的网络通信,需要具备网络编程三个要素:
(1)IP地址:这是网络环境下每一台计算机的唯一标识,通过IP地址来找到指定的计算机;
(2)端口:用于标识进程的逻辑地址,通过端口来找到指定的进程;
(3)协议:定义通信规则,符合协议则可以通信,否则无法正常通信。
IP地址
IP地址就是标识网络中设备的一个地址,好比现实生活中的家庭地址。

IP地址分为两类: IPv4和IPv6
IPv4:是目前大家使用的IP地址;
IPv6:作为了解,IPv6是未来使用的IP地址。
通过IP地址找到网络中唯一一台设备,也就是说通过IP地址能够找到网络中某台设备,然后可以跟这个设备进行数据通信。

查看IP地址与检查网络
Linux 和 mac OS 使用 ifconfig 这个命令
Windows 使用 ipconfig 这个命令
检查网络是否正常使用 ping 命令
- ping www.baidu.com 检查是否能上公网
- ping 当前局域网的ip地址 检查是否在同一个局域网内
- ping 127.0.0.1 检查本地网卡是否正常
端口和端口号
每运行一个程序都会有一个端口,想要给对应的程序发送数据,找到对应的端口即可。

端口是传输数据的通道,是数据传输必经之路
每一个端口都会有一个对应的端口号,想要找到端口通过端口号即可
端口号可以标识电脑中唯一的一个端口

知名端口号:是指众所周知的端口号,范围从0到1023
动态端口号:一般程序员开发应用程序使用端口号称为动态端口号, 范围是从1024到65535。
TCP协议
TCP的英文全拼(Transmission Control Protocol)简称传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP 通信步骤:
- 创建连接
- 传输数据
- 关闭连接
说明:
TCP通信模型相当于生活中的'打电话',在通信开始之前,一定要先建立好连接,才能发送数据,通信结束要关闭连接

TCP协议创建连接:3次握手
三次握手(Three-Way Handshake)就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立

- 第一次握手:客户端向服务端发送请求,等待服务端确认。
- 第二次握手:服务端收到请求后知道客户端请求建立连接,回复给客户端以确认连接请求。
- 第三次握手:客户端收到确认后,再次发送请求确认服务端,服务端收到正确请求后,如果正确则连接建立成功,完成三次握手,随后客户端与服务端之间可以开始传输数据了。
TCP协议断开连接:4次挥手
四次挥手说TCP断开链接的时候需要经过4次确认。TCP连接是双向,A连接B、B连接A都要断开

- 第一次挥手: 当主机A(可以是客户端也可以是服务端)完成数据传输后, 提出停止TCP 连接的请求
- 第二次挥手: 主机B收到请求后对其作出响应,确认这一方向上的TCP连接将关闭
- 第三次挥手: 主机B 端再提出反方向的连接关闭请求
- 第四次挥手: 主机A对主机B 的请求进行确认,双方向的关闭结束
TCP的特点
面向连接
- 通信双方必须先建立好连接才能进行数据传输,数据传输完成后,需要断开连接,以释放系统资源。
可靠传输
- 都建立了连接,传输数据可靠
- 必须要先建立连接,相对效率较低
- 大多数服务器程序都是使用TCP协议开发的,比如文件下载,网页浏览等
Socket套接字与TCP开发流程
socket套接字
socket(简称 套接字) 是进程之间通信一个工具,好比现实生活中的插座,所有的家用电器要想工作都是基于插座进行,而进程之间想要进行网络通信需要基于这个 socket。

socket使用场景
负责进程之间的网络数据传输,好比数据的搬运工。
只要跟网络相关的应用程序或者软件都使用到了socket。

socket(套接字)能实现不同主机之间的进程间通信。Python中有专门的socket类:
python
# 导入socket模块
import socket
要使用socket,则通常要使用到socket模块下的socket类创建socket对象:

python
import socket
# 创建socket对象
tcp = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print(tcp)
TCP网络应用程序开发分为:
-
TCP客户端程序开发(ps: 浏览器)
-
TCP服务端程序开发
说明:
客户端程序是指运行在用户设备上的程序
服务端程序是指运行在服务器设备上的程序,专门为客户端提供数据服务
在服务器端创建套接字:也叫被动套接字,只负责接受新来的客户端连接
在服务器端:accept()会返回一个新的套接字,用来和客户端收发消息
服务器端:关闭和客户端1、客户端2、客户端n的连接套接字。(不主动关闭)
服务器端:关闭被动套接字 (不主动关闭)

TCP服务器端操作步骤流程说明:
- 创建服务端套接字对象
- 绑定端口号
- 设置监听
- 等待接受客户端的连接请求
- 发送数据
- 接收数据
- 关闭套接字

TCP客户端操作步骤流程:
1.创建客户端套接字对象
2.客户端连接服务器端
3.接收数据
4.发送数据
5.关闭套接字
客户端代码
python
"""
客户端思路:
1. 创建客户端Socket对象, 指定: ipv4, tcp协议
2. 指定客户端 连接服务器端的 ip, 端口号
3. 给服务器端发送消息.
4. 接受服务器端的回执信息.
5. 释放资源.
"""
# 导包
import socket
# 1. 创建客户端Socket对象, 指定: ipv4, tcp协议
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 指定客户端 连接服务器端的 ip, 端口号
client_socket.connect(("127.0.0.1", 12345))
# 3.给服务器端发送消息
client_socket.send('你好服务器端, 我是客户端!'.encode('utf-8'))
# 4. 接受服务器端的回执信息.
# 1次接收1024个字节 解码
data = client_socket.recv(1024).decode('utf-8')
print(f"客户端收到: {data}")
# 5. 释放资源.
client_socket.close()
服务器端代码
python
"""
服务器端思路:
1. 创建服务器端Socket对象, 指定: ipv4, tcp协议
2. 绑定服务器端ip, 端口号
3. 设置最大监听数量.
4. 开启监听, 等待客户端申请建立连接.
5. 接收客户端传输的消息.
6. 给客户端回执信息.
7. 释放资源.
"""
# 导包
import socket
# 1. 创建服务器端Socket对象, 指定: ipv4, tcp协议
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定服务器端ip, 端口号
server_socket.bind(("127.0.0.1", 12345))
# 3. 设置最大监听数量.
server_socket.listen(5)
# 4. 开启监听, 等待客户端申请建立连接.
# 参1: 负责和客户端交互的socket对象.
# 参2: 客户端信息.
accept_socket, client_info = server_socket.accept()
# 5. 接收客户端传输的消息.
# 1次接收1024个字节 解码
data = accept_socket.recv(1024).decode('utf-8')
print(f"服务器端收到: {data}")
# 6. 给客户端回执信息.
accept_socket.send('欢迎来到Scoket的世界'.encode('utf-8'))
# 7. 释放资源.
accept_socket.close()
# server_socket.close()
字符串str与二进制bytes类型转换
在网络中,数据是以二进制数据类型bytes的形式进行传递的, 所以在我们向网络传输数据的时候需要把数据转化成二进制, 从网络中接受到的数据默认也是二进制类型的数据,想要正常使用这些数据也需要把这些数据从二进制类型数据转化为字符串str型。

Tcp编程
说明:
当客户端和服务端建立连接后,服务端程序退出后端口号不会立即释放,需要等待大概1-2分钟。
解决办法有两种:
- 更换服务端端口号
- 设置端口号复用(推荐大家使用),也就是说让服务端程序退出后端口号立即释放。
-
TCP服务端程序必须绑定端口号,否则客户端找不到这个TCP服务端程序,为了更稳定,建议把IP也绑定
-
accept()前的套接字是被动套接字,只负责接收新的客户端的连接请求,不能收发消息
-
当 TCP客户端程序和 TCP服务端程序连接成功后, TCP服务器端程序会产生一个新的套接字,用于收发客户端消息
-
若关闭 accept()返回的被动连接套接字,则表示和这个客户端已经通信完毕
-
对于服务器端socket,关闭时需慎重
-
当客户端的套接字调用 close 后,服务器端的 recv 会解阻塞,返回的数据长度为0,用于判断客户端是否已经下线
总结
这三个知识点覆盖Web开发、爬虫、数据分析、自动化运维几乎所有Python相关方向,搞懂它们相当于给后续的进阶之路打了最扎实的地基。如果你是准备面试的读者,把这几个知识点的常见考题(比如装饰器怎么写、深浅拷贝的区别、TCP和UDP的区别)搞懂,基本就能搞定这一块的所有高频考点。