# 解释器

# Python解释器是Python程序运行的核心
# 我们写的python代码是人类能够看懂的高级语言,而计算机只能看懂由0和1组成的机器语言
# 解释器就是负责读取Python代码(即.py文件)并将其转换为机器语言,从而使计算机能够执行
# 简单来说,Python解释器就像是Python代码与计算机之间的翻译官,把Python代码翻译成计算机能懂的语言

配置项目解释器

# 打开 PyCharm 项目
# 点击File -> Settings -> Project: [项目名称] -> Python Interpreter
# 选择项目所使用的 Python 解释器。如果没有合适的解释器,可以点击右上角的齿轮图标
# 选择Add来添加新的解释器,如系统安装的 Python 解释器或虚拟环境解释器

# 基础语法

# 注释

  • 单行注释
# 这是一段输出hello world的代码
print('hello world')  
  • 多行注释,用三个引号引起来
"""
哈哈哈
人生苦短
我学python
"""

注意

在pycharm中,可以使用ctrl+?快速进行内容的注释

# 转义字符

\b  # 退格
\e  # 转义
\n  # 换行
\t  # 横向制表符
\'  # 单引号

注意

python 中,字符串前面加上 r 表示原生字符串

print('abc\n123') # abc 换行 123
print('abc\\n123') # abc\n123
print(r'abc\n123') # abc\n123

# 数据类型

  • 可变类型与不可变类型的定义
# 可变数据类型其实就是值在发生改变的时候,内存地址不变,但是原值在变
# 列表、字典、集合

# 不可变的数据类型其实就是修改值的时候,它的内存地址也发生了改变,从而产生了新的值
# 字符串、数字(整型、浮点型、布尔)、元组
  • 整型 int
# 数字也可以加下划线,与不加下划线并无区别
print(1_2_3)
  • 浮点型 float
# 在Python中,表示较大的数时,也可以使用科学计数法
# 1e6表示1000000.0,其中e表示x10,也就是1x10^6
# 这里的1e6其实并不是整数,可以看到后面保留了一位小数,属于浮点型
# 浮点数一般都会有误差,计算机只会识别0和1的二进制数,而在浮点数转换为二进制时,会出现偏差
  • 布尔类型 bool
# 在python中True表示真,False表示假
# 真也可以用1表示,假也可以用0表示

# 在python也有一些特殊值表示Fasle,除开这些特殊值,其他转bool值都是True
# 0、""、[]、()、{}、set()、None

# None类型的应用场景:
# 1、用在函数无返回值上
# 2、在if判断中,None等同于False
# 3、用于声明无内容的变量上:name = None
  • 字符串 str
# 相比与java和c来讲,python的字符串是可以通过双引号或者单引号来定义字符串的
print("ab")
print('哈哈哈')

# 多行字符串
print("""
     春眠不觉晓,处处闻啼鸟。
     夜来风雨声,花落知多少。
""")


# 字符串的格式化方式一
name = "张三"
age = 18
height = 178.5
province = "山东省"
city = "济南市"

message ="姓名 %s" % name # s 取代字符串,也能取代数字
message ="年龄 %d" % age # d 取代数字
message ="身高 %f" % height # f 取代浮点数
message ="所在省份 %s,城市 %s" % (province,city) # 多个变量要用括号括起来

# 数字精度控制
%5d # 表示将整数的宽度控制在5位
    # 如数字11,被设置为5d,就会变成:[空格][空格][空格]111,用三个空格补足宽度
%5.2f # 表示将宽度控制为5,将小数点精度设置为2
# 小数点和小数部分也算入宽度计算,如对11.345设置了%7.2f后,结果是:[空格][空格]11.35


# 字符串的格式化方式二
# f"内容{变量}"的格式来快速格式化,这种方式:不理会类型,不做精度控制
print(f"我是{name},年龄:{age},身高:{height}")

# 字符串前面加 r 表示创建一个原始字符串
normal_string = 'C:\new_folder\test.txt' # 普通字符串
print(normal_string) # 输出 C:  ew_folder	est.txt
raw_string = r'C:\new_folder\test.txt' # 原始字符串
print(raw_string) # 输出 C:\new_folder\test.txt

# 字符串常用方法
字符串[下标]                       # 根据下标索引取出特定位置字符
字符串.index(字符串)               # 查找给定字符的第一个匹配项的下标
字符串.replace(字符串1,字符串2)     # 将字符串内的全部字符串1,替换为字符串2
字符串.split(字符串)                # 按照给定字符串,对字符串进行分隔
字符串.strip()/字符串.strip(字符串) # 移除首尾的空格和换行符或指定字符串
字符串.count(字符串)               # 统计字符串内某字符串的出现次数
len(字符串)                        # 统计字符串的字符个数
  • 列表 list
# 列表就是一组有序且可变的数据集合
# 允许重复数据存在
# 可以容纳不同类型的元素
# 使用 [] 表示

lst = []    # 创建一个空列表
name_lst = ['小张','小石','小毅','小昊',100]  # 初始化一个列表
name_lst = [[1, 2, 3], [4, 5, 6]]  # 嵌套的列表

# 可以反向索引,也就是从后向前:从-1开始,依次递减(-1、-2、-3......)
print(name_lst[-1]) # 结果:100
print(name_lst[-2]) # 结果:小昊

# 常用方法
列表.append(元素)        # 向列表中追加一个元素
列表.extend(容器)        # 将数据容器的内容依次取出,追加到列表尾部
列表.insert(下标,元素)    # 在指定下标处,插入指定的元素
del列表[下标]            # 删除列表指定下标元素
列表.pop(下标)           # 删除列表指定下标元素,返回删除的元素
列表.remove(元素)        # 从前向后,删除此元素第一个匹配项
列表.clear()             # 清空列表
列表.count(元素)         # 统计此元素在列表中出现的次数
列表.index(元素)         # 查找指定元素在列表的下标找不到报错
len(列表)                # 统计容器内有多少元素


# 列表的 sort 方法
my_list = [["a",33],["b",55],["c",11]]

def my_func(x):
    return x[1]

# 将元素传入my_func商数中,用来确定按照谁来排序
my_list.sort(key=my_func, reverse=True)
# 使用lambda函数
my_list.sort(key=lambda x:x[1])
print(my_list)

函数和方法的区别

如果将函数定义为 class(类) 的成员,那么函数会称之为方法

  • 元组 tuple
# 元组就是一组数据的集合,其数据只能访问而不能改变
# 允许重复数据存在
# 可以容纳不同类型的元素
# 使用 () 表示 

content = () # 设置一个空元组
content1 = tuple() # 设置一个空元组
content2 = (1, '小张', True)) # 初始化一个元组
content3 = ('大表哥',) # 只有一个元素的元组
content4 = ((123)(456)) # 元组的嵌套
print(type(content)) # 查看其类型

# 不需要小括号也是可以的,但是必须得有逗号进行分隔,就算只有一个元组,也要用逗号进行分隔
cont = "小石","小毅","小昊"

# 常用方法
num = t5[1][2]         # 下标索引专取出内容
元组.index(元素)        # 查找某个数据,如果数据存在返回对应的下标,否则报错
元组.count(元素)        # 统计某个数据在当前元组出现的次数
len(元组)               # 统计元组内的元素个数

元组本身不可变,但如果元组包含其他可变的元素,那些可变元素可以改变

t9=(1,2,["itheima","itcast"])
t1[2][1] = 'best'
print(t1) # 结果: (1,2,['itheima',best'])
  • 集合 set
# 集合就是一组不重复、且无序的一组数据集合
# 集合是无序的,所以集合不支持:下标索引访问
# 集合和列表一样,是允许修改的
# 使用 {} 表示 

cont = set() # 定义一个空集合,必须使用set()
cont2 = {2, 4, 5 , 1, 2, 3} # 给集合初始化值
print(cont2) # 会输出有序且无重复的集合  {1, 2, 3, 4, 5}

# 集合的数学运算
set1 = {1,2,3}
set2 = {4,5,6,1,2}
 
print(set1&set2) # 输出两个集合的交集
print(set1|set2) # 并集
print(set1-set2) # 输出set1中去除set2的元素
print(set2-set1) # 输出set2中去除set1的元素
print(1 in set1) # 看1是否在set1中
print(7 not in set2) # 看7是否不在set2中

