在这里插入代码片
# python进阶
一.模块和包的使用
1.自己写的每个python文件都可与是模块或者包:
-
smart.py:
num = 10 def my_sum(a,b): return a + b def my_sub(a,b): return a - b
2.模块的使用:
-
""" 方式1:导入整个模块,然后通过`模块名.要使用的内容`进行使用 import 模块名 import smart print(smart.num) print(smart.my_sum(2, 3)) 方式2【推荐】:从模块中导入指定要使用的内容,然后进行使用【推荐】 from 模块名 import 要使用的内容 from smart import my_sub print(my_sub(5, 2)) 方式3【不推荐】:导入模块中的所有内容,然后进行使用 from 模块名 import * from smart import * print(my_sum(1, 1)) """
3.模块起别名:(名字太长或者名字有冲突)
-
""" 情况1:给模块起别名 import 模块名 as 别名 情况2:给模块中导入的内容起别名 from 模块名 import 内容 as 别名 注意:一旦起了别名之后,那么只能通过别名使用相应的内容 """
4.模块内置变量:
(1)模块内置变量__name__.py
"""
模块内置变量:__name__
在 Python 中的每个模块都有一个 __name__ 内置变量,该内置变量的值在以下2种情况时不同:
比如现在有 1 个模块:smart.py
1)若是直接运行 smart.py 的代码,在其中获取 smart 模块的 __name__,它的值是:'__main__'
2)若是另一个模块引入了 smart 模块,并且在另一个模块中获取 smart 模块的 __name__,它的值是:'smart'
"""
---------------------------------------------------
"""
__name__ 的作用:
这个特性可以用于判断一个模块是被直接运行还是被导入,并根据不同的情况执行不同的代码。
例如,一个模块中有一些测试代码,希望只在直接运行该模块时执行这些测试代码,而在被其他模块导入时不执行。可以使用如下方式:
if __name__ == '__main__':
# 模块功能测试代码
注意:这样做的目的是,其他模块导入该模块时,被导入模块中的测试代码不会被执行
"""
(2)模块内置变量__all__.py
"""
模块内置变量:__all__
使用 `from 模块 import *` 的方式导入模块的内容时,默认只会导入被导入模块中 __all__ 内置
变量中指定的内容
"""
"""
注意:
1)在 smart.py 模块的最上方添加右边的代码:__all__ = ['num', 'sum']
2)在添加之前或添加之后,分别运行当前文件的代码查看效果
结果:
1)smart.py 中未添加 __all__ = ['num', 'sum'] 内容之前,`from smart import *` 默认
会导入 smart 模块的所有内容
2)smart.py 中添加了 __all__ = ['num', 'sum'] 内容之后,`from smart import *` 只能
导入 __all__ 中限定的内容
"""
(3)模块搜索路径:
"""
当你导入一个模块,Python解析器对模块位置的搜索顺序是:
1)当前目录
2)如果不在当前目录,Python则搜索系统路径
注意:模块搜索路径列表存储在 sys 模块的sys.path变量中。
"""
5.包:
# python内置的包和模块:内置->本身就有,可以直接进行使用
# import random
# import os
# import multiprocessing
# import threading
# import re
# 内置的包和模块是在 python 解释器安装目录的 Lib 下面
# python第三方的包和模块:第三方->本身没有,使用的时候需要先下载安装
# 安装命令:pip install 包名或模块名 -i 下载镜像源地址
# 卸载命令:pip uninstall 包名或模块名
# 阿里云 https://mirrors.aliyun.com/pypi/simple/
# 豆瓣(douban) https://pypi.douban.com/simple/
# 清华大学 https://pypi.tuna.tsinghua.edu.cn/simple/
# 中国科学技术大学 https://pypi.mirrors.ustc.edu.cn/simple/
# pandas:数据分析处理开源库
# 安装第三方的包和模块是在 python 解释器安装目录的 Lib/site-packages 下面
二.面向对象编程(解释)
1.思想:
1.对象就是存放数据和功能的容器,用对象去写程序,就是面向对象的思想。
2.类:
1.类也是容器,是用来存放同类对象共用的数据和功能的。
2.类包含两个组成部分:
- 属性:比如姓名,年龄,身高,肤色等
- 方法:比如吃饭,睡觉,飞行,歌唱等
3.对象:
1.对象是类的实例,是具体的实体。
三.类和实例
1.类的定义:
"""
注意:类名的命名规则按照大驼峰命名法,即名字中的各个单词首字母都大写
类中方法定义语法:
class 类名(object):
def 方法名1(self):
...
def 方法名2(self):
...
注意:类中方法的格式和函数类似,也可以设置参数和返回值,但是需要设置第一个参数为self
"""
#类的定义
class Hero(object):
hero_work = '射手'
count = 0
def __init__(self, name, speed, hp, atk):
self.name = name
self.speed = speed
self.hp = hp
self.atk = atk
self.equipment = []
Hero.count += 1
def get_hero_info(self):
print(f'英雄属性:名字:{self.name} 移速:{self.speed}'
f' 生命值:{self.hp} 攻击力:{self.atk}')
def set_hero_speed(self, speed_plus):
self.speed += speed_plus
def buy_equipment(self, e_name):
self.equipment.append(e_name)
def __str__(self):
return f'英雄属性:名字:{self.name} 移速:{self.speed}'
f' 生命值:{self.hp} 攻击力:{self.atk}'
f' 装备:{self.equipment}'
2.实例化
"""
Python中,可以根据已经定义的类去创建对象。
注意:类创建对象的过程叫类的`实例化`,所有对象有时也被称为`实例对象`。
格式:
对象变量名 = 类名()
通过对象调用方法:
对象变量名.方法名()
"""
# 实例化(要对初始化函数的形参进行传值)
hero1_obj = Hero('鲁班七号', 450, 3000, 100)
hero2_obj = Hero('后裔', 460, 3100, 110)
hero3_obj = Hero('虞姬', 470, 3200, 120)
#通过调用方法输出全部属性
print(hero1_obj.get_hero_info())
#对象调用方法(进行对属性的更改和增加)
hero1_obj.set_hero_speed(10)
hero1_obj.buy_equipment('反甲')
#每次实例化一个对象,都会调用初始化函数一次,count+1
print(hero1_obj.count)
#类中有__str__函数,用来输出对象的全部属性,所以可以直接输出对象,否则输出的是对象在内存中的地址
print(hero1_obj)
3.实例属性和类属性
"""
实例属性:属于单个实例对象的属性,不同实例对象互相独立,互不相干。
添加/修改:
1)方法内:self.属性名 = 值
2)类外部:实例对象.属性名 = 值
访问:
1)方法内:self.实例属性名
2)类外部:实例对象.属性名
注意:实例属性只能通过实例对象进行访问
"""
"""
类属性:属于类的属性,只有一份,被这个类的所有实例对象所共有,记录一类事物共同需要保存的内容。
添加:
1)在类定义内部,但在类方法外面的变量就是类属性
访问:
1)类名.类属性名【推荐】
2)实例对象.类属性名【不推荐】
修改:
1)类名.类属性名 = 值
注意:类属性只能通过 `类名.类属性名=值` 的方式修改
"""
class Hero:
#类属性
hero_work = '射手'
count = 0
def __init__(self, name, speed, hp, atk):
self.name = name
self.speed = speed
self.hp = hp
self.atk = atk
self.equipment = []
Hero.count += 1
hero1_obj = Hero('鲁班七号', 450, 3000, 100)
print(Hero.count)
print(hero1_obj.count)
"""
类属性有什么用?记录一类事物共同需要保存的内容
需求:记录一共创建了多少只狗的实例对象?
"""
4.self 和 绑定方法:
"""
在 Python 类中规定,实例方法的第一个参数是实例对象本身,并且约定俗成,把其名字写为self。
某个对象调用其某个方法时,Python 解释器会自动把这个对象作为第一个参数传递给对应方法
通俗理解:通过哪个对象调用方法,方法中 self 就是这个对象
"""
"""
在方法中使用 self,可以获取到调用当前方法的对象,进而获取到该对象的属性和方法
self作用:用来区分不同对象的属性和方法,谁调用方法就是谁的属性和方法
"""
# 在Python中,绑定方法是指将一个函数绑定到一个对象上,使其成为该对象的一个方法。绑定方法可以通过以下两种方式实现:
# 1.使用类定义方法:在类定义中,将函数定义为类的方法,即将函数作为类的属性,并在调用时将实例对象作为第一个参数传入。
class MyClass:
def my_method(self, arg1, arg2):
# 方法体
pass
# 创建实例对象
obj = MyClass()
# 调用绑定方法
obj.my_method(arg1, arg2)
# 2.使用types.MethodType函数:将函数绑定到对象上,使其成为对象的一个方法。
import types
class MyClass:
pass
# 定义函数
def my_method(self, arg1, arg2):
# 方法体
pass
# 创建实例对象
obj = MyClass()
# 绑定方法
obj.my_method = types.MethodType(my_method, obj)
# 调用绑定方法
obj.my_method(arg1, arg2)
#所以当类本身调用函数方法时, self必须传参数
class Animal(object):
"""动物类"""
def __init__(self):
print('Animal类中的__init__方法')
self.type = '动物'
def show_info(self):
print(f'Animal类中的show_info方法,类型:{self.type}')
class Dog(Animal):
def __init__(self):
print('Dog类中的__init__方法')
self.type = '狗'
def show_info(self):
print(f'Dog类中的show_info方法,类型:{self.type}')
# 需求:如果在子类的方法中能够调用到父类中的同名 show_info 方法
# self必须写,因为当类去调用函数方法时,就成了普通函数,就不是绑定方法了,需要传self
Animal.show_info(self)
5.魔法方法:
-
类的 __init__ 方法: 1)__init__ 方法叫做 对象的初始化方法,在 创建一个对象后会被自动调用,不需要手动调用 2)__init__ 方法的作用:给创建的对象添加属性
-
类的 __str__ 方法: 1)如果直接 print 打印对象,会看到创建出来的对象在内存中的地址 2)当使用 print(对象变量名) 输出对象的时候,只要类中定义了 __str__ 方法,就会打印 __str__ 方法返回值 __str__ 方法作用: 1)主要返回对象属性信息,print(对象变量名) 输出对象时直接输出 __str__ 方法返回的描述信息 2)注意:__str__ 方法的返回值必须是 字符串类型
6.实例方法,类方法,静态方法
"""
实例方法:属于实例对象的方法,第一个形参是self,只能通过实例对象进行调用,self形参就是实例对象
"""
class Dog(object):
"""狗类"""
def __init__(self, _name, _age):
self.name = _name
self.age = _age
# 实例方法:只能通过实例对象进行调用
def show_info(self):
print(f'我的名字:{self.name}, 年龄:{self.age}')
# 创建一个 Dog 对象
dog1 = Dog('小黄', 1)
dog1.show_info()
"""
类方法:属于类对象(类)的方法,可以通过类名或实例对象名进行调用
作用:用于对一类事物进行操作,和具体的某个实例对象没有直接关联关系,若类中的方法方法在逻辑上采用
类本身来调用更合理,那么这个方法就可以定义为类方法
需求:在类中提供一个方法,展示一共有多少只狗
定义:
class 类名(object):
@classmethod
def 类方法名(cls):
pass
注意:类方法必须有一个形参,一般叫 cls,调用类方法是 cls 形参不用传递,python解释器会自动传递
"""
class Dog(object):
# 类属性
count = 0
def __init__(self, _name, _age):
# 实例属性
self.name = _name
self.age = _age
# 只要__init__调用,就说明创建了一个对象,类属性 count 就加1
Dog.count += 1
# 实例方法:只能通过实例对象进行调用
def show_info(self):
print(f'我的名字:{self.name}, 年龄:{self.age}')
# 需求:提供一个显示一共有多少只狗的方法
@classmethod
def show_dog_count(cls):
print(f'现在一共有{cls.count}只狗')
Dog.show_dog_count()
"""
静态方法:主要是用来存放逻辑性的代码,逻辑上属于类,但是和类本身没有关系,也就是说在静态方法中,
不会涉及到类中的属性和方法的操作。可以理解为,静态方法是个独立的、单纯的函数,它仅仅托管于某个
类中,便于使用和维护。
定义:
class 类名(object):
@staticmethod
def 静态方法名():
pass
注意:静态方法不需要有 self 或 cls 参数
"""
class SysManager(object):
"""管理系统类"""
# 需求:提供一个显示菜单的方法
@staticmethodpy
def show_menu():
print('菜单')
SysManager.show_menu()
7.小练习
"""
面向对象编程的步骤:
1)分解对象:分解出编程任务中涉及到哪些对象
拆解很细:地瓜、烧烤炉、人、火、碳
咱们这个案例很简单,只拆解一个:地瓜对象
2)抽象成类:将分解出的对象抽象成类,分析类中有哪些属性和方法
地瓜类(SweetPotato)
属性:这个类的对象有哪些数据需要保存(不要凭空想象,结合需求进行分析)
state:地瓜的状态
cooked_time:地瓜的烧烤总时间
condiments:添加的佐料
方法:这个类的对象能够干什么、做什么(不要凭空想象,结合需求进行分析)
__init__(self):地瓜对象的初始化方法,给创建的每个地瓜对象添加初始的属性:state、cooked_time
cook(self, time):地瓜烧烤的方法,有一个形参 time,接收地瓜每次烧烤的时间
__str__(self):返回一个字符串,包含地瓜对象的状态和烧烤的总时间
add_condiment(self, item):添加烧烤佐料方法,有一个形参item,接收添加的佐料
3)定义出类
4)创建对象,通过对象调用方法或使用属性完成编程任务
"""
class SweetPotato(object):
"""地瓜类"""
def __init__(self):
"""地瓜对象的初始化方法,给创建的每个地瓜对象添加初始的属性:state、cooked_time"""
self.state = '生的'
self.cooked_time = 0
self.condiments = [] # 保存地瓜对象烧烤过程中,添加过的佐料
def __str__(self):
"""返回一个字符串,包含地瓜对象的状态、烧烤总时间"""
return f'地瓜状态:{self.state},烧烤总时间:{self.cooked_time},添加过的佐料:{self.condiments}'
def cook(self, time):
"""
功能:对地瓜进行烧烤
参数:
* time:接收一个数字,代表每一次烧烤烤几分钟
"""
# ① 将当前地瓜对象烧烤的总时间进行累加,再加time
# sp1.cooked_time += time
self.cooked_time += time
# ② 判断当前地瓜对象的烧烤总时间,进行地瓜状态的改变
if 0 <= self.cooked_time < 3:
# sp1.state = '生的'
self.state = '生的'
elif 3 <= self.cooked_time < 6:
self.state = '半生不熟'
elif 6 <= self.cooked_time < 8:
self.state = '熟了'
else:
self.state = '烤糊了'
def add_condiment(self, item):
"""
功能:添加佐料
参数:
* item:接收添加的佐料
"""
# 保存佐料
self.condiments.append(item)
# 创建一个地瓜对象
sp1 = SweetPotato()
print(sp1)
# 烤一次地瓜
sp1.cook(2)
sp1.add_condiment('糖')
print(sp1)
# 烤一次地瓜
sp1.cook(2)
sp1.add_condiment('辣椒面')
print(sp1)
# 烤一次地瓜
sp1.cook(2)
sp1.add_condiment('番茄酱')
print(sp1)
# 烤一次地瓜
sp1.cook(2)
sp1.add_condiment('玛莎拉')
print(sp1)
四.面向对象三大特性(封装,继承,多态)
1.封装(隐藏属性)
1.含义:
-
1.定义类的过程,也是封装
-
2.对属性和方法增加访问权限控制:公开和私有(当前面加_ _ 在类里面调用可以,类外面不可以)
-
私有属性:self._ _name = name
-
私有方法:def _ _sleep(self):
2.封装例子:
class Dog(object):
"""狗类"""
def __init__(self, _name, _age):
self.name = _name
self.__age = _age
def get_age(self):
"""提供给外部获取私有属性方法"""
return self.__age
def set_age(self,_age):
"""提供给外部设置私有属性的方法"""
if _age <= 0:
print('年龄必须大于0')
return
self.__age = _age
dog1 = Dog('小黑',5)
#公开属性可以直接调用
print(dog1.name)
#--对变量进行了隐藏,类外不能调用私有属性
# print(dog1.age)
#需要调函数接口让别人使用,类的设计者保护了数据不被别人修改(类内的函数可以调用私有属性)
print(dog1.get_age())
#私有属性的本质就是改了属性的名字 改为: _类名__属性名
#所以通过改后的名字还是可以访问到,python没有真正的私有
print(dog1._Dog__age)
2.继承
1.单继承和多继承
"""
单继承:子类定义时只继承一个父类
"""
class Animal(object):
"""动物类"""
def eat(self):
print('吃东西')
# Dog 类定义时只继承了 Animal 类
class Dog(Animal):
"""狗类"""
pass
# 创建一个 Dog 对象
dog1 = Dog()
dog1.eat()
#-------------------------------------------------------------------
"""
多继承:子类定义时同时继承多个父类
"""
class SmallDog(object):
def eat(self):
print('小口吃东西')
def sleep(self):
print('小憩一会')
class BigDog(object):
def drink(self):
print('大口喝水')
def sleep(self):
print('呼呼大睡')
# SuperDog 类定义时同时继承了 SmallDog 和 BigDog
class SuperDog(SmallDog, BigDog):
pass
# 创建一个 SuperDog 对象
sd1 = SuperDog()
# SuperDog 从 SmallDog 继承了 eat 方法
sd1.eat()
# SuperDog 从 BigDog 继承了 drink 方法
sd1.drink()
"""
子类继承的多个父类中有多个同名方法,默认调用先继承的那个父类中的同名方法。
"""
2.多层继承
"""
多层继承:继承关系为多层传递,如生活中的爷爷、父亲、儿子
"""
# 爷爷类
class Animal(object):
"""动物类"""
def eat(self):
print('吃东西')
# 爸爸类
class Dog(Animal):
"""狗类"""
def drink(self):
print('喝水')
# 儿子类
class SuperDog(Dog):
pass
# 创建一个 SuperDog 对象
sd1 = SuperDog()
sd1.drink()
sd1.eat()
3.重写
1.重写的目的:
从父类继承的方法不能满足子类的需要,为了拓展功能而重写
2.重写方式:
"""
重写形式:
1)在子类中定义了一个和父类(包括爷爷类及以上)同名的方法(参数也一样),即为对父类(包括爷爷类
及以上)的方法重写
注意:重写之后子类调用同名方法,默认只会调用子类的
"""
-
完全重写
class Animal(object): """动物类""" def __init__(self): print('Animal类中的__init__方法') self.type = '动物' def show_info(self): print(f'Animal类中的show_info方法,类型:{self.type}') class Dog(Animal): def __init__(self): """ 对父类继承的__init__方法进行重写 """ print('Dog类中的__init__方法') self.type = '狗' def show_info(self): """ 对父类继承的show_info方法进行重写 """ print(f'Dog类中的show_info方法,类型:{self.type}') # 创建一个 Dog 对象 dog1 = Dog() dog1.show_info()
class PayClass(object): def pay(self): pass def refund(self): pass class AliPay(PayClass): def pay(self): print('支付宝付款') def refund(self): print('支付宝退款') class WeChat(PayClass): def pay(self): print('微信支付') def refund(self): print('微信退款')
-
增强重写(在父类基础上重写):
子类重写了父类的方法之后,若在子类方法中还想调用父类的同名方法
""" 子类重写了父类的方法之后,若在子类方法中还想调用父类的同名方法。 可以使用如下方式: 1)父类名.同名方法(self, 形参1, ……) 2)super(子类名, self).同名方法(形参1, ……) 3)super().同名方法(形参1, ……):是方法 2 的简写,推荐的写法 """ class Animal(object): """动物类""" def __init__(self): print('Animal类中的__init__方法') self.type = '动物' def show_info(self): print(f'Animal类中的show_info方法,类型:{self.type}') class Dog(Animal): def __init__(self): print('Dog类中的__init__方法') self.type = '狗' def show_info(self): print(f'Dog类中的show_info方法,类型:{self.type}') # 需求:如果在子类的方法中能够调用到父类中的同名 show_info 方法 #方法1;父类名.同名方法(self,...) # self必须写,因为当类去调用函数方法时,就成了普通函数,就不是绑定方法了,需要传self Animal.show_info(self) #方式2:super(子类名,self).同名方法 super(Dog,self).show_info() #方式3:super().同名方法 方式2的简写 super().show_info() # 创建一个 Dog 对象 dog1 = Dog() dog1.show_info()
3.super父类方法调用(父类方法被重写后,又想调用):
#根据__mro__链表的顺序,(自下而上,自左向右)
-
单继承 ( C = > B = > A)
class A(object): def func(self): print('A类中的func函数') class B(A): def func(self): super().func() print('B类中的func函数') class C(B): def func(self): super().func() print('C类中的func函数') # 创建一个C这个类的对象 obj = C() # 思考:代码的执行结果是什么??? obj.func() # A类中的func函数 # B类中的func函数 # C类中的func函数
-
多继承 (D = > B = > C = > A)
class A(object): def func(self): print('A类中的func函数') class C(A): def func(self): super().func() print('C类中的func函数') class B(A): def func(self): super().func() print('B类中的func函数') class D(B, C): def func(self): super().func() print('D类中的func函数') # 创建一个 D 这个类的对象 obj = D() # 思考:代码执行的结果是什么??? obj.func() # A类中的func函数 # C类中的func函数 # B类中的func函数 # D类中的func函数
3.私有和继承
"""
私有和继承:
父类中的私有属性和方法不能被子类继承
如果子类想访问到父类中的私有属性和方法怎么办?
答:在父类中提供一个公有方法,在父类的公有方法中进行访问私有属性和方法,以此来达到子类能够间接
访问到父类中的私有属性和方法的效果。
"""
class Animal(object):
"""动物类"""
def __init__(self, _name, _age):
self.name = _name
self.__age = _age
def __pee(self):
"""pee:撒尿"""
print('上厕所嘘嘘')
def show_info(self):
print(f'名字:{self.name},年龄:{self.__age}')
def sleep(self):
# 睡觉撒尿
self.__pee()
print('睡觉了💤')
class Dog(Animal):
"""狗类"""
def test(self):
# Animal 中的私有属性和方法不能被 Dog 类继承
# print(self.__age)
# self.__pee()
Animal.show_info(self)
Animal.sleep(self)
# 创建一个 Dog 对象
dog1 = Dog('旺财', 20)
dog1.test()
3.多态
"""
多态:多种形态,调用同一个函数,不同表现
因为Python是动态语言,站在用户的角度,本身就是多态,不存在非多态的情况
实现多态的步骤:
1)实现继承关系
2)子类重写父类方法
3)通过对象调用该方法
"""
class PayClass(object):
"""支付类"""
def pay(self):
"""付款"""
pass
def refund(self):
"""退款"""
pass
class Alipay(PayClass):
"""支付宝类"""
def pay(self):
print('支付宝付款')
def refund(self):
print('支付宝退款')
class WeChat(PayClass):
"""微信支付类"""
def pay(self):
print('微信付款')
def refund(self):
print('微信退款')
def func(obj):
obj.pay()
ap = Alipay()
wc = WeChat()
#由于传入实例不同,效果也不同
func(ap)
func(wc)
# 支付宝付款
# 微信付款
四.闭包函数和装饰器
1.闭包函数用来保护数据,实现装饰器
"""
利用闭包统计函数的调用次数
闭包的构成条件:
1. 函数嵌套(外部函数中定义了一个内部函数)
2. 外部函数返回了内部函数
3. 内部函数使用了外部函数的变量(还包括外部函数的参数)
"""
def make_average():
nums = []
def average(n):
nums.append(n)
return sum(nums)/len(nums)
return average
func = make_average()
print(func(10)) # 结果:???
print(func(20)) # 结果:???
print(func(30)) # 结果:???
# 10.0
# 15.0
# 20.0
----------------------------------------------------
#闭包函数就是最基本的装饰器,f2不能进行参数的传值,而这时又需要外部传参进来,就用到闭包函数
def f1(x):
def f2():
print(x)
return f2
res = f1(10)
res()
2.装饰器
1.含义:
装饰器:定义函数,类,来装饰其他函数和类,为其增加功能,可扩展,增加新功能,不是对原来的进行修改。(开放封闭原则)
装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数。这个新函数通常会在原函数执行前后添加一些额外的功能或修改原函数的行为。
2.装饰器的使用可以实现以下几个目的:
-
添加额外功能:装饰器可以在被装饰的函数执行前后执行一些额外的代码,比如计算函数执行时间、记录日志等。
-
修改函数行为:装饰器可以修改函数的输入输出,或者修改函数的执行方式,以实现一些特定的需求。例如,可以通过装饰器对函数的参数进行验证,或者对函数的返回值进行处理。
-
代码复用:装饰器可以将一些通用的功能封装起来,并应用到多个函数上,从而避免重复编写相同的代码。
3.装饰器的基本原理:
"""
装饰器定义的基本形式:
# 注意:在定义装饰器的时候,外部函数必须有且只有一个形参,
# 这个形参将来要传入一个函数(这个函数就是要扩展功能的函数,也成被装饰函数)
def func_outer(func):
def wrapper():
# 增加代码,进行功能扩展
# 在内部函数中调用传入的函数(被装饰函数)
func()
# 增加代码,进行功能扩展
return wrapper
"""
#给func1()函数添加统计时间功能
import time
# time.time():获取代码执行时的时间戳:秒
def func1():
_sum = 0
for i in range(1000000):
_sum += i
return _sum
#装饰器实现
def func_outer(func):
def wrapper():
s1 = time.time()
func()
s2 = time.time()
print(f'函数执行时间:{s2-s1}')
return wrapper
#把需要装饰的函数通过形参传递给装饰器,又把返回结果赋值给需要装饰的函数名
func1 = func_outer(func1)
#此时调用的函数就是装饰完成后的函数,看似名字没有变,内容变了
func1()
4.装饰器使用时的语法糖
通过在被装饰函数前加@装饰器名
import time
# 装饰器函数
def get_time(func):
def wrapper():
s1 = time.time()
func()
s2 = time.time()
print('执行时间:', s2 - s1)
return wrapper
@get_time
def func1():
_sum = 0
print('函数1被调用')
for i in range(1000000):
_sum += i
return _sum
func1()
5.有参装饰器(装饰有参函数的)
"""
需求:编写一个装饰器,装饰 my_sum 函数,计算 my_sum 的执行时间
"""
import time
# 装饰器函数
def get_time(func):
def wrapper(num): #当装饰的函数有参数传入时,wrapper(num)放形参
s1 = time.time()
func(num) #被装饰函数也需放形参
s2 = time.time()
print('执行时间:', s2 - s1)
return wrapper
@get_time
def my_sum(n):
"""计算1-n之间的数字和"""
result = 0
# 遍历计算 1-n 之间的数字和
for i in range(1, n + 1):
result += i
# 打印计算的结果
print('结果为:', result)
my_sum(100000)
6.装饰有返回值函数
import time
# 装饰器函数
def get_time(func):
def wrapper(num):
s1 = time.time()
result = func(num)
s2 = time.time()
print('执行时间:', s2 - s1)
return result #当被装饰函数有返回值,就需要wrapper函数返回自己的值
return wrapper
@get_time
def my_sum(n):
"""计算1-n之间的数字和并返回"""
result = 0
# 遍历计算 1-n 之间的数字和
for i in range(1, n + 1):
result += i
# 返回计算的结果
return result
res = my_sum(10000)
print(res)
7.装饰器模版
def outer(func):
def wrapper(*args, **kwargs):
#此处可新增功能
res = func(*args, **kwargs) #这行就是调用原函数功能
#此处可新增功能
return res
return wrapper
8.args 和 kwargs 应用(用来写装饰器模版)
import time
def outer(func):
def wrapper(*args, **kwargs):
s1 = time.time()
res = func(*args, **kwargs) #这行就是调用原函数功能
s2 = time.time()
print('运行时间:', s2 - s1)
return res
return wrapper
@outer
def func1():
"""func1函数无参数,也无返回值"""
_sum = 0
for i in range(1000000):
_sum += i
return _sum
@outer
def func2(m, n):
"""计算m-n之间的数字和,func2函数有两个参数,也有返回值"""
result = 0
# 遍历计算 m-n 之间的数字和
for i in range(m, n + 1):
result += i
# 返回计算的结果
return result
print(func1())
list = [1,100]
dict = {'m':1,'n':100}
print(func2(*list))
print(func2(**dict))
五.多进程(操作系统分配资源的基本单位)
1.多进程实现
Python中的多进程可以通过使用multiprocessing
模块来实现。multiprocessing
模块提供了一个Process
类,可以用于创建和控制进程。
import os
import time
#导入进程包
import multiprocessing
# 跳舞函数
def dance():
print(f'dance进程的编号:{os.getpid()},父进程编号:{os.getppid()}')
for i in range(5):
print('跳舞中...')
time.sleep(1)
# 唱歌函数
def sing():
print(f'sing进程的编号:{os.getpid()},父进程编号:{os.getppid()}')
for i in range(5):
print('唱歌中...')
time.sleep(1)
if __name__ == '__main__':
print(f'主进程编号:{os.getpid()},父进程编号:{os.getppid()}')
#创建两个进行
dance_process = multiprocessing.Process(target=dance)
sing_rocess = multiprocessing.Process(target=sing)
#启动进程
dance_process.start()
sing_rocess.start()
2.进程执行带有参数
"""
进程执行带有参数的任务(函数)
"""
import multiprocessing
import time
# 带有参数的任务(函数)
def task(count):
for i in range(count):
print('任务执行中...')
time.sleep(0.2)
else:
print('任务执行完成')
if __name__ == '__main__':
#sub_process = multiprocessing.Process(target=task,args=(3,)) #两种方式
sub_process = multiprocessing.Process(target=task,kwargs={'count':5})
sub_process.start()
3.进程执行注意点:
4.设置主进程结束时子进程也结束的两种方式
1.方式1:将子进程设置为守护进程
import multiprocessing
import time
# 方式1:将子进程设置为守护进程
# 任务函数
def task():
for i in range(10):
print('任务执行中...')
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程并启动
sub_process = multiprocessing.Process(target=task)
# TODO:设置子进程为守护进程
sub_process.daemon = True
sub_process.start()
# 主进程延时 1s
time.sleep(1)
print('主进程结束!')
# 退出程序
exit()
2.方式2:主进程结束时直接终止子进程
import multiprocessing
import time
# 任务函数
def task():
for i in range(10):
print('任务执行中...')
time.sleep(0.2)
if __name__ == '__main__':
# 创建子进程并启动
sub_process = multiprocessing.Process(target=task)
sub_process.start()
# 主进程延时 1s
time.sleep(1)
print('主进程结束!')
# TODO: 终止子进程
sub_process.terminate()
# 退出程序
exit()
六.多线程(CPU调度的基本单位)
1.多线程实现:
import time
import threading
# 跳舞函数
def dance():
for i in range(5):
print('跳舞中...')
time.sleep(1)
# 唱歌函数
def sing():
for i in range(5):
print('唱歌中...')
time.sleep(1)
if __name__ == '__main__':
dance_thread = threading.Thread(target=dance)
sing_thread = threading.Thread(target=sing)
dance_thread.start()
sing_thread.start()
2.多线程执行带参数:
import threading
import time
# 带有参数的任务(函数)
def task(count):
for i in range(count):
print('任务执行中...')
time.sleep(0.2)
else:
print('任务执行完成')
if __name__ == '__main__':
#sub_thread = threading.Thread(target=task,args=(3,))
sub_thread = threading.Thread(target=task,kwargs={'count':5})
sub_thread.start()
3.线程使用注意点:
"""
线程使用的注意点介绍:
1)线程之间执行是无序的
2)主线程会等待所有的子线程执行结束再结束
3)线程之间共享全局变量
"""
4.守护线程设置:
# TODO:设置子线程为守护线程
sub_thread.daemon = True
5.线程资源共享问题:
"""
多线程会共享全局变量,当多个线程同时操作同一个共享的全局变量时,可能会造成错误的结果!
解决方法:
(1)能够使用线程等待
(2)互斥锁解决线程资源共享问题
"""
1.线程等待(join):
# 线程等待(join):等待一个线程执行结束之后,代码再继续执行,同一时刻只有一个线程执行
import threading
# 定义全局变量
g_num = 0
def sum_num1():
global g_num
# 循环一次给全局变量加1
for i in range(1000000):
g_num += 1
print('sum1:', g_num)
def sum_num2():
global g_num
# 循环一次给全局变量加1
for i in range(1000000):
g_num += 1
print('sum2:', g_num)
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动两个线程
first_thread.start()
#线程等待(join):等待一个线程执行结束之后,代码再继续执行,同一时刻只有一个线程执行
first_thread.join()
second_thread.start()
2.互斥锁
互斥锁:多个线程去抢同一把"锁",抢到锁的线程执行,没抢到锁的线程会阻塞等待
import threading
# 定义全局变量
g_num = 0
#创建一个互斥锁
lock = threading.Lock()
#注意锁的粒度,加锁,释放锁都有时间开销
def sum_num1():
global g_num
# 循环一次给全局变量加1
# 操作前加锁
lock.acquire() # 拿到锁,继续执行,拿不到,继续等待
for i in range(1000000):
g_num += 1
print('sum1:', g_num)
lock.release()
# 操作完,释放锁
def sum_num2():
global g_num
# 循环一次给全局变量加1
for i in range(1000000):
# 操作前加锁
lock.acquire() # 拿到锁,继续执行,拿不到,继续等待
g_num += 1
lock.release()
# 操作完,释放锁
print('sum2:', g_num)
if __name__ == '__main__':
# 创建两个线程
first_thread = threading.Thread(target=sum_num1)
second_thread = threading.Thread(target=sum_num2)
# 启动两个线程
first_thread.start()
second_thread.start()
1.注意:
互斥锁的粒度问题:粗粒度和细粒度
粗粒度其实更像进程的执行
# 操作前加锁
lock.acquire() # 拿到锁,继续执行,拿不到,继续等待
for i in range(1000000):
g_num += 1
print('sum1:', g_num)
lock.release()
# 操作完,释放锁
for i in range(1000000):
# 操作前加锁
lock.acquire() # 拿到锁,继续执行,拿不到,继续等待
g_num += 1
lock.release()
# 操作完,释放锁
2.注意:
Python 3.10 版本后的自动保证了线程的原子性,不需要在手动加锁
3.死锁问题:
死锁是指两个或多个线程在互相等待对方释放资源的情况下无法继续执行的情况
# 解决死锁问题的方法有以下几种:
#
# (1)避免循环等待:确保线程在请求资源时按照相同的顺序获取资源,这样可以避免循环等待的情况发生。例如,可以规定所有线程必须按照资源的编号顺序获取资源,这样就不会出现循环等待的情况。
#
# (2)使用超时机制:可以给获取资源的操作设置一个超时时间,在等待超过一定时间后,线程放弃获取资源并释放已经获取的资源。这样可以避免线程一直等待对方释放资源而导致死锁。可以使用threading.Lock().acquire(timeout=...)来设置超时时间。
#
# (3)使用线程锁:可以使用threading.Lock()来保证资源的互斥访问。线程在获取资源之前先尝试获取锁,如果锁已经被其他线程获取,则线程会被阻塞直到锁被释放。这样可以避免多个线程同时获取同一个资源而导致死锁。在使用线程锁时,需要确保所有线程都按照相同的顺序获取锁,否则可能会出现死锁。
#
# (4)使用资源分配策略:可以使用资源分配策略来避免死锁。例如,可以使用银行家算法来分配资源,确保每个线程在获取资源之前都能满足其所需的资源数量,从而避免死锁的发生。
#如果已经发送死锁:强制终止程序,并通过回滚或剥夺资源来解除死锁。
七.进程和线程
1.区别
1)进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位。
2)线程不能够独立执行,必须依存在进程中
3)创建进程的资源开销要比创建线程的资源开销要大。
4)进程之间不共享全局变量,线程之间共享全局变量,但是要注意线程资源竞争的问题。
5)多进程开发比单进程多线程开发稳定性要强。
2.优缺点
(1)进程:
优点:稳定性高,可以利用CPU多核
缺点:资源开销大
应用:适合计算型任务
(2)线程:
优点:资源开销小
缺点:稳定性低,不能利用CPU多核(仅针对Python语言:python的每个线程调用都需要GIL全局的一个锁)
应用:适合IO密集型任务(文件读写、网络请求)
八.网络编程
1.IP地址
(1)作用:标识网络中唯一的一台设备(比如你女朋友的电脑)。
(2)分类:
IPV4:点分十进制,由32个二进制位组成
例如:139.227.220.247
数量:2的32次方
IPv6:冒号十六进制,由128个二进制位组成例如:2345:0425:2CA1:0000:0000:0567:5673:23b5
数量:2的128次方
2.域名怎么解析成对应的IP?
(1)本地hosts文件
(2)本地网关解析
(3)DNS域名解析服务器
114.114.114.114
8.8.8.8
3.端口号:
(1)标识一台设备的一个网络应用程序,0-65535
(2)分类:
知名端口号:范围是0-1023,系统预留的一些端口号,不建议使用
动态端口号:范围是1024-65535,建议使用
一些动态端口号被一些常见程序占用了,自己写网络程序时,也不建议使用,比如:M小ySQL数据库3306、rdis数据库6379
、HDFS9870等.
4.网络协议(TCP):
(1):TCP(Transmission Control Protocol)是一种网络传输控制协议,它是一种面向连接的、可靠的、基于字节流的传输层通信协议。
(2):特点:
面向连接:三次握手
可靠传输:保证数据不丢包、不乱序
基于字节流:传输是bytes.二进制字节流数据
(3)TCP(传输控制协议)和UDP(用户数据报协议)是两种常用的传输层协议,它们在传输数据时有以下区别:
- 连接性:TCP是面向连接的协议,需要在通信之前先建立连接,然后进行可靠的数据传输。UDP是无连接的协议,不需要建立连接,直接将数据包发送出去。
- 可靠性:TCP提供可靠的数据传输,通过使用序号、确认和重传机制来确保数据的完整性和可靠性。UDP不提供可靠性保证,发送的数据包不会进行确认和重传,因此可能会出现丢包或乱序的情况。
- 速度:由于TCP提供了可靠性保证,它需要进行大量的数据包确认和重传操作,这会增加通信的延迟。相比之下,UDP没有这些额外的开销,因此传输速度更快。
- 数据量限制:TCP没有固定的数据包大小限制,可以传输任意大小的数据。UDP的数据包大小有限制,IPv4下最大为64KB,IPv6下最大为4GB。
- 适用场景:TCP适用于需要可靠传输和顺序交付的应用,如网页浏览、文件传输、电子邮件等。UDP适用于实时性要求高、数据丢失可容忍的应用,如实时视频、音频、在线游戏等。
5.socket(网络编程的一个工具)(就是一个Python类)
6.TCP网络编程:
1.编码解码:
"""
str和bytes的互相转换
学习目标:知道str和bytes数据之间的互相转换
"""
# str -> bytes:str.encode('编码方式:默认utf8')
# bytes -> str:bytes.decode('解码方式:默认utf8')
my_str = '你好!中国!' # str
res1 = my_str.encode()
print(res1)
res2 = res1.decode()
print(res2)
2.服务端代码:
"""
TCP服务端程序开发步骤:
1)创建服务端监听套接字对象
2)绑定端口号
3)设置监听
4)等待接受客户端的连接请求
5)接收数据
6)发送数据
7)关闭套接字
"""
import socket
# socket创建一个TCP套接字
#socket.AF_INET => 使用ipv4, socket.SOCK_STREAM => 使用tcp协议
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# bind绑定IP地址和端口号,是一个元组
server_address = ('localhost', 8888)
server_socket.bind(server_address)
# listen监听客户端连接,括号里代表最多几个客户端连接
server_socket.listen(5)
while True:
print('等待客户端连接...')
# accept接受客户端连接,否则一直阻塞等待,
# accept有两个返回值(client_socket负责和客户端进行接收和发送操作,client_address是客户端的ip和端口号)
client_socket, client_address = server_socket.accept()
print('客户端已连接:', client_address)
try:
while True:
# 接收客户端发送的数据
data = client_socket.recv(1024)
if data:
# 处理接收到的数据
print('接收到数据:', data.decode())
# 发送响应给客户端
response = input('请输入给客户端回应的消息:')
client_socket.sendall(response.encode())
else:
# 客户端关闭连接
print('客户端已关闭连接:', client_address)
break
finally:
# 关闭客户端套接字
client_socket.close()
# 关闭服务端套接字
server_socket.close()
3.客户端代码:
"""
TCP客户端程序开发步骤:
1)创建客户端套接字对象
2)和服务端套接字建立连接
3)发送数据
4)接收数据
5)关闭客户端套接字
"""
import socket
# socket创建一个TCP套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# connect连接到服务端
server_address = ('localhost', 8888)
client_socket.connect(server_address)
try:
# 发送数据给服务端
message = input('请输入发送给服务端的消息:')
client_socket.sendall(message.encode())
# 接收服务端发送的响应
response = client_socket.recv(1024)
print('接收到响应:', response.decode())
finally:
# 关闭客户端套接字
client_socket.close()
九.正则表达式:
1.re模块(match、search、findall):
"""
re 正则模块:match、search、findall 三个函数匹配字符串
"""
import re
my_str = 'abc_123_DFG_456'
"""
match函数:re.match(pattern, string, flags=0)
功能:尝试从字符串起始位置匹配一个正则表达式
1)如果不能从起始位置匹配成功,则返回None;
2)如果能从起始位置匹配成功,则返回一个匹配的对象
"""
# 匹配字符串bc(注:从头开始)
res = re.match('bc',my_str)
print(res)
#None
# 匹配字符串abc(注:从头开始)
res2 = re.match('abc',my_str)
print(res2)
#match对象.group():获取匹配到的内容
print(res2.group())
#abc
"""
search函数:re.search(pattern, string, flags=0)
功能:根据正则表达式扫描整个字符串,并返回第一个成功的匹配
1)如果不能匹配成功,则返回None;
2)如果能匹配成功,则返回一个匹配对象
"""
# 匹配连续的3位数字
res = re.search(r'\d{3}',my_str)
print(res)
"""
findall函数:re.findall(pattern, string, flags=0)
功能:根据正则表达式扫描整个字符串,并返回所有能成功匹配的子串
1)如果不能匹配成功,则返回一个空列表;
2)如果能匹配成功,则返回包含所有匹配子串的列表
"""
# 匹配字符串中的所有连续的3位数字
res2 = re.findall(r'\d{3}',my_str)
print(res2)
# ['123', '456']
2.re模块正则分组匹配:
"""
示例1:正则匹配分组操作
语法:(正则表达式)
"""
import re
my_str = '13155667788'
# 需求:使用正则提取出手机号的前3位、中间4位以及后 4 位数据
#\d{11}
#(\d{3})(\d{4})(\d{4})
#获取整个正则的内容
res = re.match(r'(\d{3})(\d{4})(\d{4})',my_str)
print(res)
#获取分组的正则内容
print(res.group(1))
print(res.group(2))
print(res.group(3))
"""
示例2:给正则分组起别名
语法:(?P<分组别名>正则表达式)
"""
import re
my_str = '<div><a href="https://www.itcast.cn" target="_blank">传智播客</a><p>Python</p></div>'
# 需求:使用正则提取出 my_str 字符串中的 `传智播客` 文本
res1 = re.search(r'<a.*>(?P<text>.*)</a>',my_str)
print(res1)
print(res1.group(1))
print(res1.group('text'))
3.re模块引用正则分组:
"""
需求:写一个正则表达式,匹配字符串形如:'xxx xxx xxx'
注意:xxx可以是任意多位的数字,但是这三个xxx必须一致,比如:'123 123 123'
"""
"""
引用正则分组的方式:
1)\num:引用正则中第 num 个分组匹配到的字符串<br/>例如:`\1`表示第一个分组,`\2`表示第二个分组...
2)(?P=name):引用正则中别名为 name 分组匹配到的字符串
"""
import re
my_str = '123 123 123'
#(\d+)\s\1\s\1
#(?P<num>\d+)\s(?P=num)\s(?P=num)
res1 = re.match(r'(\d+)\s\1\s\1',my_str)
print(res1)
res2 = re.match(r'(?P<num>\d+)\s(?P=num)\s(?P=num)',my_str)
print(res2)
# <re.Match object; span=(0, 11), match='123 123 123'>
# <re.Match object; span=(0, 11), match='123 123 123'>
4.正则表达式修饰符:
"""
re.I:匹配时不区分大小写
re.M:多行匹配,影响 ^ 和 $
re.S:影响 . 符号,设置之后,.符号就能匹配\n了
"""
import re
my_str1 = 'aB'
res = re.match('ab',my_str1,flags=re.I)
print(res)
my_str2 = 'aabb\nbbcc'
res2 = re.match('^[a-z]{4}$',my_str2,flags=re.M)
print(res2)
my_str3 = '\nabc'
res3 = re.match('.',my_str3,flags=re.S)
print(res3)
5.贪婪模式和非贪婪模式:
"""
贪婪模式:在整个表达式匹配成功的前提下,尽可能多的匹配
非贪婪模式:在整个表达式匹配成功的前提下,尽可能少的匹配
正则中的量词包括:{m,n}、?、*和+,这些量词默认都是贪婪模式的匹配,可以在这些量词后面加?将其变为非贪婪模式。
"""
import re
my_str = '<div>test1</div><div>test2</div>'
# 贪婪模式:在整个表达式匹配成功的前提下,尽可能多的匹配
re_obj = re.match('<div>.*</div>', my_str)
print(re_obj)
print(re_obj.group()) # 获取整个正则表达式匹配的内容
# 非贪婪模式:在整个表达式匹配成功的前提下,尽可能少的匹配
re_obj = re.match('<div>.*?</div>', my_str)
print(re_obj)
print(re_obj.group())
十.pymysql
1.基础用法:
#导入模块
import pymysql
#创建连接
conn = pymysql.connect(
host='127.0.0.1',
port=3306,
user='root',
password='qwe123',
database='db_3',
charset='utf8'
)
#创建游标
cursor = conn.cursor()
#执行sql
sql = 'select * from tb_card'
num = cursor.execute(sql)
print(f'查询显示的行数:{num}')
#打印输出
#获取所有结果
# cursor.scroll(0,'absolute') #绝对位置调整,从头开始
# result = cursor.fetchall()
# for i in result:
# print(i)
#每次取一行
# cursor.scroll(0,'absolute')
# result1 = cursor.fetchone()
# print(result1)
#每次取多行
cursor.scroll(1,'relative') #相对位置调整,在绝对位置上加 1
result2 = cursor.fetchmany(3)
for i in result2:
print(i)
#关闭游标
cursor.close()
#关闭连接
conn.close()
2.pymysql的增删改操作
在pymsql中的增删改操作默认是事务操作,非查询操作默认开启一个事务。最后需要提交,否则自动回滚。