本文目录
  1. 面向对象
    1. 三大特性
    2. 类和对象
    3. self关键字
    4. 添加和获取属性
    5. 魔法方法
      1. 初始化方法init
      2. 字符方法str
      3. 销毁方法del
      4. 案例
    6. 继承
      1. 单继承
      2. 多继承
      3. 方法重写
    7. 封装
      1. 私有属性
      2. 私有方法
    8. 多态
      1. 多态的条件
      2. 多态的好处
      3. 抽象类
    9. 类属性和对象属性
    10. 类方法和静态方法
  2. 虚拟环境
  3. 闭包
  4. 装饰器
    1. 带有参数或返回值的装饰器
    2. 通用装饰器
    3. 【了解】多个装饰器修饰同一个函数
    4. 【了解】装饰器传递参数
    5. 装饰器修饰类中的方法
  5. 【理解】深浅拷贝
    1. 浅拷贝
    2. 深拷贝
    3. 总结
    4. 案例演示
  6. python的编码转换
  7. tcp客户端和服务器端开发
    1. tcp客户端
    2. tcp服务器端
  8. 多任务
    1. 并发
    2. 并行
  9. 多进程
    1. 多进程完成多任务
    2. 进程创建与启动的代码
    3. 进程执行带有参数的任务
    4. 【熟悉】获取进程编号
      1. 进程编号的作用
      2. 两种进程编号
    5. 进程间不共享全局变量
    6. 主进程与子进程的结束顺序
    7. 给进程传递类中的方法
  10. 多线程
    1. 多线程完成多任务
    2. 线程创建与启动代码
    3. 线程执行带有参数的任务
    4. 主线程和子线程的结束顺序
    5. 线程间的执行顺序
      1. 获取当前线程信息
      2. 线程间的执行顺序
公告
淡泊明志,宁静致远
网站资讯
本站文章字数合计
273k
本站Hexo版本
6.1.0
本站Node版本
20.19.6
本站已运行时间
最后更新时间

分类: python | 标签: python

python高级

发表于: 2026-04-29 19:40:28 | 字数统计: 10k | 阅读时长预计: 49分钟

面向对象

面向对象它是一种编程思维,讲究万事万物皆对象(一切皆对象)
面向过程: 核心是关注业务实现的过程和步骤
面向对象: 重点分析对象的属性和行为

三大特性

  • 封装:把属性和方法封装到类中进行隐藏,对外提供访问接口
  • 继承:子承父业 子类继承父类的属性和方法
  • 多态:同一个事物,在不同场景下表现出来的不同形态.如:水: 固体,液体,气体

类和对象

类:对现实事物的抽象描述

对象:现实事物的具体体现

# 1.定义类:class 类名:
class Car:
    # 1.1.定义方法:跑起来
    def run(self):
        print('能跑起来...')

# 2.创建对象:对象名 = 类名()
car = Car()
# 3.调用方法:对象.方法名()
car.run()

self关键字

self也是Python内置的关键字之一,其指向了对象实例本身【对象自己】。

谁调用类内部函数,self就是谁

# 1.定义类:class 类名:
class Car:
    # 1.1.定义方法:跑起来
    def run(self):
        print(f'self的结果{self}')
        print('能跑起来...')


# 2.创建对象:对象名 = 类名()
car = Car()
print(f'car对象结果{car}')
# 3.调用方法:对象.方法名()
car.run()

print('*' * 30)
# 3.创建对象:对象名 = 类名()
car2 = Car()
print(f'car2对象结果{car2}')
car2.run()

添加和获取属性

添加

# 1.在类外部
对象名.属性名 = 值

# 2.在类内部 __init__() 函数
self.属性 = 值

获取

在类的内部: self.属性
在类的外部: 对象.属性

魔法方法

魔法方法: 与普通方法区别

  • 自动调用,特殊情况下
  • 前后被双下滑线包围

初始化方法init

当一个实例被创建时调用的初始化方法。

# 1.定义类
class Car:
    # 1.1 设置属性:__init__
    def __init__(self, color, number):
        self.color = color
        self.number = number

    # 1.2 获取属性: self.属性名
    def show(self):
        print(f'车身颜色:{self.color}')
        print(f'车轮数:{self.number}')


# 2.创建对象:对象名 = 类名()
car = Car('black', 8)

# 2.1 获取属性
# 类内部
car.show()
# 类外部
# print(car.color)
# print(car.number)

字符方法str

当使用print输出对象时,默认打印对象的内存地址值【十六进制】。 若要让输出对象名时,不是内存地址值,应该要在类中定义str方法。如果类定义了__str__方法,那么就会打印从在这个方法中 return的数据。

# 1,定义类
class Car:
    # 1.1 属性的设置
    def __init__(self):
        self.color = '红色'
        self.number = 4

    # 1.2 str:输出属性信息
    def __str__(self):
        return f'{self.color}的汽车有{self.number}个车轮'

# 2.创建对象
car = Car()

# 3.打印对象
print(car)

销毁方法del

当一个实例被销毁时调用的方法

# 1.定义类
class Car:
    # 1.1 属性赋值
    def __init__(self,color):
        self.color = color

    # 1.2 del魔法
    def __del__(self):
        print('自动调用了del魔法方法')

# 2.创建对象
car = Car('红色')
# 3.获取属性
# print(car.color)
del car
print(car.color)

案例

