Python高阶技巧

更新时间: 2024-04-03 20:53:02

# 闭包

在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包

def account_create(initial_amount = 0):
    def atm(num, deposit=True):
        nonlocal initial_amount
        if deposit:
            initial_amount += num
        else:
            initial_amount -= num
    return atm
1
2
3
4
5
6
7
8

# 简单闭包

def outer(logo):
    def inner(msg):
        print(f"<{logo}>{msg}<{logo}>")
    return inner

fn1 = outer("黑马程序员")
fn1("大家好呀") # <黑马程序员>大家好呀<黑马程序员>
fn1("学Python就来") # <黑马程序员>学Python就来<黑马程序员>

fn2 = outer("传智教育")
fn2("IT职业教育培训") # <传智教育>IT职业教育培训<传智教育>
fn2("学Python就来") # <传智教育>学Python就来<传智教育>
1
2
3
4
5
6
7
8
9
10
11
12

注意

需要使用nonlocal关键字修饰外部函数的变量,才可在内部函数中修改它

def outer(num1):
    def inner(num2):
        nonlocal num1
        num1 += num2
        print(num1)
    return inner

fn = outer(10)
fn(10) # 20
fn(20) # 40
1
2
3
4
5
6
7
8
9
10

# 闭包注意事项

优点,使用闭包可以让我们得到:

  • 无需定义全局变量即可实现通过函数,持续的访问、修改某个值
  • 闭包使用的变量的作用域在函数内,难以被错误的调用修改

缺点:

  • 由于内部函数持续引用外部函数的值,所以会导致这一部分内存空间不被释放,一直占用内存。

# 装饰器

装饰器其实也是一种闭包,其功能就是在不破坏目标函数原因的代码和功能的前提下,为目标函数增加新功能。

def sleep():
    import random
    import time
    print("睡眠中......")
    time.sleep(random.randint(1, 5))
1
2
3
4
5

希望给sleep函数,增加一个功能:

  • 在调用sleep前输出:我要睡觉了
  • 在调用sleep后输出:我起床了

# 装饰器的一般写法(闭包写法)

# 定义一个闭包函数,在闭包函数内部:
#   执行目标函数
#   并完成功能的添加
def outer(func):
    def inner():
        print("我要睡觉了")
        func()
        print("我起床了")
    return inner

def sleep():
    import random
    import time
    print("睡眠中......")
    time.sleep(random.randint(1, 5))

fn = outer(sleep)
fn()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 装饰器的语法糖写法

使用@outer定义在目标函数sleep之上

def outer(func):
    def inner():
        print("我要睡觉了")
        func()
        print("我起床了")
    return inner

@outer
def sleep():
    import random
    import time
    print("睡眠中......")
    time.sleep(random.randint(1, 5))

sleep()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 设计模式

设计模式是一种编程套路,可以极大的方便程序的开发。最经典最常见、最经典的设计模式,就是我们所学习的面向对象了。

除了面向对象歪,在编程中也有很多既定的套路可以方便开发,我们称之为设计模式:

  • 单例、工厂模式
  • 建造者、责任链、状态、备忘录、解释器、访问者、观察者、中介、模板、代理模式

# 单例模式

class Tool:
    pass

t1 = Tool()
t2 = Tool()
print(t1)
print(t2)
1
2
3
4
5
6
7

创建类的实例后,就可以得到一个完整的、独立的对象。
通过print语句可以看出,它们的内存地址是不相同的,即t1和t2是完全独立的两个对象。

某些常见下,我们需要一个类无论多少次类对象,都仅仅提供一个具体的实例,用以节省创建类对象的开销和内存开销

比如某些工具类,仅需要1个实例,即可在各处使用

这就是单例模式所要实现的效果。

单例的实现模式:

# 在一个文件中定义如上代码
class StrTools
    pass

str_tool = StrTools()
1
2
3
4
5
# 在另一个文件中导入对象
from test import str_tool

s1 = str_tool
s2 = str_tool