# 常用方法
集合.add(元素)                  # 集合内添加一个元素
集合.remove(元素)               # 移除集合内指定的元素
集合.pop()                      # 从集合中随机取出一个元素
集合.clear()                    # 将集合清空
集合1.difference(集合2)         # 得到一个新集合,内含2个集合的差集
集合1.difference_update(集合2)  # 在集合1中,删除集合2中存在的元素
集合1.union(集合2)              # 得到1个新集合,内含2个集合的全部元素
len(集合)                       # 得到一个整数,记录了集合的元素数量

注意

集合是不能存储可变数据类型的,只能存储不可变数据类型

  • 字典 dict
# 主要是不重复无序的,主要采用键值对的方式来存储数据
# 字典的 Key 和 Value 可以是任意数据类型
# 字典是可以嵌套的

dic1 = {}  # 创建一个空字典
dict2 = dict() # 创建一个空字典
dict3 = {'name':'小石', 'age':18, 'hobby':'战斗'} 

# 常用方法
print(dic['name'])    # 通过键值对的方式去访问值
dic['name'] = '小张'  # 修改字典的'name'值
字典.pop(Key)         # 取出Key对应的Value并在字典内删除此Key的键值对
字典.clear()          # 清空字典
字典.keys()           # 获取字典的全部Key,可用于for循环遍历字典
len(字典)             # 计算字典内的元素数量

注意

  • 字典是不重复的,字典的键如果相同,就取最后一个重复键的值
  • 新增和更新元素的语法一致,如果Key不存在即新增,如果Key存在即更新
  • 容器通用功能
通用for循环        # 遍历容器(字典是遍历key)
max()/min()/len() # 容器内最大、小元素,个数

str()   # 转字符串
int()   # 转整型
bool()  # 转布尔值
list()  # 转换为列表
tuple() # 转换为元组
set()   # 转换为集合
sorted(序列,[reverse=True])  # 排序,reverse=True表示降序
  • 切片的使用
# 语法 : 序列 [起始下标 : 结束下标 : 步长] 
# 表示从序列中,从指定位置开始,依次取出元素,到指定位置结束,得到一个新序列
# 起始下标表示从何处开始,可以留空,留空视作从头开始
# 结束下标(不含)表示何处结束,可以留空,留空视作截取到结尾

# 对list进行切片:从1开始,4结束,步长1
my_list = [0, 1, 2, 3, 4, 5, 6]
result1 = my_list[1:4] # 步长默认是1,所以可以省略不写

# 对tuple进行切片,从头开始,到最后结束,步长1
my_tuple = (0,1,2,3,4,5,6)
result2 = my_tuple[:] # 起始和结束不写表示从头到尾,步长为1可以省略

# 对str进行切片,从头开始,到最后结束,步长2
my_str = "01234567"
result3 = my_str[::2]

# 对str进行切片,从头开始,到最后结束,步长-1
my_str = "01234567"
result4 = my_str[::-1] # 等同于将序列反转了

# 对列表进行切片,从3开始,到1结束,步长-1
my_list = [0, 1, 2, 3, 4, 5, 6]
result5 = my_list[3:1:-1]

# 对元组进行切片,从头开始、到尾结束,步长-2
my_tuple = (0, 1, 2, 3, 4, 5, 6)
result6 = my_tuple[::-2]

# 运算符

  • 算术运算符
除 /        # x 除以 y,除出来的数应该是一个小数
取整数 //    # 返回商的整数部分(向下取整),9 // 2 ——>4
幂 **       # x的y次幂	a**b为10的20次方
  • 成员运算符 in、not in
lst = [1,2,3,4]   # 定义一个列表
print(1 in lst)
print(1 not in lst)
 
>> True
>> False
  • 身份运算符 is、is not
# is和==有区别,==只是看两个值是否长得一样,is是比较内存地址
# 可以用id()查看一个值的内存地址

# 判断语句

# 定义变量
age = 30

# 进行判断
if age >= 18 and age!= null:
    print("我已经成年了") # 需要4个空格作为缩进
elif age >= 40:
    print("我已经中年了") # 需要4个空格作为缩进
else:
    print("我已经老年了")	 # 需要4个空格作为缩进

# 循环语句

  • while 循环
# 定义变量
i = 0

# 进行判断,基于空格缩进来决定层次关系
while i < 100:
    print("hello") # 需要4个空格作为缩进
    i += 1
	j = 1
	while j <= 10:
	    print(f"送给小美第{j}只玫瑰花")
	    j += 1
  • for 循环
#定义字符串name
name="itheima"

# for循环,本质上是遍历:序列类型
for x in name:
    print(x)
  • range 循环
range(num) # 从0开始到num结束的数字序列(不含num本身)
range(num1,num2) # 从num1开始到num2结束的数字序列(不含num2本身)
range(num1,num2,step) # 从num1开始到num2结束的数字序列(不含num2本身),step步长(默认为1)
for x in range(5):
    print(x) # 0,1,2,3,4
	
for x in range(5,10):
    print(x) # 5,6,7,8,9
		
for x in range(5,10,2):
    print(x) # 5,7,9

# 函数的用法

  • 函数的定义
num = 100

# 定义函数
def add(x, y):
    """
    函数说明
    :param x: 参数1
    :param y: 参数2
    :return: 返回值
    """
	global num
    num = x + y
    print(f"{x} + {y}的结果是: {num}")
	
# 调用函数
add(5, 6)

注意

  • 如果函数没有使用 return 语句返回数据,实际上就是返回了 None 这个字面量
  • 使用 global 关键字,可以在函数内部声明变量为全局变量
  • 多个返回值的函数
def test return():
    return 1,"hello", True

x,y,z = test_return
print(x) # 结果 1
print(y) # 结果 hello
print(z) # 结果 True
# 按照返回值的顺序,写对应顺序的多个变量接收即可
# 支持不同类型的数据 return
  • 位置参数:调用函数时根据函数定义的参数位置来传递参数
def user_info(name, age, gender):
    print(f'您的名字是:{name}, 年龄是{age}, 性别是{gender}')

# 传递的参数和定义的参数的顺序及个数必须一致
user_info('TOM', 20,'男')
  • 关键字参数:函数调用时通过 "键=值" 形式传递参数
def user info(name, age, gender):
    print(f"您的名字是:{name}, 年龄是:{age}, 性别是:{gender}")

# 关键字传参
user_info(name="小明", age=20, gender="男")
# 可以不按照固定顺序
user_info(age=20, gender="男", name="小明")
# 可以和位置参数混用,位置参数必须在前,且匹配参数顺序
user_info("小明", age=20, gender="男")
  • 缺省参数:缺省参数也叫默认参数,用于定义函数,为参数提供默认值
def user_info(name, age, gender='男'):
    print(f'您的名字是:{name}, 年龄是{age}, 性别是{gender}')

# 传递的参数和定义的参数的顺序及个数必须一致
user_info('TOM', 20, '男')
user_info('Rose', 18, '女')
  • 不定长参数:不定长参数也叫可变参数:用于不确定调用的时候会传递多少个参数
# 位置传递 *args
def user_info(*args):
    print(args)
	
# 传进的所有参数都会被args变量收集,合并为一个元组(tuple),args是元组类型
user info('ToM', 18)

# 关键字传递 **kwargs
def user info(**kwargs):
    print(kwargs)

# 传进的所有参数都会被kwargs变量收集,合并为一个字典(dict),kwargs是字典类型
user_info(name='小王', age=11, gender='男孩', addr='北京')
  • 函数作为参数传递
# 函数 compute 作为参数,传入了test_func函数中使用
def test_func(compute):
    result =compute(1, 2)
    print(result)
	
def compute(x, y):
    return x+y

test_func(compute) # 结果:3	
  • lambda匿名函数
# def关键字,可以定义带有名称的函数,可以基于名称重复使用
# lambda关键字,可以定义匿名函数(无名称),只可临时使用一次

def test func(compute):
    result = compute(12)
    print(result)
	
# 通过lambda关键字,传入一个一次性使用的lambda匿名函数
# 函数体,就是函数的执行逻辑,要注意:只能写一行,无法写多行代码
test_func(lambda x,y:x+y)