"""
需求:
    定义一个 地瓜类, 属性为: 被烤的时间(cook_time), 地瓜的生熟状态(cook_state), 添加的调料(condiments).
    行为有: cook() 表示烘烤,  add_condiment() 表示 添加调料.
    请用所学, 用面向对象的思维完成这个事情.

烘烤规则(时间及其对应的状态):
    0 ~ 3分钟     生的
    3 ~ 7分钟     半生不熟
    7 ~ 12分钟    熟了
    超过12分钟     已烤焦, 糊了
"""
class Potato:
    def __init__(self):
        self.cook_time = 0
        self.cook_state = '生的'
        self.condiments = []

    def __str__(self):
        return f'时间:{self.cook_time}分钟,状态:{self.cook_state},调料:{self.condiments}'

    def cook(self, cook_time):
        self.cook_time = cook_time
        if 0 <= cook_time <= 3:
            self.cook_state = '生的'
        elif cook_time <= 7:
            self.cook_state = '半生不熟'
        elif cook_time <= 12:
            self.cook_state = '熟了'
        else:
            self.cook_state = '已烤焦, 糊了'

    def add_condiment(self, condiment):
        self.condiments.append(condiment)

if __name__ == '__main__':
    p = Potato()
    p.add_condiment('辣椒')
    p.add_condiment('五香粉')
    p.add_condiment('孜然粉')
    p.cook(8)
    print(p)

继承

单继承

class Master:
    def __init__(self):
        self.konfu = '古法煎饼技术'

    def make_cake(self):
        print(f'师傅采用{self.konfu},制作煎饼')


class Prentice(Master): #继承Master
    pass


if __name__ == '__main__':
    p = Prentice()
    print(p.konfu)
    p.make_cake()

多继承

class Master:
    def __init__(self):
        self.konfu = '古法煎饼技术'

    def make_cake(self):
        print(f'师傅采用{self.konfu},制作煎饼')

class School:
    def __init__(self):
        self.konfu = '现代煎饼技术'

    def make_cake(self):
        print(f'黑马采用{self.konfu},制作煎饼')

class Prentice(School,Master): #继承School和Master
    pass


if __name__ == '__main__':
    p = Prentice()
    print(p.konfu)
    p.make_cake()

当一个类有多个父类时,默认使用第一个父类的同名属性和方法,可以使用类名**.mro**属性或类名.mro()方法查看调用的先后顺序。

注:MRO(Method Resolution Order):方法解析顺序

image-20230330111528253

print(Tudi.__mro__)
print(Tudi.mro())

方法重写

当子类属性或方法与父类的属性或方法名字相同的时候,从父类继承下来的成员可以重新定义!

# 故事4: 很多顾客都希望吃到 徒弟自研的煎饼果子, 也有 黑马配方的煎饼果子味道. 请用所学, 模拟这个知识点.
# 1. 定义师傅类Master.
class Master(object):
    # 1.1 属性, kongfu = '[古法摊煎饼果子技术]'
    def __init__(self):
        self.kongfu = '[古法摊煎饼果子技术]'

    # 1.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')


# 2. 定义黑马学校类School.
class School(object):
    # 2.1 属性, kongfu = '[黑马AI摊煎饼果子技术]'
    def __init__(self):
        self.kongfu = '[黑马AI摊煎饼果子技术]'

    # 2.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')


# 3. 定义徒弟类Prentice, 继承自 师傅类.
class Prentice(School,Master):     # 多继承, 同名属性和行为, 优先参考第1个父类, 即: 从左往右的顺序.
    # 3.1 属性
    def __init__(self):
        self.kongfu = '[独创(自研) 摊煎饼果子技术]'

    # 3.2 行为, make_cake(), 表示: 摊煎饼.
    def make_cake(self):
        print(f'采用 {self.kongfu} 制作煎饼果子!')

    # 3.3 行为, make_master_cake(),  从老师傅继承过来的 煎饼果子配方.
    def make_master_cake(self):
        Master.__init__(self)
        Master.make_cake(self)

    # 3.4 行为, make_school_cake(),  从黑马学校继承过来的 煎饼果子配方.
    def make_school_cake(self):
        School.__init__(self) # 类名.父类方法名() 调用
        School.make_cake(self) # 类名.父类方法名() 调用

    # 3.5 行为, make_old_cake(),  从 父类(super)继承过来的 煎饼果子配方.
    def make_old_cake(self):
        super().__init__() # super().父类方法名() 调用 适合单继承
        super().make_cake() # super().父类方法名() 调用 适合单继承


# 在main函数中测试.
if __name__ == '__main__':
    # 4. 创建徒弟类对象.
    p = Prentice()
    # 自研的.
    p.make_cake()
    # 5. 打印 徒弟类对象 从 Master(老师父类)继承过来的 行为.
    p.make_master_cake()
    # 6. 打印 徒弟类对象 从 School(黑马学校类)继承过来的 行为.
    p.make_school_cake()
    # 7. 打印 徒弟类对象 从 父类继承过来的 行为. 即: 旧的煎饼果子配方.
    p.make_old_cake()

封装

私有属性

在属性名前面加上两个下划线 __

  • 只能在类内部访问
  • 定义公有方法获取get_xxx或设置set_xxx私有属性值

私有方法

在方法名前面加上两个下划线 __

  • 只能在类内部访问
  • 定义公有方法调用私有方法
class Master:
    def __init__(self):
        self.__money = 200 #定义私有属性

    def get_money(self):
        return self.__money # 获取私有属性

    def __make_cake(self): # 定义私有方法
        print('制作煎饼技术。。。')

    def make_cake2(self):
        self.__make_cake() # 获取私有方法


class Prentice(Master):
    pass


if __name__ == '__main__':
    p = Prentice()
    # print(p.money)
    print(p.get_money())
    p.make_cake2()

多态

同样一个函数在不同的场景下有不同的状态

多态的条件

1、有继承 (定义父类、定义子类,子类继承父类)
2、函数重写 (子类重写父类的函数)
3、父类引用指向子类对象 (子类对象传给父类对象调用者)