# s1和s2是同一个对象
print(s1)
print(s2)
1
2
3
4
5
6
7
8
9

# 工厂模式

当需要大量创建一个类的实例的时候,可以使用工厂模式
即,从原生的使用类的构造去创建对象的形式
迁移到,基于工厂提供的方法去创建对象的形式

class Person:
    pass

class Worker(Person):
    pass

class Student(Person):
    pass

class Teacher(Person):
    pass

# worder = Worker()
# stu = Student()
# teacher = Teacher()

class Factory:
    def get_person(self, p_type):
        if p_type == 'w':
            return Worker()
        elif p_type == 's':
            return Student()
        else:
            return Teacher()

factory = Factory()
worker = factory.get_person('w')
stu = factory.get_person('s')
teacher = factory.get_person('t')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

使用工厂类的get_person()方法去创建具体的类对象优点:

  • 大批量创建对象的时候有统一的入口,易于代码维护
  • 当发生修改,仅修改工厂类的创建方法即可
  • 符合现实世界的模式,即由工厂来制作产品(对象)

# 多线程并行执行概念

# 进程、线程

现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统。
进程: 就是一个程序,运行在系统之上,那么便称之为这个程序为一个运行进程,并分配进程ID方便系统管理。
现成: 线程是归属于进程的,一个进程可以开启多个线程,执行不同的工作,是进程的实际工作最小单位。

进程就好比一家公司,是操作系统对程序进行运行管理的单位
现成就好比公司的员工,进程可以有多个线程,是进程实际的工作者

操作系统中可以运行多个进程,即多任务运行
一个进程内可以运行多个线程,即多线程运行

注意

进程之间是内存隔离的,即不同的进程拥有各自的内存空间,这就类似于不同的公司拥有不同的办公场所。

线程之间是内存共享的,线程是属于进程的,一个进程内的多个线程之间是共享这个进程所拥有的内存空间的,这就好比,公司员工之间是共享公司的办公场所。

# 并行执行

并行执行的意思指的是同一时间做不同的工作。
进程之间就是并行执行的,操作系统可以同时运行好多程序,这些程序都是在并行执行。

除了进程外,线程其实也是可以并行执行的。一个程序在同一时间做两件乃至多件不同的事情,我们就称之为:多线程并行执行。

# 多线程编程

# threading模块

绝大多数编程语言,都允许多线程编程,Python也不例外。
Python的多线程可以通过threading模块来实现。

import threading

thread_obj = threading.Thread([group [, target [, name [, args [, kwargs]]]]])
# group: 暂时无用,未来功能的预留参数
# target:执行的目标任务名
# args: 以元组的方式给执行任务传参
# kwargs: 以字典方式给执行任务传参
# name: 线程名,一般不用设置

# 启动线程,让线程开始工作
thread_obj.start()
1
2
3
4
5
6
7
8
9
10
11

示例:通过下面代码,即可实现多线程编程,让一个Python程序实现启动两个线程,每个线程各执行一个函数。

import time
import threading

def sing():
    while True:
        print("我在唱歌,啦啦啦...")
        time.sleep(1)

def dance(msg):
    while True:
        print("我在跳舞,哗哗哗...")
        time.sleep(1)

# 创建一个唱歌的线程
sing_thread = threading.Thread(target=sing)

# 创建一个跳舞的线程
dance_thread = threading.Thread(target=dance)

# 让线程去干活吧
sing_thread.start()
dance_thread.start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

需要传参的话可以通过:

  • args参数通过元组(按参数顺序)的方式传参
  • 或使用kwargs参数用字典的形式传参
import time
import threading

def sing(msg):
    while True:
        print(f"我在唱歌,{msg}...")
        time.sleep(1)

def dance(msg):
    while True:
        print(f"我在跳舞,{msg}...")
        time.sleep(1)

# 创建一个唱歌的线程
sing_thread = threading.Thread(target=sing, args=("哈哈哈",))