# 闭包

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

def account(initial_amount = 0):
    def atm(num, deposit=True):
	    # 需要使用nonlocal关键字修饰外部函数的变量才可在内部函数中修改它
        nonlocal initial_amount
        if deposit:
            initial_amount += num
            print(f"存款:+{num},账户余额:{initial_amount}")
        else:
            initial_amount -= num
            print(f"取款:-{num},账户余额:{initial_amount}")
    return atm

fn = account()
fn(300)
fn(200)

# 装饰器

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

  • 装饰器的一般写法(闭包写法)
def outer(func):
    def inner():
        print("我要睡觉了")
        func()
        print("我起床了")
    return inner

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

f = outer(sleep)
f()
# 我要睡觉了
# 睡眠中......
# 我起床了
  • 装饰器的语法糖写法
def myFunc(func):
    def inner():
        print("我要睡觉了")
        func()
        print("我起床了")
    return inner

# 使用@myFunc定义在目标函数sleep之上
@myFunc
def sleep():
    import random
    import time
    print("睡眠中......")
    time.sleep(random.randint(1,5))

sleep()
# 我要睡觉了
# 睡眠中......
# 我起床了

# 类的使用

# 基础介绍



 


 

 





 




 


 



# 定义类
class Student:
    # 成员属性可以不用定义
    name: str
    age: int
    grade: int = 0  # 默认值
	
    # 构造方法,返回none
    def __init__(self, name: str, age: int, grade: int) -> None:
        self.name = name
        self.age = age
        self.grade = grade
		
    # 方法,返回int
    def get_grade(self) -> int:
        return self.grade


# 创建类对象,导包
from Stu import Student

# 不需要 new
stu = Student("张三", 20, 99)
stu.get_grade()

self 关键字是成员方法定义的时候,必须填写的

  • 它用来表示类对象自身的意思
  • 当我们使用类对象调用方法的是,self会自动被python传入
  • 在方法内部,想要访问类的成员变量,必须使用self

# 魔术方法(内置的类方法)

# __init__ 构造方法
# __str__ 将类装欢为字符串
def __str__(self):
	return f"Student类对象,name={self.name},age={self.age},grade={self.grade}"
	
stu = Student("张三", 20, 99)
print(stu)

# __lt__ 小于、大于符号比较
def __lt__(self, other):
	return self.age < other.age
		
stu1 = Student("张三", 20, 99)
stu2 = Student("李四", 30, 89)
print(stu1 < stu2) # True	

# __le__小于等于、大于等于符号比较
def __le__(self, other):
  return self.age <= other.age

stu1 = Student("张三", 20, 99)
stu2 = Student("李四", 30, 89)
print(stu1 <= stu2) # True	

# __eq__ 等于符号比较
def __eq__(self, other):
  return self.age == other.age

stu1 = Student("张三", 20, 99)
stu2 = Student("李四", 30, 89)
print(stu1 == stu2) # False

# 私有成员

私有成员无法被类对象使用,但是可以被其它的成员使用


 


 




 




 



class Student:
    # 私有成员变量:变量名以__开头(2个下划线)
    __gender: str

    # 私有成员方法:方法名以__开头(2个下划线)
    def __getGender(self):
        return self.__gender
    
	# 可以被其它的成员使用
	def __init__(self, name: str, age: int, gender: str) -> None:
        self.name = name
        self.age = age
        self.__gender = gender
		
    # 可以被其它的成员使用
    def __str__(self):
        return f"Student类对象,name={self.name},age={self.age},gender={self.__gender}"

# 类的继承

  • 单继承










 











# Person类
class Person:
    name:str
    
    def getName(self):
        return self.name
		
# Student类
from Per import Person


class Student(Person):

    def __init__(self, name: str, age: int, grade: int, gender: str) -> None:
        self.name = name
        self.age = age
        self.grade = grade
        self.__gender = gender
		
	def get_grade(self) -> int:
        return self.grade
  • 多继承
 
 


class MyPhone(Phone,NFCReader,RemoteControl):
	  # 没有方体的方法
      pass

注意事项

多继承中,如果有同名的成员,默认的继承顺序为从左到右,即先继承的保留,后继承的被覆盖

# 类的重写

即在子类中重新定义同名的属性或方法









 


 



class phone:
	IMEI = None #序列号
	producer = "ITCAST" #厂商

    def call_by_5g(self):
		print("父类的5g通话")
		
class MyPhone(phone):
    # 重写父类属性
	proucer = "ITHEIMA"
	
	# 重写父类方法
	def call_by_5g(self):
		print("子类的5g通话")

如果需要使用被复写的父类的成员,需要特殊的调用方式:













 





 







class phone:
	IMEI = None #序列号
	producer = "ITCAST" #厂商

    def call_by_5g(self):
		print("父类的5g通话")
		
class MyPhone(phone):
	proucer = "ITHEIMA"
	
	def call_by_5g(self):
	
	    # 方式一
		# 父类名.成员变量
		print(f"父类的品牌是:{phone.producer}")
		# 父类名.成员方法(self)
		phone.call_by_5g(self)
		
		# 方式二
		# 用super().成员变量
		print(f"父类的品牌是:{super().producer}")
		# super().成员方法()
		super().call_by_5g()
		
		print("子类的5g通话")

# 类的多态

多态常作用在继承关系上,以父类做定义声明,以子类做实际工作,用以获得同一行为的不同状态

class Animal:
	def speak(self):
		pass
		
class Dog(Animal):
	def speak(self):
		print("汪汪汪")
	
class Cat(Animal):
	def speak(self):
		print("喵喵喵")
		
def make_noise(animal: Anima1):
    anima1.speak()
	
dog = Dog()
cat = Cat()

make_noise(dog) # 输出:汪汪注
make_noise(cat) # 输出:喵喵喵

# 抽象类(接口)

父类用来确定有哪些方法,具体的方法实现,由子类自行决定

抽象类 # 含有抽象方法的类称之为抽象类
抽象方法 # 方法体是空实现的(pass)称之为抽象方法

# 类型注解

# 类型注解的语法

为变量设置注解,显示的变量定义,一般无需注解

# 基础数据类型注解
var1:int = 10
var2:float = 21.56
var3:bool = True
var4:str = "张三"

# 类对象的注解
class student:
	pass
		
stu:student = student()

# 基础容器类型注解
my_list:list = [1, 2, 3]
my_tuple:tuple = (1, 2, 3)
my_set:set = {1, 2, 3}
my_dict:dict = {"name": sylone}

# 容器类型详细注解
my_list:list[int] = [1, 2, 3]
my_tuple:tuple[str, int, bool] = ("sylone", 666, True)
my_set:set[int] = {1, 2, 3}
my_dict:dict[str, int] = {"sylone": 666}

# 函数(方法)的类型注解:形参注解,返回值注解
def func(name: str, age: int, grade: int) -> None:
	pass

# 除了使用 变量:类型,这种语法做注解外,也可以在注释中进行类型注解
var1 = random.randint(l, 10) # type: int
var2 = json.loads(data)  # type: dict[str, int]
var3 = student()   # type: student

注意:类型注解的作用

  • 帮助第三方IDE工具(如PyCharm)对代码进行类型推断,协助做代码提示
  • 帮助开发者自身对变量进行类型注释
  • 并不会真正的对类型做验证和判断,类型注解仅仅是提示性的
# 这样的代码,是不会报错的哦
var1:int = "itheima"
var2:str = 123

# Union联合类型注解

# 先导包
from typing import Union

my_list:list[Union[str, int]] = [1, 2, "itheima", "itcast"]
my_dict:dict[str, union[str, int]]={"name": "sylone", "age": 31}

def func(data: union[int, str]) -> union[int, str]:
    pass

# 文件操作

# 打开文件

使用open函数,可以打开一个已经存在的文件,或者创建一个新文件,语法如下:

# name 是要打开的目标文件名的字符串(可以包含文件所在的具体路径)

# mode 设置打开文件的模式:只读、写入、追加等
#      r:以只读方式打开文件。文件的指针将会放在文件的开头。这是默认模式
#      w:打开一个文件只用于写入。如果该文件已存在则打开文件,并从开头开始编辑,原有内容会被删除
#        如果该文件不存在,创建新文件
#      a:打开一个文件用于追加。如果该文件已存在,新的内容将会被写入到已有内容之后
#        如果该文件不存在,创建新文件进行写入