多态的好处

1、在不改变框架代码的情况下,通过多态语法轻松的实现模块和模块之间的解耦合;实现了软件系统的可拓展

2、对解耦合的大白话解释:搭建的平台函数def object_play(herofighter:HeroFighter, enemyfighter:EnemyFighter) 相当于任务的调用者;子类、孙子类重写父类的函数,相当于子任务;相当于任务的调用者和任务的编写者进行了解耦合

3、对可拓展的大白话解释: 搭建的平台函数def object_play(herofighter:HeroFighter, enemyfighter:EnemyFighter),在不做任何修改的情况下,可以调用后来人写的代码

4、对“继承和多态对照理解”大白话解释:
    继承相当于:孩子可以复用老爹的东西。
    多态相当于:老爹框架,不做任何修改的情况下,可以可拓展的使用后来人(孩子)写的东西。

案例:动物类案例

class Animal:
    def speek(self):
        print('动物在叫。。。')


class Cat(Animal):
    def speek(self):
        print('猫在叫。。。')


class Dog(Animal):
    def speek(self):
        print('狗在叫。。。')


# 4. 定义函数 make_noise(动物类对象), 接收动物对象, 实现: 传入什么动物, 就怎么叫.
# 函数: make_noise(an: Animal):  # an:Animal 意思是: an "必须" 是Animal类的对象 或者 其子类对象.
def make_noise(an: Animal):
    an.speek()


# 在main方法中测试.
if __name__ == '__main__':
    # 5. 分别创建猫类, 狗类对象.
    cat = Cat()
    dog = Dog()
    make_noise(cat)
    make_noise(dog)

抽象类

概述:在Python中, 抽象类也叫接口, 指的是: 有抽象方法的类, 就叫抽象类.
抽象方法:没有方法体的方法, 即: 空实现的方法, 用pass修饰, 就叫: 抽象方法.
用法: 抽象类一般充当 父类, 即: 制定整个继承体系的 标准(规范)
      具体的体现, 实现交由 子类来完成.

案例

image-20260506200913180

类属性和对象属性

对象属性: 属于每个对象的属性, 即: A对象修改了他的属性值, 不会影响B对象的属性值.
类属性:   它是属于该类的所有对象 所共享的, 即: A对象修改了类属性, 则B对象用的是 修改后的属性值.
        
对象属性 相关格式:
    定义格式:
        类外: 对象名.属性名 = 属性值
        类内: self.属性名 = 属性值
    获取属性值的格式:
        类外: 对象名.属性名
        类内: self.属性名

类属性:
    定义格式:
        定义在类中, 函数外的 变量, 就叫: 类变量, 它能被该类下所有的对象所共享.
    调用格式:
        方式1: 类名.类属性名            # 推荐使用.
        方式2: 对象名.类属性名          # 可以用, 但是不推荐.

细节: 什么时候定义类变量, 什么时候定义对象变量?
    如果1个变量是被该类下所有的对象所共享的, 就考虑定义成: 类变量(类属性), 否则定义成: 对象变量(对象属性).

类方法和静态方法

类方法:
1. 第1个参数必须是 当前类的对象, 一般用 cls当做变量名(即: class)
2. 类方法必须通过 @classmethod 来修饰.
3. 类方法是属于 类的方法, 能被该类下所有的对象所共享.
4. 可以通过 类名. 或者 对象名. 的方式调用, 推荐: 前者.

静态方法:
1. 静态方法没有参数的硬性要求, 可以1个参数都不传.
2. 静态方法必须通过 @staticmethod 来修饰.
3. 类方法是属于 类的方法, 能被该类下所有的对象所共享.
4. 可以通过 类名. 或者 对象名. 的方式调用, 推荐: 前者.

区别:
类方法 和 静态方法的区别: 要不要传参, 即: 第一个参数是写 还是 不写, 再简单点说: 是否需要使用 该类的对象, 用就定义成 类方法, 不用就定义成 静态方法.

案例

class Student(object):
    # 1.1 老师名字.
    teacher_name = '王宝强'

    # 1.2 name属性, 每个学生的名字都不一样, 所以定义成: 对象属性.
    def __init__(self, name):
        self.name = name

    # 1.3 定义静态方法, 访问: teacher_name 这个类变量.
    @staticmethod
    def get_static_name():
        print(f'teacher_name={Student.teacher_name}')
        
    # 1.4 定义类方法, 访问: teacher_name 这个类变量.
    @classmethod
    def get_class_name(cls):
        print(f'teacher_name={cls.teacher_name}')

if __name__ == '__main__':
    # 2. 创建学生对象.
    Student.get_class_name()

    Student.get_static_name()

虚拟环境

创建和切换

conda create -n ollama_env python=3.10 -y # 创建名为ollama_env的虚拟环境
conda activate ollama_env # 切换到ollama_env环境
conda remove -n ollama_env --all #删除虚拟环境
conda env list # 查看当前已经创建的虚拟环境列表

批量安装依赖

requirements.txt

streamlit>=1.35.0
ollama>=0.2.1

安装

pip install -r D:\requirements.txt

PyCharm加载虚拟环境

在pycharm右下角的虚拟环境名称中单击:Add New Interpreter - Add Local Interpreter

进入之后按下面三步加载

image-20260507104709802

闭包

1- 函数定义【有嵌套】:外层函数的里面定义了另外一个函数
2- 外层函数【返回内层函数的名称】。注意:内层函数名称的后面不可以有小括号
3- [可选]内层函数可以使用外层函数的变量

案例

# 函数嵌套
def func_outer():
    def func_inner():
        print("内层函数被执行了")
    return func_inner #返回内层函数的名称