# 创建一个跳舞的线程
dance_thread = threading.Thread(target=dance, kwargs={"msg":"啦啦啦"})

# 让线程去干活吧
sing_thread.start()
dance_thread.start()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 网络编程

# Socket

socket是进程之间通信的工具,好比显示生活中的插座,所有的家用电器要想工作都是基于插座进行,进程之间想要进行网络通信需要socket。
Socket负责进程之间的网络数据传输,好比数据的搬运工。

# 客户端和服务端

2个进程之间通过Socket进行相互通讯,就必须有服务端和客户端

Socket服务端:等待其它进程的连接,可接受发来的消息,可以回复消息
Socket客户端:主动连接服务端,可以发送消息,可以接收回复

# Socket服务端编程

主要分为如下几个步骤:

  1. 创建socket对象
import socket
socket_server = socket.socket()
1
2
  1. 绑定socket_server到指定IP和地址
socket_server.bind(host, port)
1
  1. 服务端开始监听端口
socker_server.listen(backlog)
# backlog为int整数,表示允许的连接数量,超出的会等待,可以不填,不填会自动设置一个合理值
1
2
  1. 收客户端连接,获得连接对象
conn, address = socket_server.accept()
print(f"接收到客户端连接,连接来自:{address}")
# accept方法是阻塞方法,如果没有连接,会卡再当前这一行不乡下执行代码
# accept返回的事一个二元元组,可以使用上述形式,用两个变量接收二元元组的2个元素
1
2
3
4
  1. 客户端连接后,通过recv方法,接收客户端发送的消息
while True:
    data = conn.recv(1024).decode("UTF-8")
    # recv方法返回值是字节数组(Bytes),可以通过decode使用UTF-8解码为字符串
    # recv方法的传参是buffsize,缓冲区大小,一般设置为1024即可
    if data == 'exit':
        break
    print("接收到发送来的数据:",data)
# 可以通过while True 无限循环来持续和客户端进行数据交互
# 可以通过判定客户端发来的特殊标记,如exit,来退出无限循环
1
2
3
4
5
6
7
8
9
  1. 通过conn(客户端当次连接对象),调用send方法可以回复消息
while True:
    data = conn.recv(1024).decode("UTF-8")
    if data == 'exit':
        break
    print("接收到发送来的数据:",data)

    conn.send("你好呀哈哈哈哈".encode("UTF-8"))
1
2
3
4
5
6
7
  1. conn(客户端当次连接对象)和socket_server对象调用close方法,关闭连接
"""
演示Socket服务器开发
"""
"""
演示Socket服务器开发
"""
import socket
# 构建Socket对象
socket_server = socket.socket()
# 绑定ip地址和端口
socket_server.bind(("localhost",8888))
# 监听端口
socket_server.listen(1)
# 接受客户端信息
conn, address = socket_server.accept()
print(f"接收到了客户端的链接,客户端的信息是:{address}")
while True:
    data: str = conn.recv(1024).decode("UTF-8")
    # 发送回复消息
    print(f"客户发来的消息:{data}")
    msg = input("请输入你要和客户回复的消息").encode("UTF-8")
    if msg == 'exit':
        break
    conn.send(msg)

# 关闭
conn.close()
socket_server.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# Socket客户端编程

主要分为如下几个步骤:

  1. 创建socket对象
import socket
socket_client = socket.socket()
1
2
  1. 连接到服务端
socket_client.connect(("localhost", 8888))
1
  1. 发送消息
while True: # 可以通过无限循环来确保持续的发送消息给服务端
    send_msg = input("请输入要发送的消息")
    if send_msg === 'exit':
        # 通过特殊标记来确保可以退出无限循环
        break
    socket_client.send(send_msg.encode("UTF-8")) # 消息需要编码为字节数组(UTF-8编码)
1
2
3
4
5
6
  1. 接收返回消息