# encoding 编码格式(推荐使用UTF-8)
open(name, mode, encoding)

f = open("python.txt", "r", encoding="UTF-8")
# encoding的顺序不是第三位,所以不能用位置参数,用关键字参数直接指定

# 读取操作

f.read(num)
# num表示要从文件中读取的数据的长度(单位是字节),如果没有传入num,那么就表示读取文件中所有的数据
f = open('python.txt')
content = f.read(10) # 读取10个字节

f.readline()
# 读取一行数据
line1 = f.readline()
line2 = f.readline()
print(f"第一行数据是:{line1}")
print(f"第二行数据是:{line2}")

f.readlines()
# 可以按照行的方式把文件中的内容进行一次性读取,返回的是一个list列表,其中每一行的数据为一个元素
f = open('python.txt')
content = f.readlines()
print(content)
# ['hello world\n', 'abcdefg\n', 'aaa\n', 'bbb\n', 'ccc']
f.close() # 关闭文件

# for循环读取文件行
for line in open("python.txt", "r"):
    # 每一个line临时变量,就记录了文件的一行数据
    print(line)

# 关闭文件对象

f = open("python.txt","r")
f.close()
# 最后通过close,关闭文件对象,也就是关闭对文件的占用
# 如果不调用close,同时程序没有停止运行,那么这个文件将一直被python程序占用

with open("python.txt", "r") as f:
    f.readlines()
# 通过在with open的语句块中对文件进行操作
# 可以在操作完成后自动关闭close文件,避免遗忘掉close方法

# 写入操作

# 直接调用write,内容并未真正写入文件,而是会积攒在程序的内存中,称之为缓冲区
# 当调用flush的时候,内容会真正写入文件
# 这样做是避免频繁的操作硬盘,导致效率下降(攒一堆,一次性写磁盘)

# 1.打开文件
f= open('python.txt', 'w')
# 2.文件写入
f.write('hello world')
# 3.内容刷新
f.flush()
# 4.或者close方法,内置了flush的功能
f.close()


url='http://www.1ge0.com/imgs/logo.jpg'
r=requests.get(url)
print(r.status_code) # 状态码 200

# 以二进制写入模式('wb')进行操作
with open('logo.jpg','wb') as f:
    f.write(r.content) # 图片
	

# 追加写入操作
f= open('python.txt', 'a') # 文件不存在会创建文件
f.write('hello world')
f.flush()
# 或者close方法,内置了flush的功能
f.close()

# 综合案例

# 打开文件得到文件对象,准备读取
fr = open("D:/bill.txt", "r", encoding="UTF-8")
# 打开文件得到文件对象、准备写入
fw = open("D:/bill.txt.bak", "w", encoding="UTF-8") 
# for循环读取文件
for line in fr:
    line = line.strip()
	# 判断内容,将满足的内容写出
	if line.split(",")[4] == "测试":
		continue # continue进入下一次循坏,这一次后面的内容就跳过了
	# 将内容写出去
	fw.write(line)
	# 由于前面对内容进行了strip()的操作,所以要手动的写出换行符
	fw.write("\n")
# close2个文件对象
fr.close()
# 写出文件调用close()会自动flush()
fw.close()

# 异常处理

  • 常规异常
try:
	# 可能发生错误的代码
except:
	# 如果出现异常执行的代码
  • 捕获指定异常
try:
	print(name)
except NameError as e:
	print('name变量名称未定义错误')
  • 捕获多个异常
try:
	print(1/0)
# 使用元组的方式进行书写
except (NameError, ZeroDivisionError):
	print('ZeroDivision错误...')
  • 捕获所有异常
try:
	f= open("D:/123.txt","r")
except Exception as e:
	print("出现异常了")
  • 异常 else
try:
	print(1)
except Exception as e:
	print(e)
else:
	print("我是else,是没有异常的时候执行的代码")
  • 异常 finally 表示的是无论是否异常都要执行的代码
try:
	f = open('test.txt', 'r')
except Exception as e:
	f = open('test.txt', 'w')
else:
	print('没有异常,真开心')
finally:
	f.close()

# 模块的介绍

模块 Module,是一个 Python 文件,以.py 结尾,模块能定义函数、类和变量,也能包含可执行的代码

# 模块的导入方式

[from 模块名] import [模块 || 变量 | 函数 | * ] [as 别名]

# 常用的组合形式

  • import 模块名
# 导入时间模块
import time

print("开始")
# 让程序睡眠1秒(阻塞)
time.sleep(1)
print("结束")
  • from 模块名 import 功能名
# 导入时间模块中的sleep方法
from time import sleep

print("开始")
# 让程序睡眠1秒(阻塞)
sleep(1)
print("结束")
  • as 定义别名