def func_outer2(num1):
    def func_inner2(num2):
        return num1+num2 #内层函数可以使用外层函数的变
    return func_inner2

if __name__ == '__main__':
    my_fn = func_outer()
    my_fn()

    print("-"*30)

    my_fn2 = func_outer2(10)
    print(my_fn2(20))

装饰器

作用在不改变现有函数源代码以及函数调用方式的前提下,实现对函数的增强。

装饰器的本质就是一个闭包函数(三步:① 有嵌套 ② 有引用 ③ 有返回)

有返回代表外部函数返回内部函数的内存地址(内部函数的名称),不带小括号

装饰器总结:
    1- 作用:在不改变原有函数的定义、调用的代码情况下,增强函数的功能
    2- 装饰器是一个特殊闭包
    3- 装饰器的语法要求:
        3.1- 有函数定义的嵌套
        3.2- 外层函数返回内层函数的名称
        3.3- 外层函数的形参有且只能有一个,用来接收被装饰/被增强的原始函数

案例

"""
    需求:统计某个函数运行的耗时
"""
import time

# 定义装饰器
def outer_fn(old_fn):
    def inner_fn():
        # time.time()获取当前时间戳(从1970-01-01 00:00:00到目前过去的(毫)秒数)
        start = time.time()
        old_fn()
        end = time.time()
        print(f"函数运行耗时:{end - start}")
    return inner_fn

#使用装饰器
@outer_fn
def my_sum():
    total_result = 0
    for i in range(1000000):
        total_result += i
    print(f"求和的结果:{total_result}")

if __name__ == '__main__':
    my_sum()

带有参数或返回值的装饰器

import time

# 4- 原始函数有参数,有返回值
def outer_func_4(old_func):
    def inner_func_4(start,end,step): #内层函数的形参接受原始函数返回值
        start_time = time.time()

        result = old_func(start,end,step)

        use_time = time.time() - start_time
        print(f"运行耗时:{round(use_time, 4)}")
        return result #内层函数返回原始函数调用之后的返回值
    return inner_func_4

@outer_func_4
def my_sum_4(start,end,step):
    result = 0
    for i in range(start,end,step):
        result += i
    return result

if __name__ == '__main__':
    print(my_sum_4(0, 10000001, 2))

通用装饰器

import time

# 通用装饰器【掌握】
def outer(old_fn):
    def inner(*args, **kwargs):
        start = time.time()
        """
            注意:传递给原始函数的参数,必须带上星号。
                如果不带old_fn(args, kwargs),这样是两个参数,args是一个元组,kwargs是一个字典
                带上星号,它会自动的进行拆包
        """
        fn_result = old_fn(*args, **kwargs)

        print("运行耗时:",time.time() - start)

        return fn_result

    return inner

# 有多个参数,有返回值
@outer
def my_sum_3(start,end):
    total = 0
    for i in range(start,end):
        total += i
    return total

if __name__ == '__main__':

    result_3 = my_sum_3(0,1000000)
    print("my_sum_3 累加结果是:", result_3)

【了解】多个装饰器修饰同一个函数

多个装饰器的装饰过程是: 离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程

案例

def outer_func_1(old_func):
    def inner_func_1(*args,**kwargs):
        print("a 第1个装饰器被调用了")
        result = old_func(*args,**kwargs)
        return result
    return inner_func_1

def outer_func_2(old_func):
    def inner_func_2(*args,**kwargs):
        print("b 第2个装饰器被调用了")
        result = old_func(*args,**kwargs)
        return result
    return inner_func_2

@outer_func_1
@outer_func_2
def my_sum():
    result = 0
    for i in range(10000001):
        result += i
    print(f"c 累计求和结果是:{result}")

if __name__ == '__main__':
    my_sum()

image-20260510224344127

【了解】装饰器传递参数

带有参数的装饰器语法总结:
    1- 装饰器需要3层嵌套
    2- 每层的作用如下:
        2.1- 最外层:只负责接收装饰器自己需要的参数
        2.2- 中间层:只负责接收被增强/被装饰的原始函数
        2.3- 最里层:只负责接收被增强/被装饰的原始函数需要的参数

基本语法

def 装饰器(fn):
    ...

@装饰器('参数')
def 函数():
    # 函数代码

案例

import time

def wrapper(num_cnt):
    def outer_func(old_func):
        def inner_func(*args, **kwargs):
            start_time = time.time()

            result = old_func(*args, **kwargs)

            use_time = time.time() - start_time
            print(f"运行耗时:{round(use_time, num_cnt)}")
            return result

        return inner_func
    return outer_func


@wrapper(num_cnt=4)
def my_sum(start, end, step):
    result = 0
    for i in range(start,end,step):
        result += i
    return result

@wrapper(num_cnt=10)
def my_func():
    result = 0
    for i in range(100000):
        result += i
    return result

if __name__ == '__main__':
    final_result = my_sum(1,10000001,3)
    print(final_result)

    print("-"*30)

    final_result2 = my_func()
    print(final_result2)

装饰器修饰类中的方法

装饰器既能够在面向过程中去装饰函数,也能够在面向对象中装饰方法

import time
class Funcs:

    @staticmethod
    def wrapper(num_cnt):
        def outer_func(old_func):
            def inner_func(*args, **kwargs):
                start_time = time.time()

                result = old_func(*args, **kwargs)

                use_time = time.time() - start_time
                print(f"运行耗时:{round(use_time, num_cnt)}")
                return result

            return inner_func

        return outer_func

    @wrapper(num_cnt=4)
    def my_sum(self, start, end, step):
        result = 0
        for i in range(start, end, step):
            result += i
        return result

    @wrapper(num_cnt=10)
    def my_func(self):
        result = 0
        for i in range(100000):
            result += i
        return result