while True: # 可以通过无限循环来确保持续的发送消息给服务端
    send_msg = input("请输入要发送的消息")
    socket_client.send(send_msg.encode("UTF-8"))

    recv_data = socket_client.recv(1024) # 1024是缓冲区大小,一般1024即可
    # recv方法是阻塞式的,即不接收到返回,就卡在这里等待
    print("服务端回复消息为:",recv_data.decode("UTF-8"))
1
2
3
4
5
6
7
  1. 关闭链接
socket_client.close()
1
"""
演示Socket客户端开发
"""
import socket
# 创建socket对象
socket_client = socket.socket()
# 链接到服务器
socket_client.connect(("localhost",8888))
while True:
    # 发送消息
    msg = input("请输入要给服务器发送的消息:")
    if msg == 'exit':
        break
    socket_client.send(msg.encode("UTF-8"))
    # 接收返回信息
    recv_data = socket_client.recv(1024)
    print(f"服务器回复的消息是:{recv_data.decode('UTF-8')}")

# 关闭链接
socket_client.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 正则表达式

# 正则的三个基础方法

Python正则表达式,使用re模块,并基于re模块中三个基础方法来做正则匹配。
分别是: match、search、findall三个基础方法

# re.match(匹配规则,被匹配字符串)

从被匹配字符串开头进行匹配,匹配成功返回匹配对象(包含匹配的信息),匹配不成功返回空。

s = 'python itheima python itheima python itheima'

result = re.match('python', s)
print(result) # <re.Match object; span=(0,6), match='python'>
print(result.span()) # (0, 6)
print(result.group()) # python

s = '1python itheima python itheima python itheima'
result = re.match('python', s)
print(result) # None 从开头开始匹配的,如果开头都没有,后面的就不关心拉
1
2
3
4
5
6
7
8
9
10

# search(匹配规则,被匹配字符串)

搜索整个字符串,找出匹配的。从前向后,找到第一个后,就停止,不会继续向后

s = '1python666itheima666python666'

result = re.search('python', s)
print(result) # <re.Match object; span=(1,7), match='python'>
print(result.span()) # (1,7)
print(result.group()) # python
1
2
3
4
5
6

整个字符串都找不到,返回None

s = 'itheima666'

result = re.search('python', s)
print(result) # None
1
2
3
4

# findall(匹配规则,被匹配字符串)

匹配整个字符串,找出全部匹配项

s = '1python666itheima666python666'

result = re.findall('python', s)
print(result) # ['python', 'python']
1
2
3
4

找不到返回空 list:[]

s = '1python666itheima666python666'

result = re.findall('itcast',s)
print(result) # None
1
2
3
4

# 元字符匹配

s = "itheima1 @@python2 !!666 ##itcast3"

# 找出全部数字
re.findall(r'\d',s) # ['1', '2', '6', '6', '6', '3']
# 字符串的r标记,表示当前字符串是原始字符串,即内部的转义字符无效而是普通字符
# 找出特殊字符
re.findall(r'\W', s) # [' ', '@', '@', ' ', '!', '!', ' ', '#', '#']
# 找出全部英文字母
re.findall(r'[a-zA-Z]',s)
# ['i', 't', 'h', 'e', 'i', 'm', 'a', 'p', 'y', 't', 'h', 'o', 'n', 'i', 't', 'c', 'a', 's', 't']
1
2
3
4
5
6
7
8
9
10

# 递归

递归在编程中是一种非常重要的算法
递归:即方法(函数)自己调用自己的一种特殊编程写法

# 递归找文件

最典型的递归场景为找出一个文件夹中全部的文件

import os

def get_files_recursion_from_dir(path):

    file_list = []
    if os.path.exists(path):
        for f in os.listdir(path):
            new_path = path + "/" + f
            if os.path.isdir(new_path):
                file_list += get_files_recursion_from_dir(new_path)
            else:
                file_list.append(new_path)
    else:
        print(f"路径不存在")
        return []

    return file_list

print(get_files_recursion_from_dir("D:/尚学堂-视频课"))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19