# 模块别名
import time as tt
tt.sleep(2)
print('hello")


# 功能别名
from time import sleep as sl
sl(2)
print('hello")

# 常用变量

def test(a, b):
	print(a + b)

# PyCharm 中右键运行的时候__name__=='__main__'
if __name__ == '__main__':
	test(1, 2)
# 通过在模块文件中设置 __all__ 变量,当其它文件以 from 模块名 import * 的形式导入该模块时
# 该文件中只能使用 __all__ 列表中指定的成员

# test1.py
__all__ = ["test"]
def test():
    print('----test-----')
      
def test1():
    print('----test1----')


# main.py
from test1 import *
      
def main():
    test()
      
    #test1()
      
main()

# 内置模块

  • os 模块
os.getenv('path') # 读取环境变量path的信息
os.path.split(r'D:\Java\nginx-1.9.9.zip') # 分割路径,一个是目录,一个是文件名
os.path.dirname(r'D:\Java\nginx-1.9.9.zip') # 获得目录
os.path.basename(r'D:\Java\nginx-1.9.9.zip') # 返回文件名
os.path.exists(r'D:\Java\nginx-1.9.9.zip') # 判断路径是否存在
os.path.isabs(r'D:\Java\nginx-1.9.9.zip') # 判断是否是绝对路径
os.path.abspath('./') # 返回当前路径的绝对路径
os.path.abspath('../') # 返回上层路径的绝对路径
os.path.isfile(r'D:\Java\nginx-1.9.9.zip') # 判断一个文件是否存在
os.path.isdir(r'D:\Java') # 判断一个文件夹是否存在
  • sys 模块
sys.getdefaultencoding() # 获取系统当前编码
sys.path # 获取使用PYTHONPATH环境变量的值
sys.platform # 获取当前系统平台名称
sys.version # 获取Python解释程序的版本信息
  • time 模块
time.time() # 获取时间戳 1740917414.0492156
time.localtime() # 获取本地时间  time.struct_time(tm_year=2022, tm_mon=8, tm_mday=13, tm_hour=10, tm_min=57, tm_sec=14, tm_wday=3, tm_yday=223, tm_isdst=0)
time.asctime() # 获取本地时间字符串  Sun Mar  2 20:11:04 2025
time.strftime('%Y-%m-%d',time.localtime()) # 格式化时间字符串
time.strptime('2025-05-20 12:12:25','%Y-%m-%d %H:%M:%S') # 将格式字符串转化成struct_time
  • logging 模块
DEBUG # 调试
INFO  # 信息
WARNING  # 警告
ERROR  # 错误
CRITLCAL  # 危险信号

import logging

# 简单使用
formatLog = '日志时间:%(asctime)s - 日志级别:%(levelname)s - 日志内容:%(message)s'
logging.basicConfig(format=formatLog, filename=r'D:\test.log', level=logging.DEBUG)
logging.debug('This message should go to the log file')

# 日志管理器的使用
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
# 处理器
sh = logging.StreamHandler() # 控制台输出
sh.setLevel(logging.DEBUG)
fh = logging.FileHandler(r'D:\test.log',encoding='utf-8') # 文件输出
fh.setLevel(logging.ERROR)
# 格式器
formatter = logging.Formatter(formatLog)
sh.setFormatter(formatter)
fh.setFormatter(formatter)
# 日志器添加处理器
logger.addHandler(sh)
logger.addHandler(fh)
# 日志器输出
logger.debug('This message should go to the log file')
logger.info('So should this')
logger.warning('And this, too')

# 包文件的介绍

包就是一个文件夹,在该文件夹下包含了一个 _ init _.py 文件,该文件夹可用于包含多个模块文件

# 在项目根目录右击-> New -> Python Package -> 输入包名称 -> 点击OK
# 新建包后,包内部会自动创建 _init_.py 文件

# 导入包

  • 方式一
# import 包名.模块名
# 包名.模块名.目标

import my_package.my_module1
import my_package.my_module2

# 包中的my_module1模块的info_print1()方法
my_package.my_module1.info_print1()
# 包中的mymodule2模块的info_print2()方法
my_package.my_module2.info_print2()
  • 方式二
# 注意: 必须在 _init_.py 文件中添加 _all_=[] 控制允许导入的模块列表
# _init_.py
all = ["my_module2"]

# text_my_module.py 
from my_package import *

# 包中的my_module1模块的info_print1()方法
my_modulel.info_print1() # 报错
# 包中的mymodule1模块的info print2()方法
my_module2.info_print2()

# 第三方包的使用

  • python 命令
py -0      # 可以列出所有已安装的 Python 版本
py -版本号  #来运行特定版本的 Python
  • 使用 pip 命令
# 查看已安装的 Python 包列表
pip list
python.exe -m pip list
py -3.9 -m pip install numpy

# 安装包文件
pip install pandas

# 可以通过如下命令,让其连接国内的网站进行包的安装
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple 包名称
  • PyCharm 安装第三方包
# 在 PyCharm 中,点击File -> Settings -> Project: [项目名称]
# 在 Python Interpreter 页面中,点击+号
# 在弹出的 Available Packages 窗口中搜索需要的依赖库,如pandas、numpy等
# 选中后点击 nstall Package 按钮,PyCharm 会自动下载并安装该库到项目的解释器环境中
  • 手动导入本地库
# 添加本地库路径
# 有一些本地开发的依赖库或第三方库的本地副本,需要将其所在的文件夹路径添加到项目的Python Path中
# 在 PyCharm 中,点击File -> Settings -> Project: [项目名称] -> Project Structure
# 点击 Add Content Root 或 Add Source Root 按钮,选择本地库所在的文件夹,将其添加到项目结构中

# 标记为依赖库
# 在项目的Python Path中添加本地库路径后,还需要在 Python Interpreter 页面中
# 右键点击添加的本地库文件夹,选择 Mark as -> Sources Root 或 Mark as -> Test Sources Root 等
# 将其标记为相应的源文件或测试文件根目录,以便 PyCharm 正确识别和加载其中的模块

# json 的使用

import json

data = [{"name":"张大山","age":11},{"name":"王大锤","age": 13}]
# ensure_ascii=False 是否使用ascii编码
json_str = json.dumps(data, ensure_ascii=False)
print(json_str)
# json字符串转List
loads = json.loads(json_str)
print(loads)

# 正则表达式

# match() 方法的基本使用

  • 从字符串的起始位置匹配一个正则表达式,如果起始位置匹配不成功,返回None
  • 如果匹配到了数据,使用group方法来提取数据
import re

res = re.match('S', 'six')
print(res.group()) # 匹配失败 返回 None

res = re.match('s', 'six')
print(res.group()) # 匹配成功 返回 s
  • 匹配单个字符
.  # 匹配任意1个字符(除了\n)
[] # 匹配[ ]中列举的一个字符
\d # 匹配数字,即0-9
\D # 匹配非数字,即不是数字
\s # 匹配空白,即空格,tab键
\S # 匹配非空白
\w # 匹配非特殊字符,即a-z,A-Z,0-9,_,汉字
\W # 匹配特殊字符
# 匹配任意1个字符
res = re.match('t.o', 'too')
print(res.group()) # too

# 匹配[ ]中列举的一个字符
res = re.match('[hH]', 'hello')
print(res.group()) # h
res = re.match('^ab','abs')  # 匹配开头
print(res.group())

res = re.match('[0123456789]hello','1hello') 
print(res.group()) # 1hello
res = re.match('[0123456789]h2ello','1hello')
print(res.group()) # 匹配失败 返回 None
res = re.match('[0-9]hello','1hello') 
print(res.group()) # 1hello
res = re.match('[0-35-9]hello','1hello') # 匹配0-9但不匹配4
print(res.group()) # 1hello

# 匹配数字,即0-9
res = re.match('今天是2\d号','今天是21号了吗')
print(res.group()) # 今天是21号

# 匹配空白,即空格,tab键
res = re.match('\sh',' hello python')
print(res.group()) # 空白h
res = re.match('\S','hello python')
print(res.group()) # h

# 匹配非特殊字符
res = re.match('\w','hello python')
print(res.group()) # h
  • 匹配多个字符
*     # 匹配前一个字符出现0次或者无数次,即可有可无
+     # 匹配前一个字符出现1次或者无数次,即至少有1次
?     # 匹配前一个字符出现1次或者0次,即最多1次
{m}   # 匹配前一个字符出现m次
{m,n} # 匹配前一个字符出现m到n次
# 匹配前一个字符出现0次或者无数次,即可有可无
res = re.match('[A-Z][a-z]*','Hello python')
print(res.group()) # Hello
res = re.match('[a-z]*python[a-z]*','hellopythonaaa')
print(res.group()) # hellopythonaaa

# 匹配前一个字符出现1次或者无数次,即至少有1次
res = re.match('[a-zA-Z]+python','hellopython')
print(res.group()) # hellopython

# 匹配前一个字符出现1次或者0次,即要么有1次要么没有
res = re.match('[1-9]?[0-9]','1234')
print(res.group()) # 12
res = re.match('[1-9]?\d','1234')
print(res.group()) # 12

# 匹配前一个字符出现m次
res = re.match('[0-9]{6}','123456789')
print(res.group()) # 123456

res = re.match('[a-zA-Z0-9_]{6,8}','a12345678')
print(res.group()) # a1234567
  • 匹配开头和结尾
^          # 匹配字符串开头
$          # 匹配字符串结尾,匹配的字符串必须符合正则表达式的要求位数
[^指定字符] # 除了指定字符外都匹配
res = re.match('^[0-9]','1abs')  # 匹配数字开头
print(res.group()) # 1

res = re.match('[^0-9]','abs')  # 匹配非数字开头
print(res.group()) # a

res = re.match('[1-9]?\d$', '10')  # 匹配数字结尾
print(res.group()) # 10

res = re.match('^[0-9]{3}\-[0-9]{3,8}$', '010-12345')
print(res.group()) # 提取数据 010-12345
  • 匹配分组
|          # 匹配左右任意一个表达式
(ab)       # 将括号中的字符作为一个分组
\num       # 引用分组 num 匹配到的字符串
(?P<name>) # 分组起别名
(?P=name)  # 引用别名为name分组匹配到的字符串
# 将括号中的字符作为一个分组
res = re.match('\w{4,20}@(163|qq|139).com', 'test@163.com')
print(res.group()) # 匹配全部内容:test@163.com
print(res.group(0)) # 匹配全部内容:test@163.com
print(res.group(1)) # 匹配第1个分组的内容:163

# 引用分组 num 匹配到的字符串
res = re.match('<(\w*)>\w*</\\1>', '<html>title</html>') # \1 代表引用第一个分组的内容
# \转义符 用\转义一下,来表示引用。否则会认为是普通字符,在字符串里原样输出
print(res.group()) # <html>title</html>
# 或者用 r 原样输出
res = re.match(r'<(\w*)>\w*</\1>', '<html>title</html>')

# 分组起别名
# ?P<n1>是给分组起名字 ?P=n1是引用分组
res = re.match('<(?P<n1>\w*)><(?P<n2>\w*)>.*</(?P=n2)></(?P=n1)>', '<h1><a>hello</a></h1>')
print(res.group()) # <h1><a>hello</a></h1>

# 高级用法

  • search() 扫描整个字符串并返回第一个成功的匹配,match() 只能从开始位置匹配
res = re.search('\d+', '我今年18岁,明年19岁')
print(res.group()) # 18

res = re.match('ab', '12abc')
print(res.group()) # 返回none 报错

res = re.search('ab', '12abc')
print(res.group()) # ab
  • findall() 以列表的形式返回匹配到的字符串,直接打印结果
res = re.findall('\d+', '我今年18岁,明年19岁')
print(res) # ['18', '19']

res = re.findall('[^\d+]', '我今年18岁,明年19岁')
print(res) # ['我', '今', '年', '岁', ',', '明', '年', '岁']
  • sub() 将匹配到的数据进行替换
res = re.sub('\d+','60', '我今年18岁,明年19岁')
print(res) # 我今年60岁,明年60岁
# count 替换的次数
res = re.sub('\D+','60', '我今年18岁,明年19岁', count=2)
print(res) # 6018601960
  • split() 根据匹配进行切割字符串,并返回一个列表
res = re.split(':| ','a b:c d') # 按冒号或空格切分
print(res) # ['a', 'b', 'c', 'd']

res = re.split('[:, ]','a b:c d') # 按冒号或空格切分
print(res) # ['a', 'b', 'c', 'd']

res = re.split('[:, ]','a b:c d',2) # 切2次
print(res) # ['a', 'b', 'c d']
  • 贪婪匹配和非贪婪匹配
# 贪婪匹配:在满足匹配时,匹配尽可能长的字符串,默认情况下,采用贪婪匹配
# 非贪婪匹配:在满足匹配时,匹配尽可能短的字符串,使用 ? 来表示非贪婪匹配

res = re.match('ab*','abbbc')
print(res.group()) # abbb

res = re.match('ab*?','abbbc')
print(res.group()) # a

res = re.findall('<.*>','<h1>hello</h1>')
print(res) # ['<h1>hello</h1>']

res = re.findall('<.*?>','<h1>hello</h1>')
print(res) # ['<h1>', '</h1>']

# 线程的使用

# 线程的基础用法

使用 threading 模块里的 Thread 类创建出实例对象,然后通过 start() 方法产生一个新的线程

  • 程序启动默认会有一个主线程
  • 通过 start() 方法来启动子线程
  • 线程之间执行是无序的,它是由cpu调用来决定的
  • 通过 name 属性设置线程名称
  • 通过 is_alive 获取线程运行状态
import time
import threading

start = time.time()

def dance(name):
    print('%s 跳舞' % name)
    time.sleep(1)

if __name__ == '__main__':
    for i in range(5):
        # 创建线程,arcgs是元组,元组中只有一个元素,需要在元素后加逗号
        t = threading.Thread(target=dance, args=('t-%s' % i,))
	
		# 设置线程名字
		t.name = '111'
		
		# 获取线程名字
		print(t.name)
		
		# 获取线程运行状态
		print(t1.is_alive())
		print(t2.is_alive())
		
        # 启动线程
        t.start()

end = time.time()
print(end - start)

注意

threading.Thread(target=dance('t-%s' % i)) 如果这样 dance 加了括号,就是调用了这个函数,而这个函数没有return,就是返回none,target=none

  • 守护线程 daemon:主线程结束,子线程也结束,不会等待子线程执行完成
  • 阻塞线程 join:主线程等待子线程执行完再执行
import time
from threading import Thread

def dance(name):
    print('%s 跳舞前\n' % name)
    time.sleep(5)
    print('%s 跳舞后\n' % name)

def sing(name):
    print('%s 唱歌前\n' % name)
    time.sleep(5)
    print('%s 唱歌后\n' % name)

if __name__ == '__main__':
    t1 = Thread(target=dance,args=('张三',))
    t2 = Thread(target=sing,args=('李四',))

    # 设置守护线程
    t1.daemon = True
    t2.daemon = True

    t1.start()
    t2.start()

    print('主线程结束')
	
	# 输出
	# 张三 跳舞前
	# 李四 唱歌前
	# 主线程结束
	
    # 阻塞线程,主线程等待t1、t2执行完再执行
    t1.join()
    t2.join()
	
	print('主线程结束')
	# 张三 跳舞前
	# 李四 唱歌前
	# 李四 唱歌后
	# 张三 跳舞后
	# 主线程结束
  • 线程执行代码封装,默认按顺序执行
import time
from threading import Thread

# 定义一个新类集成Thread
class MyThread(Thread):
    def run(self):
        print("子线程开始\n")
        time.sleep(2)
        print("子线程结束\n")

if __name__ == "__main__":
    t = MyThread()
    t.start()

    print("主线程开始")
	
	# 输出
	# 子线程开始
	# 主线程开始
	# 子线程结束
  • run 与 start 方法的区别
start方法 # 是声明分到一个子线程的函数已经就绪,等待被CPU执行
run方法 # 是执行到这个子线程时,自动调用的方法
import threading
from threading import Thread

# 定义一个新类集成Thread
class MyThread(Thread):
    def run(self):
        print(f'当前线程名称:{threading.current_thread().name}')

if __name__ == "__main__":
    t = MyThread()
    t.start()

    t.run()
	
	# 输出
	# 当前线程名称:Thread-1
	# 当前线程名称:MainThread

# 局部变量和全局变量

  • 对于函数的局部变量,局部变量是每个线程独有的资源,不会产生冲突的,每个线程都会有局部变量的拷贝
import threading
from time import sleep

def thread_entry():
    var = 1
    for i in range(10):
	    # 获得当前线程对象,ident表示线程的id号
        print('th #{} :{}'.format(threading.current_thread().ident, var))  
        sleep(1)
        var += 1

if __name__ == '__main__':
    print('main thread start.')
    t1 = threading.Thread(target=thread_entry)
    t2 = threading.Thread(target=thread_entry)

    t1.start()
    t2.start()
    print('main thread end.')
  • 全局变量是共享的,是会产生冲突,要控制
from threading import Thread

a = 0
b = 1000000

def sum1():
    for i in range(b):
        global a
        print(a)
        a += 1
    print(f"第一次:{a}\n",end="")


def sum2():
    for i in range(b):
        global a
        print(a)
        a += 1
    print(f"第二次:{a}\n",end="")


if __name__ == "__main__":
    t1 = Thread(target=sum1)
    t2 = Thread(target=sum2)
    t1.start()
    t2.start()
	
	# 输出
	# 第一次:1987150
	# 第二次:2000000
  • 解决资源竞争的两个办法:
# 线程等待 join
# 互斥锁:对共享数据进程锁定,保证同一时刻只能有一个线程操作

# 互斥锁的创建流程
# 1、定义互斥锁
# 2、acquire 加锁
# 3、release 释放锁
import threading
from threading import Thread

a = 0
b = 1000000

# 创建全局互斥锁
lock = threading.Lock()

def sum1():
    lock.acquire()
    for i in range(b):
        global a
        a += 1
    print(f"第一次:{a}\n",end="")
    lock.release()

def sum2():
    lock.acquire()
    for i in range(b):
        global a
        a += 1
    print(f"第二次:{a}\n",end="")
    lock.release()

if __name__ == "__main__":
    t1 = Thread(target=sum1)
    t2 = Thread(target=sum2)
    t1.start()
    t2.start()	
	
	# 输出
	# 第一次:1000000
	# 第二次:2000000
  • 信号量是允许同一时间同时几个线程访问共享变量
import threading,time

# 锁 --- 信号量
sem = threading.BoundedSemaphore(3)

def foo(i):
    # 上锁
    sem.acquire()
    time.sleep(1)
    print(str(i)+'\n')
    # 解锁
    sem.release()

for i in range(10):
    t = threading.Thread(target=foo,args=(i,))
    t.start()
	
	# 输出
	# 2 1 0   4 5 3  8 7 6  9

# 生产者消费者

import threading,time
from random import randint
 
# 存放共享资源的 列表
commandList =[]
 
# 创建锁对象
cv = threading.Lock()
 
# 生产者线程
def thread_producer():
    global commandList
 
    cmdNo = 0
    while True:
 
        cmdNo += 1
 
        # 这里生产的资源,就先用一个字符串来表示
        resource = 'command_{cmdNo}'
 
        # 随机等待一段时间,表示 生产资源的时间,就是输入命令耗费的时间
        # 其中参数a是下限,参数b是上限,生成的随机数n:a<=n<=b
        time.sleep(randint(3,3))
 
        # 生产好了后,先申请锁
        cv.acquire()
 
        #申请锁成功后, 资源 存放入 commandList (共享对象)中
        commandList.append(resource)
 
        print('produce resource %s' % resource)
        # 释放锁
        cv.release()
 
 
 
# 消费者线程,
def thread_consumer ():
    global commandList
 
    while True:
        # 先申请锁
        cv.acquire()
 
        resource = None
        # 拿出 生产者线程 产生的一个资源,也就是一个命令
        if commandList:
            # 表示,已经被本消费者取出该资源了
            resource =  commandList.pop(0)
 
 
        # 取出一个共享资源后释放锁(生产者线程就可以对共享资源进行操作了)
        cv.release()
 
        if resource != None:
            # 随机等待一段时间,表示 消费资源的时间
            time.sleep(randint(1, 3))
            print('consume resource %s' % resource)
 
        # 注意上面的代码,当commandList里面没有 命令的时候
        #  就会不停的执行空循环,非常耗CPU资源
 
if __name__=='__main__':
    t1 = threading.Thread(target=thread_producer)
    t2 = threading.Thread(target=thread_consumer)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
  • 设置条件变量
import threading,time
from random import randint
 
commandList =[]
 
# 调用 Condition,返回一个条件对象, 该对象包含了一个锁对象
cv = threading.Condition()
 
 
# 消费者线程
def thread_consumer ():
    global  commandList
 
    while True:
        # 先申请锁,条件变量中包含了锁,可以调用acquire
        cv.acquire()
 
        # 如果命令表为空 调用条件变量wait方法 ,该调用会释放锁,并且阻塞在此处,
        # 直到生产者 生产出资源后,调用 该条件变量的notify , 唤醒 自己
        # 一旦被唤醒, 将重新获取锁(所以生产者线程此时不能对共享资源进行操作)
        while commandList == []:
            cv.wait()
 
        resource = None
        # 拿出 生产者线程 产生的一个资源
        if commandList:
            # 表示,已经被本消费者取出该资源了
            resource = commandList.pop(0)
 
        # 取出一个共享资源后释放锁(生产者线程就可以对共享资源进行操作了)
        cv.release()
 
        if resource != None:
            # 随机等待一段时间,表示 消费资源的时间
            time.sleep(randint(1, 3))
            print('consume resource %s' % resource)
 
 
# 生产者线程
def thread_producer():
    global  commandList
 
    cmdNo = 0
    while True:
        cmdNo += 1
 
        # 这里生产的资源,就先用一个字符串来表示
        resource = 'command_{cmdNo}'
 
        # 随机等待一段时间,表示生产资源的时间
        time.sleep(randint(3,3))
 
        # 通过条件变量 先申请锁
        cv.acquire()
 
        #申请锁成功后, 资源 存放入commandList 中
        commandList.append(resource)
 
        print('produce resource %s' % resource)
 
        # 随后调用notify,就像说 有任务啦,等任务的线程来处理吧。。
        # 该调用会唤醒一个 阻塞在该条件变量上等待的消费者线程
        cv.notify()
 
        # 当然也要释放一下condition里面的锁
        cv.release()
 
if __name__=='__main__':
    t1 = threading.Thread(target=thread_producer)
    t2 = threading.Thread(target=thread_consumer)
    t1.start()
    t2.start()
    t1.join()
    t2.join()

# 进程的使用

# 进程的基础用法

Process类的参数说明
# target 表示调用对象,即子进程执行的任务
# args 给target指定的函数传递的参数,元组的方式传递
# kwargs 表示调用字典对象
# name 子进程名称
# group 指定进程组

常用属性:
name  # 当前进程的别名,默认Process-N,N从1开始递增的整数
pid  # 进程号
ppid  # 父进程号

import os
os.getpid() # 查看当前进程
os.getppid() # 查看当前进程父进程

进程常用的方法
start() # 启动进程实例
is_alive() # 判断子进程是否还活着,存活返回True或False
join([timeout]) # 是否等待子进程执行结束(在当前位置阻塞主进程),主进程等待子进程多长时间timeout
terminate() # 不管任务是否完成,立即终止子进程

注意

进程间不同享全局变量,进程间相对独立

from multiprocessing import Process
import os, time

def proc1_entry(name):
    print('子进程 1: %s (%s)...' % (name, os.getpid()))
    time.sleep(2)

def proc2_entry(name):
    print('子进程 2: %s (%s)...' % (name, os.getpid()))
    time.sleep(2)

if __name__ == '__main__':
    # 主进程的父进程是PyCharm的ID
    print('主进程 %s...主进程的父进程 %s ' % (os.getpid(), os.getppid()))
    p1 = Process(target=proc1_entry, args=('p1',))
    p2 = Process(target=proc2_entry, args=('p2',))

    p1.start()
    p2.start()

    p1.join()
    p2.join()
    print('主进程结束')
	
	# 输出
	# 主进程 8064...主进程的父进程 3748 
	# 子进程 1: p1 (1848)...
	# 子进程 2: p2 (5480)...
	# 2秒后
	# 主进程结束
  • 进程间的通信,本质是共享的消息队列
# 支持队尾插入元素,队头删除元素

# 可以使用 multiprocessing 模块的 Queue 实现多进程之间的数据传递,Queue本身是一个消息列队程序
q.empty() # 是否为空
q.full() # 是否满了
q.put() # 放入数据
q.get() # 取出数据
q.qsize() # 查看消息数量
q.maxsize # 队列中存放数据的上限(整数)一旦达到上限,插入会导致阻塞,直到队列中的数据被消费掉
          # maxsize<=0队列大小没有限制
from multiprocessing import Queue

# 初始化一个Queue对象,最多可接收三条消息
q = Queue(3)
q.put("Message 1")
q.put("Message 2")
print(q.full()) # False
q.put("Message 3")
print(q.full()) # True

# 因为消息队列已满,下面的try会等待2秒后再抛出异常
try:
    # True是block值,消息队列没空间写入时,程序进入阻塞状态
    # 2是timeout值,等待超时时间,阻塞时间超过2秒后,抛出异常
    q.put("Message 4", True, 2) 
except:
    print("消息队列已满,现有消息数量:%s" %q.qsize())

# 会立刻抛出异常
try:
    q.put_nowait("Message 4")
except:
    print("消息队列已满,现有消息数量:%s" %q.qsize())
from multiprocessing import Queue, Process
import time

list1 = [1, 2, 3, 4, 5]

def write(q):
    for i in list1:
        print("正在写入:", i)
        q.put(i)
        time.sleep(2)

def read(q):
    while True:
        if not q.empty():
            value = q.get(True)
            print("正在读取:", value)
            time.sleep(1)
        else:
            break

if __name__ == '__main__':
    q = Queue(10) # 初始化一个Queue对象,最多可接收10条put消息
    p1 = Process(target=write, args=(q,)) # 写入进程
    p2 = Process(target=read, args=(q,)) # 读取进程

    p1.start()
    p1.join()
    p2.start()
	
	# 输出
	# 正在写入: 1
	# 正在写入: 2
	# 正在写入: 3
	# 正在写入: 4
	# 正在写入: 5
	# 正在读取: 1
	# 正在读取: 2
	# 正在读取: 3
	# 正在读取: 4
	# 正在读取: 5

# 进程池

# 主要方法
p.apply_async() # 非阻塞方式调用 func 并行执行
p.apply() # 阻塞方式调用 func 并行执行,即同步
p.close() # 关闭进程池,防止进一步操作(进程池不接收新的任务)
p.join() # 阻塞
p.enumerate() # 不管任务是否完成,立即终止
from multiprocessing import Pool
import time

def learn(n):
    print("learning %s" % n)
    time.sleep(2)
    return n**2

if __name__ == '__main__':
    p = Pool(2) # 创建进程池,进程池中最大有3个进程一起执行
    res_l = []
    for i in range(5):
        res = p.apply_async(learn, args=(i,)) # 异步提交任务
        res_l.append(res)
    p.close() # 关闭进程池,不再接收新的请求
    p.join() # 等待进程池中所有的子进程执行完毕,必须放在close后面
    for res in res_l:
        print(res.get()) # 从进程池中获取结果,必须放在join后面
	
	# 输出
	# learning 0
	# learning 1
	# learning 2
	# learning 3
	# learning 4
	# 0
	# 1
	# 4
	# 9
	# 16
  • 进程池通信
# 需要使用 multiprocessing.Manager() 中 Queue()
# Manager()模块 专门做数据共享

# multiprocessing 模块下的 Queue 为进程提供通信服务
# Manager 模块下的 Queue 为线程提供通信服务
from multiprocessing import Pool,Manager
import os

def rd(q):
    print(f'rd:{os.getpid()},father:{os.getppid()}')
    for i in range(q.qsize()):
        print(f'rd:{q.get()}')

def wr(q):
    print(f'wr:{os.getpid()},father:{os.getppid()}')
    for i in '123':
        print(f'wr:{i}')
        q.put(i)

if __name__ == '__main__':
    print(f'main:{os.getpid()},father:{os.getppid()}')
    # 实例化进程池队列对象
    q = Manager().Queue()
    # 实例化进程池对象
    p = Pool()
    p.apply_async(wr,args=(q,))
    p.apply_async(rd,args=(q,))
    p.close()
    p.join() # 阻塞主进程
    print(f'end {os.getpid()}')
	
	# 输出
	# main:6392,father:2520
	# wr:4348,father:6392
	# wr:1
	# wr:2
	# wr:3
	# rd:6364,father:6392
	# rd:1
	# rd:2
	# rd:3
	# end 6392

# 协程的使用

# 生成器 yield

  • 生成器函数可以在运行中暂停并返回一个中间结果,下一次调用时,可以从上一次暂停的位置开始执行
  • yield语句类似于return语句,但不同之处在于,它可以多次返回一个中间结果,并保留函数的状态
# 使用yield语句定义生成器函数,生成数字1、2、3和4
def my_generator():
    yield 1
    yield 2
    yield 3
    yield 4
	
# 通过调用next()函数来迭代生成器 
g = my_generator()
print(next(g)) # 输出 1
print(next(g)) # 输出 2
print(next(g)) # 输出 3
print(next(g)) # 输出 4

# 还可以使用for循环对生成器进行迭代
for i in my_generator():
    print(i)
import time

def fun1():
    a = 1
    print("将a赋值")
    yield a #中断函数,返回return a,等待写一个next才会执行
    b = 2
    print("将b赋值")
    yield b
    pass

def main():
    g1 = fun1()
    print(next(g1))
    time.sleep(2)
    print(next(g1))
    pass

if __name__=='__main__':
    main()

# 输出
# 将a赋值
# 1
# 将b赋值
# 2

# 协程的基本介绍

# 协程:单线程下的开发,又称微线程,可以在单个线程中实现多个并发任务
# 协程看着也是子程序,但执行过程中,在子程序内部可中断,转而执行别的子程序,适当的时候再返回来执行
# 注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断
# 应用场景:IO特别多,可以避免多线程的上下文切换开销,提高程序的效率
import time

def task1():
    while True:
        print("---任务1---")
        time.sleep(1)
        yield

def task2():
    while True:
        print("---任务2---")
        time.sleep(2)
        yield


def main():

    t1 = task1()
    t2 = task2()

    while True:
        next(t1)
        next(t2)
    pass

if __name__=='__main__':
    main()

# 输出
# ---任务1---
# ----任务2---
# ----任务1---
# ----任务2---
# ----任务1---
# ----任务2---
# ----任务1---
# ----任务2---
#无限下去。。。。

# greenlet 模块

greenlet 实现了轻量级的协程,通过调用 switch() 方法,可以从当前协程中切换到指定的协程上下文,从而实现协程之间的切换

from greenlet import greenlet

def main():
    def eat(name1,name2):
        print("%s eat 1" % name1)
        g2.switch("haha")
        print("%s eat 2" % name2)
        g2.switch()

    def play(name):
        print("%s play 1" % name)
        g1.switch()
        print("%s play 2" % name)


    g1 = greenlet(eat)
    g2 = greenlet(play)

    g1.switch("hehe","heihei")

    pass

if __name__=='__main__':
    main()

# 输出
# hehe eat 1
# haha play 1
# heihei eat 2
# haha play 2

# gevent 模块

# 可以自动切换协程
# gevent.spawn() 创建协程对象
# gevent.sleep(1) 用来模拟一个耗时操作,注意不是time模块中的sleep
# join()阻塞,直到这个协程执行完毕
import gevent

def eat(name):
    print("%s eat 1"%name)
    gevent.sleep(2) #模拟拥塞
    print("%s eat 2"%name)

def play(name):
    print("%s play 1"%name)
    gevent.sleep(1)
    print("%s eat 2"%name)

def main():
    g1 = gevent.spawn(eat,"小明") #创建协程对象
    g2 = gevent.spawn(play,"小红")
    g1.join()
    g2.join()
    print("主")

    pass

if __name__=='__main__':
    main()
	
# 输出
# 小明 eat 1
# 小红 play 1
# 小红 eat 2
# 小明 eat 2
# 主
  • joinall()方法的参数是一个协程对象列表,它会等待所有协程都执行完毕后再退出
import gevent

def task(name):
    for i in range(3):
        print(f'函数名是:{name},执行次数是:{i}')
        gevent.sleep(1)

if __name__ == '__main__':
    g1 = gevent.spawn(task, 'task1')
    g2 = gevent.spawn(task, 'task2')

    gevent.joinall([g1, g2])
	
# 输出
# 函数名是:task1,执行次数是:0
# 函数名是:task2,执行次数是:0
# 函数名是:task1,执行次数是:1
# 函数名是:task2,执行次数是:1
# 函数名是:task1,执行次数是:2
# 函数名是:task2,执行次数是:2

# 网络爬虫

# requests 模块

  • 基础请求
response.text # str类型,自动根据http头部对响应的编码作出推测,可以通过response.encodinpg设置
response.content # bytes类型,可以通过decode()解码
import requests

# 获取文字内容
url='https://www.baidu.com/'
response=requests.get(url)
response.encodinpg='utf-8' # 设置编码格式
print(response.status_code) # 状态码 200
print(response.text) # 网页源码
print(response.content.decode('utf-8')) # 网页源码

# 读取内容输入到文本
with open('baidu.txt','w', encoding='utf-8') as f:
    f.write(response.content.decode('utf-8'))

# 获取图片内容
url='http://www.1ge0.com/imgs/logo.jpg'
response=requests.get(url)

with open('baidu.txt','wb') as f:
    f.write(response.content) # 图片
  • 带上 user-agent 发送请求
url='http://www.baidu.com'

# 第一种直接定义请求头
headers={
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}

# 第二种定义多个请求头
UAlist=[
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
]
ua = random.choice(UAlist)
headers={
    'User-Agent':ua
}

# 第三种方法,使用fake_useragent模块
from fake_useragent import UserAgent
ua = UserAgent().random
headers={
    'User-Agent':ua
}


# 带上请求头
response=requests.get(url,headers=headers)
print(response.request.headers) # 打印请求头
print(response.content.decode()) # 打印响应体
  • url 传参
from urllib.parse import quote,unquote

print(quote('你好')) # 明文转码
print(unquote('%E4%BD%A0%E5%A5%BD')) # 解码


url='http://www.baidu.com/s?'

ua = UserAgent().random
headers={
    'User-Agent':ua
}
# 请求参数
kw = {'wd': 'python'}

# 带上请求头
response=requests.get(url,headers=headers,params=kw)
print(response.content.decode())
  • cookie 的使用
url='http://www.baidu.com'

ua = UserAgent().random
headers={
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
    'Cookie': 'BAIDUID=4253081149544622520:FG=1'
}

# 带上请求头
response=requests.get(url,headers=headers)
print(response.content.decode())
  • post 请求
url='http://www.baidu.com'

ua = UserAgent().random
headers={
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
}

post_data={
    'wd':'python'
}

# post 请求
response=requests.get(url,headers=headers,data=post_data)
print(response.content.decode())
  • session 的使用
requests模块中的Session类能够自动处理发送请求获取响应过程中产生的cookie,进而达到状态保持的目的。
url='http://www.baidu.com'

session=requests.session() #创建一个session对象
session.post(url,data={'kw':'python'}) #发送一个post请求
session.get(url) #发送一个get请求