if __name__ == '__main__':
    # 创建类的实例对象
    obj = Funcs()

    # 调用实例方法
    final_result = obj.my_sum(1, 10000001, 3)
    print(final_result)

    print("-" * 30)

    final_result2 = obj.my_func()
    print(final_result2)

【理解】深浅拷贝

浅拷贝

浅拷贝: 创建新对象,其内容是原对象的引用。

浅拷贝之所以称为浅拷贝,是它仅仅只拷贝了一层,拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。

案例1:赋值

image-20210121124612944

1744103224485

案例2:可变类型浅拷贝

image-20210121124816952

案例3:不可变类型浅拷贝

image-20210121124927773

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

深拷贝

深拷贝:和浅拷贝对应,深拷贝拷贝了对象的所有元素,包括多层嵌套的元素。深拷贝出来的对象是一个全新的对象,不再与原来的对象有任何关联。

所以改变原有被复制对象不会对已经复制出来的新对象产生影响。只有一种形式,copy模块中的deepcopy函数。

可变类型深拷贝:

image-20210121125125964

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

image-20210121125301655

总结

深浅拷贝
    1- 浅拷贝
        可变类型:只会拷贝第一层内容的内存地址,更深层次不受到影响
        不可变类型:浅拷贝没有任何变化,没有使用价值

    2- 深拷贝
        可变类型:不管嵌套多少层,重新开辟新的空间,拷贝前后之间没有任何关系,彻底独立
        不可变类型:深拷贝没有任何变化,没有使用价值     
        
总结:可变类型的浅拷贝只拷贝第一层的内存地址,深拷贝无论多少层都会重新开辟空间。不可变类型的深浅拷贝没有任何变化。        

案例演示

课堂代码:

import copy

# 1- 可变、不可变类型的浅拷贝
def demo01():
    # 可变类型 的 浅拷贝
    a = [1, 2, 3]
    b = [11, 22, 33]
    data = [a, b]
    print(f"data的内容:{data}")
    print(f"data的内存地址:{id(data)}")

    # 执行浅拷贝
    data_copy = copy.copy(data)
    print(f"data_copy的内容:{data_copy}")
    print(f"data_copy的内存地址:{id(data_copy)}")

    print("-"*30)

    # 修改内容
    data[0][1] = 99
    print(f"data的内容:{data}")
    print(f"data_copy的内容:{data_copy}")


    print("="*50)

    # 不可变类型 的 浅拷贝
    a = (1, 2, 3)
    b = (11, 22, 33)
    data = (a, b)
    print(f"data的内容:{data}")
    print(f"data的内存地址:{id(data)}")

    # 执行浅拷贝
    data_copy = copy.copy(data)
    print(f"data_copy的内容:{data_copy}")
    print(f"data_copy的内存地址:{id(data_copy)}")

# 2- 可变、不可变类型的深拷贝
def demo02():
    # 可变类型 的深拷贝
    a = [1, 2, 3]
    b = [11, 22, 33]
    data = [a,b]
    print(f"data的内容:{data}")
    print(f"data的内存地址:{id(data)}")

    # 执行深拷贝
    data_copy = copy.deepcopy(data)
    print(f"data_copy的内容:{data_copy}")
    print(f"data_copy的内存地址:{id(data_copy)}")

    print("-"*30)

    # 修改
    data[0][1] = 99
    data_copy[1][1] = 666
    print(f"data的内容:{data}")
    print(f"data的内存地址:{id(data)}")
    print(f"data_copy的内容:{data_copy}")
    print(f"data_copy的内存地址:{id(data_copy)}")

    print("="*50)

    # 不可变类型 的深拷贝
    a = (1, 2, 3)
    b = (11, 22, 33)
    data = (a, b)
    print(f"data的内容:{data}")
    print(f"data的内存地址:{id(data)}")

    # 执行深拷贝
    data_copy = copy.deepcopy(data)
    print(f"data_copy的内容:{data_copy}")
    print(f"data_copy的内存地址:{id(data_copy)}")

# 3- 可变 嵌套 不可变类型的深拷贝
def demo03():
    a = 1   # 不可变类型
    print(id(a))
    a = 99
    print(id(a))

    b = 2   # 不可变类型
    data = [a, b]   # 可变类型

    # 浅拷贝
    # data_copy = copy.copy(data)

    # 深拷贝
    data_copy = copy.deepcopy(data)
    print(data, id(data))
    print(data_copy, id(data_copy))

    print("-" * 30)
    data[0] = 99
    print(data, id(data))
    print(data_copy, id(data_copy))

if __name__ == '__main__':
    # 1- 可变、不可变类型的浅拷贝
    # demo01()

    # 2- 可变、不可变类型的深拷贝
    # demo02()

    # 3- 可变 嵌套 不可变类型的深拷贝
    demo03()

demo03的原理图:

image-20260508173915374

python的编码转换

数据转换方法说明:

函数名说明
encode编码 将字符串转化为字节码
decode解码 将字节码转化为字符串

提示:encoed()和decode()函数可以接受参数,encoding是指在编解码过程中使用的编码方案。

字符串编码:

str.encode(encoding=”utf-8”)

二进制解码:

bytes.decode(encoding=“utf-8”)

代码示例:

if __name__ == '__main__':
    # 注意:编码、解码需要使用相同的编码集

    # 编码:将字符串 转成 二进制
    content = "大模型工程师的工资有多少"
    bytes_result = content.encode(encoding="UTF-8")
    print(bytes_result)

    # 解码:将二进制 转成 字符串
    decode_result = bytes_result.decode(encoding="UTF-8")
    print(decode_result)

tcp客户端和服务器端开发

tcp客户端

import socket

# 查看源代码的快捷键: ctrl+左键   ctrl+q
if __name__ == '__main__':
    # 1- 创建Socket工具
    """
        参数解释:
            family=socket.AF_INET:使用IPv4
            type=socket.SOCK_STREAM:数据以二进制流的形式传递
    """
    socket_obj = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

    # 2- 建立与服务器的连接
    # windows中查看端口的占用情况:netstat -ano | findstr "8888"
    socket_obj.connect(("127.0.0.1",8888))

    # 3- 发送消息给到服务器
    msg = "土豆土豆,我是恁爹"
    socket_obj.send(msg.encode(encoding="UTF-8"))

    # 4- 接收响应结果
    # 1024:表示每次最多接收1024字节大小的内容
    bytes_result = socket_obj.recv(1024)
    str_result = bytes_result.decode(encoding="UTF-8")
    print(f"客户端接收到的响应内容:{str_result}")

    # 5- 关闭连接
    socket_obj.close()

tcp服务器端

import socket

if __name__ == '__main__':
    # 1- 创建Socket工具
    socket_obj = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

    # 2- 将服务绑定到指定IP和端口
    socket_obj.bind(("127.0.0.1",8888))

    # 3- 监听客户端
    # 10表示服务端同一时刻最多能够为10个客户端服务
    socket_obj.listen(10)

    # 4- 接收客户端的请求
    """
        返回值解释:
            client_socket_obj:每来一个新的客户端连接,那么服务端就会新创建一个连接对象,专门为该客户端服务
            client_addr_info:连接过来的客户端信息
    """
    client_socket_obj, client_addr_info = socket_obj.accept()

    # 5- 接收客户端发送过来的数据
    recv_msg_bytes = client_socket_obj.recv(1024)
    recv_msg_str = recv_msg_bytes.decode(encoding="UTF-8")
    print(f"服务端接收到的消息内容:{recv_msg_str}")

    # 6- 将响应结果返回给到客户端
    client_socket_obj.send("好的,你是我儿".encode(encoding="UTF-8"))

    # 7- 关闭连接
    client_socket_obj.close()
    socket_obj.close()

多任务

多任务是指在同一时间内执行多个任务。

例如: 现在电脑安装的操作系统都是多任务操作系统,可以同时运行着多个软件。

并发

在一段时间内【交替】去执行多个任务。ps:联想发胶,并发是交替执行

并行

在一段时间内【同时】一起执行多个任务

多进程

进程(Process)是CPU资源分配的最小单位,它是操作系统进行资源分配和调度运行的基本单位,通俗理解:一个正在运行的程序就是一个进程。

例如:正在运行的qq , 微信等 他们都是一个进程。

注: 一个程序运行后至少有一个进程

多进程完成多任务

① 导入进程包
import multiprocessing

② 通过进程类创建进程对象 
进程对象 = multiprocessing.Process([group [, target=任务名 [, name]]])

③ 启动进程执行任务
进程对象.start()

Process的参数说明:

参数名说明
target执行的目标任务名,这里指的是函数名(方法名)
name进程名,一般不用设置
group进程组,目前只能使用None

进程创建与启动的代码

import time
import multiprocessing

def sing():
    for i in range(100):
        print(f"唱歌_{i}")

        # 休息0.1秒钟
        time.sleep(0.1)

def dance():
    for i in range(100):
        print(f"跳舞_{i}")
        time.sleep(0.1)

if __name__ == '__main__':
    # 创建子进程
    """
        参数解释:
            target:子进程需要负责的工作内容。传递的是函数名称,注意不能加小括号
    """
    process_1 = multiprocessing.Process(target=sing)
    process_2 = multiprocessing.Process(target=dance)

    # 启动子进程
    process_1.start()
    process_2.start()

    # 主进程的代码
    print("这是主进程的代码")

进程执行带有参数的任务

Process([group [, target [, name [, args [, kwargs]]]]])

参数说明:

参数名说明
args以元组的方式给执行任务传参,args表示调用对象的位置参数元组,args=(1,2,’anne’,)
kwargs以字典方式给执行任务传参,kwargs表示调用对象的字典,kwargs={‘name’:’anne’,’age’:18}

案例:args参数和kwargs参数的使用

参数传递总结。给进程中的函数传递参数,有如下两种写法:
    1- 第一种:通过args传递元组,参数值的顺序需要与参数顺序保持一致
    2- 第二种:通过kwargs传递字典,字典中key的名称需要与参数名称保持一致
    
    使用推荐:如果参数比较多,推荐使用kwargs的形式;否则推荐用元组。
import time
import multiprocessing

def sing():
    for i in range(10):
        print(f"唱歌_{i}")

        # 休息0.1秒钟
        time.sleep(0.1)

def dance():
    for i in range(10):
        print(f"跳舞_{i}")
        time.sleep(0.1)

if __name__ == '__main__':
    # 创建子进程
    """
        参数解释:
            target:子进程需要负责的工作内容。传递的是函数名称,注意不能加小括号
    """
    process_1 = multiprocessing.Process(target=sing)
    process_2 = multiprocessing.Process(target=dance)

    # 启动子进程
    process_1.start()
    process_2.start()

    # 主进程的代码
    print("这是主进程的代码")

【熟悉】获取进程编号

进程编号的作用

当程序中进程的数量越来越多时 , 如果没有办法区分主进程和子进程还有不同的子进程 , 那么就无法进行有效的进程管理 , 为了方便管理实际上每个进程都是有自己编号的。

两种进程编号

① 获取当前进程编号

getpid()

② 获取当前进程的父进程ppid = parent pid

getppid()

③ 案例:获取父进程与子进程编号

import os
import multiprocessing
import time

def sing():
    for i in range(10):
        print(f"唱歌_{i},进程编号是{os.getpid()},父进程编号是{os.getppid()}")
        time.sleep(1)

def dance():
    for i in range(10):
        print(f"跳舞_{i},进程编号是{os.getpid()},父进程编号是{os.getppid()}")
        time.sleep(1)

if __name__ == '__main__':
    # 创建子进程
    process_1 = multiprocessing.Process(target=sing)
    process_2 = multiprocessing.Process(target=dance)

    # 启动子进程
    process_1.start()
    process_2.start()

    print(f"主进程的代码,进程编号是{os.getpid()},父进程编号是{os.getppid()}")

运行效果截图:

image-20250702154001266

进程间不共享全局变量

实际上==创建一个子进程就是把主进程的资源进行拷贝产生了一个新的进程==,这里的主进程和子进程是互相独立的。

进程中的全局变量总结:
   1- 主进程、各个子进程之间不共享全局变量
   2- 如何实现不共享全局变量:各个进程将全局变量全部都深拷贝一份,各自用各自的

image-20210112154216955

案例:

import multiprocessing
import time

"""
    进程中的全局变量总结:
        1- 主进程、各个子进程之间不共享全局变量
        2- 如何实现不共享全局变量:各个进程将全局变量全部都深拷贝一份,各自用各自的
"""

# 定义全局变量
my_list = []
print(f"全局变量:{id(my_list)}")

def sing():
    for i in range(10):
        if i%2!=0:
            # 存放奇数
            my_list.append(i)
            print(f"奇数:{my_list},变量:{id(my_list)}")
            time.sleep(0.1)

def dance():
    for i in range(10):
        if i%2==0:
            # 存放偶数
            my_list.append(i)
            print(f"偶数:{my_list},变量:{id(my_list)}")
            time.sleep(0.1)

if __name__ == '__main__':
    # 创建子进程
    process_1 = multiprocessing.Process(target=sing)
    process_2 = multiprocessing.Process(target=dance)

    # 启动子进程
    process_1.start()
    process_2.start()

    # 主进程代码
    time.sleep(5)
    print(f"主进程执行到这里了,{my_list},变量:{id(my_list)}")

运行结果截图:

image-20250901164941108

知识点小结:

创建子进程会对主进程资源进行拷贝,也就是说子进程是主进程的一个副本,好比是一对双胞胎,之所以进程之间不共享全局变量,是因为操作的不是同一个进程里面的全局变量,只不过不同进程里面的全局变量名字相同而已。

主进程与子进程的结束顺序

image-20210112155744400

代码演示:

进程间的结束顺序:
    1- 默认 主进程会等待所有的子进程运行结束以后才会结束
    2- 可以通过如下的两种方式调整结束顺序。也就是当主进程运行结束以后,子进程不管有没有运行完成都会被动结束
       2.1- 推荐使用。子进程实例对象.daemon = True。注意:需要在启动子进程之前设置好
       2.2- 子进程实例对象.terminate() 。注意:需要在启动子进程之后再调用
import multiprocessing
import time
import os

"""
    进程间的结束顺序:
        1- 默认 主进程会等待所有的子进程运行结束以后才会结束
        2- 可以通过如下的两种方式调整结束顺序。也就是当主进程运行结束以后,子进程不管有没有运行完成都会被动结束
            2.1- 推荐使用。子进程实例对象.daemon = True。注意:需要在启动子进程之前设置好
            2.2- 子进程实例对象.terminate() 。注意:需要在启动子进程之后再调用
"""

def sing():
    for i in range(10):
        print(f"唱歌_{i},当前进程ID:{os.getpid()},父进程ID:{os.getppid()}")
        time.sleep(0.5)

if __name__ == '__main__':
    print(f"主进程执行到这里了,当前进程ID:{os.getpid()},父进程ID:{os.getppid()}")
    time.sleep(3)

    # 创建子进程
    process_1 = multiprocessing.Process(target=sing)

    # 方式一:设置进程为守护进程。推荐使用该方式,是温柔杀死,会进行资源回收
    # 注意:需要在start之前设置好
    # process_1.daemon = True

    # 启动子进程
    process_1.start()

    time.sleep(1)

    # 方式二:子进程主动的杀死自己。暴力杀死,不会进行资源回收
    # 注意:需要在start之后再调用
    process_1.terminate()

给进程传递类中的方法

把函数和方法当成同一个东西对待即可

import multiprocessing
import time

class IKun:

    # 实例方法
    def sing(self):
        for i in range(10):
            print(f"唱歌_{i}")
            time.sleep(1)

    # 类方法
    @classmethod
    def dance(cls,end):
        for i in range(end):
            print(f"跳舞_{i}")
            time.sleep(1)

    # 静态方法
    @staticmethod
    def rap(start,end,step):
        for i in range(start,end,step):
            print(f"RAP_{i}")
            time.sleep(1)

if __name__ == '__main__':
    # 创建类的实例对象
    ikun_obj = IKun()

    # 创建子进程
    process_1 = multiprocessing.Process(target=ikun_obj.sing)
    process_2 = multiprocessing.Process(target=IKun.dance, args=(10,))
    process_3 = multiprocessing.Process(target=IKun.rap, kwargs={"start":1,"end":10,"step":2})

    # 启动子进程
    process_1.start()
    process_2.start()
    process_3.start()

多线程

在Python中,想要实现多任务还可以使用多线程来完成。

进程是分配资源的最小单位 , 一旦创建一个进程就会分配一定的资源 , 就像跟两个人聊QQ就需要打开两个QQ软件一样是比较浪费资源的 .

线程是程序执行的最小单位 , 实际上进程只负责分配资源 , 而利用这些资源执行程序的是线程 , 也就说进程是线程的容器 , 一个进程中最少有一个线程来负责执行程序 。同时线程自己不拥有系统资源,只需要一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源 。这就像通过一个QQ软件(一个进程)打开两个窗口(两个线程)跟两个人聊天一样 , 实现多任务的同时也节省了资源。

多线程完成多任务

① 导入线程模块
import threading

② 通过线程类创建线程对象
线程对象 = threading.Thread(target=任务名) 

② 启动线程执行任务
线程对象.start()

Thread参数说明:

参数名说明
target执行的目标任务名,这里指的是函数名(方法名)
name线程名,一般不用设置
group线程组,目前只能使用None

线程创建与启动代码

单线程案例:

import time


def music():
    for i in range(3):
        print('听音乐...')
        time.sleep(0.2)


def coding():
    for i in range(3):
        print('敲代码...')
        time.sleep(0.2)


if __name__ == '__main__':
    music()
    coding()

多线程案例:

import threading
import time


def sing():
    for i in range(10):
        print(f"唱歌_{i}")
        time.sleep(0.1)

def dance():
    for i in range(10):
        print(f"跳舞_{i}")
        time.sleep(0.1)

if __name__ == '__main__':
    # 创建子线程
    thread_1 = threading.Thread(target=sing)
    thread_2 = threading.Thread(target=dance)

    # 启动子线程
    thread_1.start()
    thread_2.start()

    print("主线程的代码")

线程执行带有参数的任务

==多线程传递参数与多进程的使用完全一样==

参数名说明
args以元组的方式给执行任务传参
kwargs以字典方式给执行任务传参
import threading
import time

def code():
    for i in range(10):
        print(f"写代码_{i}")
        time.sleep(0.1)

def music(end):
    for i in range(end):
        print(f"听音乐_{i}")
        time.sleep(0.1)

def eat(start,end,step):
    for i in range(start,end,step):
        print(f"吃辣条_{i}")
        time.sleep(0.1)

if __name__ == '__main__':
    # 创建子线程
    thread_1 = threading.Thread(target=code)
    
    # 线程的参数传递方式、注意事项,与多进程中完全一样
    thread_2 = threading.Thread(target=music, args=(10,))
    thread_3 = threading.Thread(target=eat, kwargs={"start":1,"end":10,"step":3})

    # 启动子线程
    thread_1.start()
    thread_2.start()
    thread_3.start()

    # 主线程代码
    print("主线程代码执行到这里了")

主线程和子线程的结束顺序

设置守护线程

主线程和子线程的结束顺序:
   1- 默认:主线程会等待所有的子线程执行完以后才会结束
   2- 可以通过如下的两种方式改变结束顺序,也就是主线程运行结束以后,子线程不管有没有运行完成都得结束
       2.1- 方式一:threading.Thread(target, daemon=True),将参数daemon设置为True
       2.2- 方式二:将属性值daemon设置为True。注意:需要在start线程前设置好
  
  注意:没有 子线程对象.terminate()的方法
import threading
import time

"""
    主线程和子线程的结束顺序:
        1- 默认:主线程会等待所有的子线程执行完以后才会结束
        2- 可以通过如下的两种方式改变结束顺序,也就是主线程运行结束以后,子线程不管有没有运行完成都得结束
            2.1- 方式一:threading.Thread(target, daemon=True),将参数daemon设置为True
            2.2- 方式二:将属性值daemon设置为True。注意:需要在start线程前设置好
"""
def code():
    for i in range(10):
        print(f"子线程_写代码_{i}")
        time.sleep(1)

if __name__ == '__main__':
    # 创建子线程
    thread_1 = threading.Thread(target=code)

    # 设置守护线程的方式一:将参数daemon设置为True
    # thread_1 = threading.Thread(target=code, daemon=True)

    # 设置守护线程的方式二:将属性值daemon设置为True
    # 注意:需要在start线程前设置好
    thread_1.daemon = True

    # 启动子线程
    thread_1.start()

    print("主线程执行到这里了")

线程间的执行顺序

for i in range(5):
    sub_thread = threading.Thread(target=task)
    sub_thread.start()

思考:当我们在进程中创建了多个线程,其线程之间是如何执行的呢?按顺序执行?一起执行?还是其他的执行方式呢?

答:线程之间的执行是【无序的】

获取当前线程信息

# 通过current_thread方法获取线程对象
current_thread = threading.current_thread()

# 通过current_thread对象可以知道线程的相关信息,例如被创建的顺序
print(current_thread)

线程间的执行顺序

import threading
import time

"""
    多线程执行顺序总结:
        主线程、子线程之间的执行顺序我们无法控制,具体执行到谁由CPU调度决定。
        也就是CPU调度到哪个线程,那么对应的线程就执行
"""
def code():
    for i in range(10):
        print(f"子线程_写代码_{i},线程名称{threading.current_thread()}")
        time.sleep(0.1)

if __name__ == '__main__':
    # 创建子线程
    thread_1 = threading.Thread(target=code)
    # thread_1 = threading.Thread(target=code,name="sub_thread")

    # 启动子线程
    thread_1.start()

    # 主线程代码
    for i in range(10):
        print(f"主线程_吃辣条_{i},线程名称{threading.current_thread()}")
        time.sleep(0.1)
多线程执行顺序总结:
    主线程、子线程之间的执行顺序我们无法控制,具体执行到谁由CPU调度决定。
    也就是CPU调度到哪个线程,那么对应的线程就执行
------ 本文结束,感谢您的阅读 ------
本文作者: 程序员青阳
版权声明: 本文采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。