Python基础2

124 阅读58分钟

个人学习笔记,并且主要针对机器学习深度学习,可能会有增删改,不涉及其他python方向,仅供参考。 [官方文档最靠谱](3.12.0 Documentation (python.org))

第七章 类——面向对象的编程

引子

Why:面向对象更符合人类对客观世界的抽象和理解

  • 一切皆对象
    一只小狗,一把椅子,一张信用卡,一条巧克力。。。

  • 一切对象,都有自己内在的属性
    狗狗的品种、椅子的质地、信用卡的额度、巧克力的口味。。。

  • 一切行为,皆是对象的行为
    狗狗蹲下、椅子移动位置、刷信用卡、巧克力融化了。。。

How:类是对象的载体

不同年龄、肤色、品质的猫,每一只都是一个对象

他们有一个共同的特征:都是猫

我们可以把一类对象的公共特征抽象出来,创建通用的类

# 创建类
class Cat():
    """模拟猫"""
    
    def __init__(self, name):
        """初始化属性"""
        self.name = name
    
    def jump(self):
        """模拟猫跳跃"""
        print(self.name + " is jumping") 
# 用类创建实例
my_cat = Cat("Loser")
your_cat = Cat("Lucky")
# 调用属性
print(my_cat.name)
print(your_cat.name)
Loser
Lucky
# 调用方法
my_cat.jump()
your_cat.jump()
Loser is jumping
Lucky is jumping

7.1 类的定义

三要素:类名、属性、方法

7.1.1 类的命名

  • 要有实际意义

  • 驼峰命名法——组成的单词首字母大写
    Dog、 CreditCard、 ElectricCar

# class 类名:
"""类前空两行"""


class Car():
    """对该类的简单介绍"""
    pass

"""类后空两行"""

7.1.2 类的属性

# def __init__(self,要传递的参数)  初始化类的属性
class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0        

7.1.3 类的方法

# 相对于类内部定义的函数
class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0  
        
    def get_main_information(self):        # self不能省
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}".format(self.brand, self.model, self.year))
    
    def get_mileage(self):
        """获取总里程"""
        return "行车总里程:{}公里".format(self.mileage)

7.2 创建实例

7.2.1 实例的创建

将实例赋值给对象,实例化过程中,传入相应的参数
v = 类名(必要的初始化参数)

my_new_car = Car("Audi", "A6", 2018)

7.2.2 访问属性

实例名.属性名

print(my_new_car.brand)
print(my_new_car.model)
print(my_new_car.year)
Audi
A6
2018

7.2.3 调用方法

class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0  
        
    def get_main_information(self):        # self不能省
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}".format(self.brand, self.model, self.year))
    
    def get_mileage(self):
        """获取总里程数"""
        return "行车总里程:{}公里".format(self.mileage)

实例名.方法名(必要的参数)

my_new_car = Car("Audi", "A6", 2018)
my_new_car.get_main_information()
品牌:Audi   型号:A6   出厂年份:2018
mileage = my_new_car.get_mileage()
print(mileage)
行车总里程:0公里

7.2.4 修改属性

1、直接修改

my_old_car = Car("BYD", "宋", 2016)

先访问,后修改

print(my_old_car.mileage)
my_old_car.mileage = 12000
print(my_old_car.mileage)
0
12000
print(my_old_car.get_mileage())
行车总里程:12000公里

2、通过方法修改属性

class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0  
        
    def get_main_information(self):        # self不能省
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}".format(self.brand, self.model, self.year))
    
    def get_mileage(self):
        """获取总里程数"""
        return "行车总里程:{}公里".format(self.mileage)
    
    def set_mileage(self, distance):
        """设置总里程数"""
        self.mileage = distance
my_old_car = Car("BYD", "宋", 2016)
print(my_old_car.get_mileage())
my_old_car.set_mileage(8000)
print(my_old_car.get_mileage())
行车总里程:0公里
行车总里程:8000公里

3、继续拓展

  • 禁止设置负里程
class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0  
        
    def get_main_information(self):        # self不能省
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}".format(self.brand, self.model, self.year))
    
    def get_mileage(self):
        """获取总里程数"""
        print("行车总里程:{}公里".format(self.mileage)) 
    
    def set_mileage(self, distance):
        """设置总里程数"""
        if distance >= 0:
            self.mileage = distance
        else:
            print("里程数不能为负!")
    
    def increment_mileage(self, distance):
        """总里程数累计"""
        if distance >= 0:
            self.mileage += distance
        else:
            print("新增里程数不能为负!") 
            
my_old_car = Car("BYD", "宋", 2016)
my_old_car.get_mileage()
my_old_car.set_mileage(-8000)
my_old_car.get_mileage()
行车总里程:0公里
里程数不能为负!
行车总里程:0公里
  • 将每次的里程数累加
my_old_car.get_mileage()
my_old_car.set_mileage(8000)
my_old_car.get_mileage()
my_old_car.increment_mileage(500)
my_old_car.get_mileage()
行车总里程:0公里
行车总里程:8000公里
行车总里程:8500公里

小结

my_new_car = Car("Audi", "A6", 2018)
my_cars = [my_new_car, my_old_car]
  • 包含的信息量可以是极大的,可以创建无穷多的实例
  • 高度的拟人(物)化,符合人类对客观世界的抽象和理解

7.3 类的继承

引子

看一下人在生物界的分支链

生物——动物界——脊索动物门——哺乳动物纲——灵长目——人科——人属——智人种

公共特征逐渐增加的过程

【问题】

假设二元系统: 人属 = {A人种, B人种, C人种。。。。}
为每一个人种构造一个类

方案一: 各自独立,分别构造各自人种的类

方案二:
1、将各人种公共特征提取出来,建立人属的类;
2、各人种继承上一级(人属)的公共特征,然后添加自身特殊特征,构建各自人种的类。

通常,我们选择方案二,因为他避免了过多的重复劳动

所谓继承,就是低层抽象继承高层抽象的过程

7.3.1 简单的继承

父类

class Car():
    """模拟汽车"""
    
    def __init__(self, brand, model, year):
        """初始化汽车属性"""               # 相当于类内部的变量
        self.brand = brand                 # 汽车的品牌
        self.model = model                 # 汽车的型号
        self.year = year                   # 汽车出厂年份
        self.mileage = 0                   # 新车总里程初始化为0
        
        
    def get_main_information(self):        # self不能省
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}".format(self.brand, self.model, self.year))
    
    def get_mileage(self):
        """获取总里程数"""
        print("行车总里程:{}公里".format(self.mileage)) 
    
    def set_mileage(self, distance):
        """设置总里程数"""
        if distance >= 0:
            self.mileage = distance
        else:
            print("里程数不能为负!")
    
    def increment_mileage(self, distance):
        """总里程数累计"""
        if distance >= 0:
            self.mileage += distance
        else:
            print("新增里程数不能为负!")

子类

class 子类名(父类名):

  • 新建一个电动汽车的类
class ElectricCar(Car):
    """模拟电动汽车"""
    
    def __init__(self, brand, model, year):
        """初始化电动汽车属性"""
        super().__init__(brand, model, year)  # 声明继承父类的属性
  • 自动继承父类的所有方法
my_electric_car = ElectricCar("NextWeek", "FF91", 2046)
my_electric_car.get_main_information()
品牌:NextWeek   型号:FF91   出厂年份:2046

7.3.2 给子类添加属性和方法

class ElectricCar(Car):
    """模拟电动汽车"""
    
    def __init__(self, brand, model, year, bettery_size):
        """初始化电动汽车属性"""
        super().__init__(brand, model, year)    # 声明继承父类的属性
        self.bettery_size = bettery_size        # 电池容量
        self.electric_quantity = bettery_size   # 电池剩余电量
        self.electric2distance_ratio = 5        # 电量距离换算系数 5公里/kW.h
        self.remainder_range = self.electric_quantity*self.electric2distance_ratio # 剩余可行驶里程
    
    def get_electric_quantit(self):
        """查看当前电池电量"""
        print("当前电池剩余电量:{} kW.h".format(self.electric_quantity))
        
    def set_electric_quantity(self, electric_quantity):
        """设置电池剩余电量,重新计算电量可支撑行驶里程"""
        if electric_quantity >= 0 and electric_quantity <= self.bettery_size:
            self.electric_quantity = electric_quantity
            self.remainder_range = self.electric_quantity*self.electric2distance_ratio
        else:
            print("电量未设置在合理范围!")
    
    def get_remainder_range(self):
        """查看剩余可行驶里程"""
        print("当前电量还可以继续驾驶 {} 公里".format(self.remainder_range))              
my_electric_car = ElectricCar("NextWeek", "FF91", 2046, 70)
my_electric_car.get_electric_quantit()            # 获取当前电池电量
my_electric_car.get_remainder_range()             # 获取当前剩余可行驶里程
当前电池剩余电量:70 kW.h
当前电量还可以继续驾驶 350 公里
my_electric_car.set_electric_quantity(50)         # 重设电池电量
my_electric_car.get_electric_quantit()            # 获取当前电池电量
my_electric_car.get_remainder_range()             # 获取当前剩余可行驶里程
当前电池剩余电量:50 kW.h
当前电量还可以继续驾驶 250 公里

7.3.3 重写父类的方法——多态

class ElectricCar(Car):
    """模拟电动汽车"""
    
    def __init__(self, brand, model, year, bettery_size):
        """初始化电动汽车属性"""
        super().__init__(brand, model, year)    # 声明继承父类的属性
        self.bettery_size = bettery_size        # 电池容量
        self.electric_quantity = bettery_size   # 电池剩余电量
        self.electric2distance_ratio = 5        # 电量距离换算系数 5公里/kW.h
        self.remainder_range = self.electric_quantity*self.electric2distance_ratio # 剩余可行驶里程
    
    def get_main_information(self):        # 重写父类方法
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}   续航里程:{} 公里"
              .format(self.brand, self.model, self.year, self.bettery_size*self.electric2distance_ratio))
    
    def get_electric_quantit(self):
        """查看当前电池电量,重新计算电量可支撑行驶里程"""
        print("当前电池剩余电量:{} kW.h".format(self.electric_quantity))
        
    def set_electric_quantity(self, electric_quantity):
        """设置电池剩余电量"""
        if electric_quantity >= 0 and electric_quantity <= self.bettery_size:
            self.electric_quantity = electric_quantity
            self.remainder_range = self.electric_quantity*self.electric2distance_ratio
        else:
            print("电量未设置在合理范围!")
    
    def get_remainder_range(self):
        """查看剩余可行驶里程"""
        print("当前电量还可以继续驾驶 {} 公里".format(self.remainder_range))
my_electric_car = ElectricCar("NextWeek", "FF91", 2046, 70)
my_electric_car.get_main_information()
品牌:NextWeek   型号:FF91   出厂年份:2046   续航里程:350 公里

7.3.4 用在类中的实例

把电池抽象成一个对象
逻辑更加清晰

class Bettery():
    """模拟电动汽车的电池"""
    
    def __init__(self, bettery_size = 70):
        self.bettery_size = bettery_size        # 电池容量
        self.electric_quantity = bettery_size   # 电池剩余电量
        self.electric2distance_ratio = 5        # 电量距离换算系数 5公里/kW.h
        self.remainder_range = self.electric_quantity*self.electric2distance_ratio # 剩余可行驶里程

    def get_electric_quantit(self):
        """查看当前电池电量"""
        print("当前电池剩余电量:{} kW.h".format(self.electric_quantity))
        
    def set_electric_quantity(self, electric_quantity):
        """设置电池剩余电量,计重新算电量可支撑行驶里程"""
        if electric_quantity >= 0 and electric_quantity <= self.bettery_size:
            self.electric_quantity = electric_quantity
            self.remainder_range = self.electric_quantity*self.electric2distance_ratio
        else:
            print("电量未设置在合理范围!")
    
    def get_remainder_range(self):
        """查看剩余可行驶里程"""
        print("当前电量还可以继续驾驶 {} 公里".format(self.remainder_range))
class ElectricCar(Car):
    """模拟电动汽车"""
    
    def __init__(self, brand, model, year, bettery_size):
        """初始化电动汽车属性"""
        super().__init__(brand, model, year)    # 声明继承父类的属性
        self.bettery = Bettery(bettery_size)    # 电池
    
    def get_main_information(self):        # 重写父类方法
        """获取汽车主要信息"""
        print("品牌:{}   型号:{}   出厂年份:{}   续航里程:{} 公里"
              .format(self.brand, self.model, self.year, 
              self.bettery.bettery_size*self.bettery.electric2distance_ratio))
my_electric_car = ElectricCar("NextWeek", "FF91", 2046, 70)
my_electric_car.get_main_information()                  # 获取车辆主要信息
品牌:NextWeek   型号:FF91   出厂年份:2046   续航里程:350 公里
my_electric_car.bettery.get_electric_quantit()          # 获取当前电池电量
当前电池剩余电量:70 kW.h
my_electric_car.bettery.set_electric_quantity(50)       # 重设电池电量
my_electric_car.bettery.get_electric_quantit()          # 获取当前电池电量    
当前电池剩余电量:50 kW.h
my_electric_car.bettery.get_remainder_range()           # 获取当前剩余可行驶里程
当前电量还可以继续驾驶 250 公里

第八章 文件、异常和模块

实际应用中,我们绝大多数的数据都是通过文件的交互完成的

8.1 文件的读写

8.1.1 文件的打开

  • 文件的打开通用格式
with open("文件路径", "打开模式", encoding = "操作文件的字符编码") as f:
    "对文件进行相应的读写操作"
使用with 块的好处:执行完毕后,自动对文件进行close操作。

【例1】一个简单的文件读取

with open("E:\ipython\测试文件.txt", "r", encoding = "gbk") as f:     # 第一步:打开文件
    text = f.read()                                                   # 第二步:读取文件
    print(text)
我是一个测试文件

1、文件路径

  • 完整路径,如上例所示

  • 程序与文件在同一文件夹,可简化成文件名

with open("测试文件.txt", "r", encoding = "gbk") as f:     # 第一步:打开文件
    text = f.read()                                        # 第二步:读取文件
    print(text)
我是一个测试文件

2、打开模式

  • "r"  只读模式,如文件不存在,报错

  • "w" 覆盖写模式,如文件不存在,则创建;如文件存在,则完全覆盖原文件

  • "x" 创建写模式,如文件不存在,则创建;如文件存在,报错

  • "a"  追加写模式,如文件不存在,则创建;如文件存在,则在原文件后追加内容

  • "b" 二进制文件模式,不能单独使用,需要配合使用如"rb","wb","ab",该模式不需指定encoding

  • "t" 文本文件模式,默认值,需配合使用 如"rt","wt","at",一般省略,简写成如"r","w","a"

  • "+",与"r","w","x","a"配合使用,在原功能基础上,增加读写功能

  • 打开模式缺省,默认为只读模式

3、字符编码

  • 万国码 utf-8

包含全世界所有国家需要用到的字符

  • 中文编码 gbk

专门解决中文编码问题

  • windows系统下,如果缺省,则默认为gbk(所在区域的编码)

  • 为清楚起见,除了处理二进制文件,建议不要缺省encoding

8.1.2 文件的读取

1、读取整个内容——f.read()

with open("三国演义片头曲_utf.txt", "r", encoding="utf-8") as f:       # 第一步:打开文件
    text = f.read()                                                    # 第二步:读取文件
    print(text)
临江仙·滚滚长江东逝水
滚滚长江东逝水,浪花淘尽英雄。
是非成败转头空。
青山依旧在,几度夕阳红。
白发渔樵江渚上,惯看秋月春风。
一壶浊酒喜相逢。
古今多少事,都付笑谈中。

with open("三国演义片头曲_utf.txt", encoding="utf-8") as f:     # "r",可缺省,为清晰起见,最好写上
    text = f.read()                                                   
    print(text)
临江仙·滚滚长江东逝水
滚滚长江东逝水,浪花淘尽英雄。
是非成败转头空。
青山依旧在,几度夕阳红。
白发渔樵江渚上,惯看秋月春风。
一壶浊酒喜相逢。
古今多少事,都付笑谈中。

  • 解码模式不匹配
with open("三国演义片头曲_utf.txt", "r", encoding="gbk") as f:     
    text = f.read()                                                    
    print(text)
---------------------------------------------------------------------------

UnicodeDecodeError                        Traceback (most recent call last)

<ipython-input-6-8e9ea685585d> in <module>
      1 with open("三国演义片头曲_utf.txt", "r", encoding="gbk") as f:
----> 2     text = f.read()
      3     print(text)


UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 50: illegal multibyte sequence
with open("三国演义片头曲_utf.txt", "r") as f:     # encoding缺省,windows系统默认为"gbk"
    text = f.read()                                                    
    print(text)
---------------------------------------------------------------------------

UnicodeDecodeError                        Traceback (most recent call last)

<ipython-input-7-480622bc01aa> in <module>
      1 with open("三国演义片头曲_utf.txt", "r") as f:     # encoding缺省,windows系统默认为"gbk"
----> 2     text = f.read()
      3     print(text)


UnicodeDecodeError: 'gbk' codec can't decode byte 0x80 in position 50: illegal multibyte sequence

2、逐行进行读取——f.readline()

with open("三国演义片头曲_gbk.txt", "r", encoding="gbk") as f:     
    for i in range(3):
        text = f.readline()                                                   # 每次只读取一行
        print(text)
临江仙·滚滚长江东逝水

滚滚长江东逝水,浪花淘尽英雄。

是非成败转头空。

with open("三国演义片头曲_gbk.txt", "r", encoding="gbk") as f:     
    while True:
        text = f.readline()
        if not text:
            # print(text is "")
            break
        else:
            # print(text == "\n")
            print(text, end="")      # 保留原文的换行,使print()的换行不起作用
临江仙·滚滚长江东逝水
滚滚长江东逝水,浪花淘尽英雄。
是非成败转头空。

青山依旧在,几度夕阳红。
白发渔樵江渚上,惯看秋月春风。
一壶浊酒喜相逢。
古今多少事,都付笑谈中。

3、读入所有行,以每行为元素形成一个列表——f.readlines()

with open("三国演义片头曲_gbk.txt", "r", encoding="gbk") as f:
    text = f.readlines()              # 注意每行末尾有换行符
    print(text)               
['临江仙·滚滚长江东逝水\n', '滚滚长江东逝水,浪花淘尽英雄。\n', '是非成败转头空。\n', '\n', '青山依旧在,几度夕阳红。\n', '白发渔樵江渚上,惯看秋月春风。\n', '一壶浊酒喜相逢。\n', '古今多少事,都付笑谈中。\n']
with open("三国演义片头曲_gbk.txt", "r", encoding="gbk") as f:     
    for text in f.readlines():                                                   
        print(text)                # 不想换行则用print(text, end="")         
临江仙·滚滚长江东逝水

滚滚长江东逝水,浪花淘尽英雄。

是非成败转头空。



青山依旧在,几度夕阳红。

白发渔樵江渚上,惯看秋月春风。

一壶浊酒喜相逢。

古今多少事,都付笑谈中。

4、文本文件读取小结

文件比较大时,read()和readlines()占用内存过大,不建议使用

readline用起来又不太方便

with open("三国演义片头曲_gbk.txt", "r", encoding="gbk") as f:     
    for text in f:         # f本身就是一个可迭代对象,每次迭代读取一行内容 
        print(text)  
临江仙·滚滚长江东逝水

滚滚长江东逝水,浪花淘尽英雄。

是非成败转头空。



青山依旧在,几度夕阳红。

白发渔樵江渚上,惯看秋月春风。

一壶浊酒喜相逢。

古今多少事,都付笑谈中。

5、二进制文件

图片:二进制文件

with open("test.jpg", "rb") as f:     
        print(len(f.readlines()))
69

8.1.3 文件的写入

1、向文件写入一个字符串或字节流(二进制)——f.write()

with open("恋曲1980.txt", "w", encoding="utf-8") as f:                      
        f.write("你曾经对我说\n")        # 文件不存在则立刻创建一个
        f.write("你永远爱着我\n")        # 如需换行,末尾加换行符\n
        f.write("爱情这东西我明白\n")
        f.write("但永远是什么\n")
with open("恋曲1980.txt", "w", encoding="utf-8") as f:                      
        f.write("姑娘你别哭泣\n")        # 如果文件存在,新写入内容会覆盖掉原内容,一定要注意!!!
        f.write("我俩还在一起\n")        
        f.write("今天的欢乐\n")
        f.write("将是明天创痛的回忆\n")

2、追加模式——"a"

with open("恋曲1980.txt", "a", encoding="utf-8") as f:                      
        f.write("姑娘你别哭泣\n")        # 如果文件存在,新写入内容会覆盖掉原内容,一定要注意!!!
        f.write("我俩还在一起\n")        
        f.write("今天的欢乐\n")
        f.write("将是明天创痛的回忆\n")

3、将一个元素为字符串的列表整体写入文件——f.writelines()

ls = ["春天刮着风", "秋天下着雨", "春风秋雨多少海誓山盟随风远去"]
with open("恋曲1980.txt", "w", encoding="utf-8") as f:
    f.writelines(ls)
ls = ["春天刮着风\n", "秋天下着雨\n", "春风秋雨多少海誓山盟随风远去\n"]
with open("恋曲1980.txt", "w", encoding="utf-8") as f:
    f.writelines(ls)

8.1.4 既读又写

1、"r+"

  • 如果文件名不存在,则报错
  • 指针在开始
  • 要把指针移到末尾才能开始写,否则会覆盖前面内容
with open("浪淘沙_北戴河.txt", "r+", encoding="gbk") as f:
#     for line in f:
#         print(line)   # 全部读一遍后,指针到达结尾
    f.seek(0,2)         # 或者可以将指针移到末尾f.seek(偏移字节数,位置(0:开始;1:当前位置;2:结尾))
    text = ["萧瑟秋风今又是,\n", "换了人间。\n"]
    f.writelines(text)

2、"w+"

  • 若文件不存在,则创建
  • 若文件存在,会立刻清空原内容!!!
with open("浪淘沙_北戴河.txt", "w+", encoding="gbk") as f:
    pass
with open("浪淘沙_北戴河.txt", "w+", encoding="gbk") as f:
    text = ["萧瑟秋风今又是,\n", "换了人间。\n"]  # 清空原内容
    f.writelines(text)                             # 写入新内容,指针在最后
    f.seek(0,0)            # 指针移到开始
    print(f.read())        # 读取内容

3、"a+"

  • 若文件不存在,则创建
  • 指针在末尾,添加新内容,不会清空原内容
with open("浪淘沙_北戴河.txt", "a+", encoding="gbk") as f:
    f.seek(0,0)            # 指针移到开始
    print(f.read())        # 读取内容
大雨落幽燕,
白浪滔天。
秦皇岛外打鱼船。
一片汪洋都不见,
知向谁边?
往事越千年,
魏武挥鞭,
东临碣石有遗篇。
萧瑟秋风今又是,
换了人间。
with open("浪淘沙_北戴河.txt", "a+", encoding="gbk") as f:
    text = ["萧瑟秋风今又是,\n", "换了人间。\n"]  
    f.writelines(text)                             # 指针在最后,追加新内容, 
    f.seek(0,0)            # 指针移到开始
    print(f.read())        # 读取内容
大雨落幽燕,
白浪滔天。
秦皇岛外打鱼船。
一片汪洋都不见,
知向谁边?
往事越千年,
魏武挥鞭,
东临碣石有遗篇。
萧瑟秋风今又是,
换了人间。
萧瑟秋风今又是,
换了人间。

8.1.5 数据的存储与读取

通用的数据格式,可以在不同语言中加载和存储

本节简单了解两种数据存储结构csv和json

1、csv格式

由逗号将数据分开的字符序列,可以由excel打开

  • 读取
with open("成绩.csv", "r", encoding="gbk") as f:
    ls = [] 
    for line in f:                              # 逐行读取
        ls.append(line.strip("\n").split(","))  # 去掉每行的换行符,然后用“,”进行分割
for res in ls:
    print(res)
['编号', '数学成绩', '语文成绩']
['1', '100', '98']
['2', '96', '99']
['3', '97', '95']
  • 写入
ls = [['编号', '数学成绩', '语文成绩'], ['1', '100', '98'], ['2', '96', '99'], ['3', '97', '95']]
with open("score.csv", "w", encoding="gbk") as f:   # encoding="utf-8"中文出现乱码
    for row in ls:                                  # 逐行写入
        f.write(",".join(row)+"\n")                 # 用逗号组合成字符串形式,末尾加换行符

也可以借助csv模块完成上述操作

2、json格式

常被用来存储字典类型

  • 写入——dump()
import json

scores = {"Petter":{"math":96 , "physics": 98},
        "Paul":{"math":92 , "physics": 99},
        "Mary":{"math":98 , "physics": 97}}
with open("score.json", "w", encoding="utf-8") as f:             # 写入整个对象 
        # indent 表示字符串换行+缩进 ensure_ascii=False 显示中文
        json.dump(scores, f, indent=4, ensure_ascii=False)       
        
  • 读取——load()
with open("score.json", "r", encoding="utf-8") as f:                                         
        scores = json.load(f)           # 加载整个对象
        for k,v in scores.items():
            print(k,v)
Petter {'math': 96, 'physics': 98}
Paul {'math': 92, 'physics': 99}
Mary {'math': 98, 'physics': 97}

8.2 异常处理

8.2.1 常见异常的产生

1、除0运算——ZeroDivisionError

1/0
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-1-9e1622b385b6> in <module>
----> 1 1/0


ZeroDivisionError: division by zero

2、找不到可读文件——FileNotFoundError

with open("nobody.csv") as f:
    pass
---------------------------------------------------------------------------

FileNotFoundError                         Traceback (most recent call last)

<ipython-input-2-f2e8c7d0ac60> in <module>
----> 1 with open("nobody.csv") as f:
      2     pass


FileNotFoundError: [Errno 2] No such file or directory: 'nobody.csv'

3、值错误——ValueError

传入一个调用者不期望的值,即使这个值的类型是正确的

s = "1.3"
n = int(s)
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-8-69942d9db3c0> in <module>
      1 s = "1.3"
----> 2 n = int(s)


ValueError: invalid literal for int() with base 10: '1.3'

4、索引错误——IndexError

下标超出序列边界

ls = [1, 2, 3]
ls[5]
---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

<ipython-input-9-acf459124b52> in <module>
      1 ls = [1, 2, 3]
----> 2 ls[5]


IndexError: list index out of range

5、类型错误——TypeError

传入对象类型与要求不符

1 + "3"
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-10-ee505dc42f75> in <module>
----> 1 1 + "3"


TypeError: unsupported operand type(s) for +: 'int' and 'str'

6、其他常见的异常类型

NameError 使用一个未被定义的变量
KeyError 试图访问字典里不存在的键
。。。

print(a)
---------------------------------------------------------------------------

NameError                                 Traceback (most recent call last)

<ipython-input-11-bca0e2660b9f> in <module>
----> 1 print(a)


NameError: name 'a' is not defined
d = {}
d["1"]
---------------------------------------------------------------------------

KeyError                                  Traceback (most recent call last)

<ipython-input-12-e629d551aca0> in <module>
      1 d = {}
----> 2 d["1"]


KeyError: '1'

当异常发生的时候,如果不预先设定处理方法,程序就会中断

8.2.2 异常的处理

提高程序的稳定性和可靠性

1、try_except

  • 如果try内代码块顺利执行,except不被触发

  • 如果try内代码块发生错误,触发except,执行except内代码块

  • 单分支

x = 10
y = 0
try:
    z = x/y
except ZeroDivisionError:                  # 一般来说会预判到出现什么错误
    # z = x/(y+1e-7)
    # print(z)
    print("0不可以被除!")    
0不可以被除!
x = 10
y = 0
try:
    z = x/y
except NameError:                  # 一般来说会预判到出现什么错误
    # z = x/(y+1e-7)
    # print(z)
    print("0不可以被除!")   
---------------------------------------------------------------------------

ZeroDivisionError                         Traceback (most recent call last)

<ipython-input-14-aea58863ad72> in <module>
      2 y = 0
      3 try:
----> 4     z = x/y
      5 except NameError:                  # 一般来说会预判到出现什么错误
      6     # z = x/(y+1e-7)


ZeroDivisionError: division by zero
  • 多分支
ls = []
d = {"name": "大杰仔"}
try:
    y = m
    # ls[3]
    # d["age"]
except NameError:
    print("变量名不存在")
except IndexError:
    print("索引超出界限")
except KeyError:
    print("键不存在")
变量名不存在
  • 万能异常 Exception (所有错误的老祖宗)
ls = []
d = {"name": "大杰仔"}
try:
    # y = m
    ls[3]
    # d["age"]
except Exception:
    print("出错啦")
出错啦
  • 捕获异常的值 as
ls = []
d = {"name": "大杰仔"}
# y = x
try:
    y = m
    # ls[3]
    # d["age"]
except Exception as e:    # 虽不能获得错误具体类型,但可以获得错误的值
    print(e)
name 'm' is not defined

2、try_except_else

  • 如果try 模块执行,则else模块也执行

可以将else 看做try成功的额外奖赏

try:
    with open("浪淘沙_北戴河.txt") as f:
        text = f.read()
except FileNotFoundError:
    print("找不到该文件,ta是不是用了美颜?")
else:
    for s in ["\n", ",", "。", "?"]:         # 去掉换行符和标点符号
        text = text.replace(s, "")
    print("毛主席的名作《浪淘沙_北戴河》共由{}个字组成。".format(len(text)))
毛主席的名作《浪淘沙_北戴河》共由65个字组成。

3、try_except_finally

  • 不论try模块是否执行,finally最后都执行
ls = []
d = {"name": "大杰仔"}
# y = x
try:
    y = m
    # ls[3]
    # d["age"]
except Exception as e:    # 虽不能获得错误具体类型,但可以获得错误的值
    print(e)
finally:
    print("不论触不触发异常,都将执行")
name 'm' is not defined
不论触不触发异常,都将执行

8.3 模块简介

已经被封装好

无需自己再“造轮子”

声明导入后,拿来即用

8.3.1 广义模块分类

1、Python 内置

 时间库time\  随机库random\  容器数据类型collection\  迭代器函数itertools

2、第三方库

 数据分析numpy、pandas\  数据可视化matplotlib\  机器学习scikit-learn\  深度学习Tensorflow

3、自定义文件

  • 单独py文件

  • 包——多个py文件

# 文件夹内多个py文件,再加一个__init__.py文件(内容可为空)

8.3.2 模块的导入

1、导入整个模块——import 模块名

  • **调用方式:**模块名.函数名或类名
import time

start = time.time()      # 调用time模块中的time()
time.sleep(3)            # 调用time模块中的sleep()  休息3秒钟
end = time.time()       
print("程序运行用时:{:.2f}秒".format(end-start))
程序运行用时:3.00秒
import fun1

fun1.f1()
导入fun1成功

2、从模块中导入类或函数——from 模块 import 类名或函数名

  • **调用方式:**函数名或类名
from itertools import product   

ls = list(product("AB", "123"))
print(ls)
[('A', '1'), ('A', '2'), ('A', '3'), ('B', '1'), ('B', '2'), ('B', '3')]
from function.fun1 import f1         # 注意这种用法

f1()
导入fun1成功

一次导入多个

from function import fun1, fun2

fun1.f1()
fun2.f2()
导入fun1成功
导入fun2成功

3、导入模块中所有的类和函数——from 模块 import *

  • **调用方式:**函数名或类名
from random import * 

print(randint(1,100))       # 产生一个[1,100]之间的随机整数
print(random())             # 产生一个[0,1)之间的随机小数
36
0.6582485822110181

8.3.3 模块的查找路径

模块搜索查找顺序:

  • 1、内存中已经加载的模块
import fun1

fun1.f1()
导入fun1成功
# 删除硬盘上的fun1 文件
import fun1

fun1.f1()
导入fun1成功
# 修改硬盘上的fun1 文件
import fun1

fun1.f1()
# 居然没变,说明是优先从内存中读取的
导入fun1成功
  • 2、内置模块
# Python 启动时,解释器会默认加载一些 modules 存放在sys.modules中
# sys.modules 变量包含一个由当前载入(完整且成功导入)到解释器的模块组成的字典, 模块名作为键, 它们的位置作为值
import sys

print(len(sys.modules))
print("math" in sys.modules)
print("numpy" in sys.modules)
for k,v in list(sys.modules.items())[:20]:
    print(k, ":", v)
738
True
False
sys : <module 'sys' (built-in)>
builtins : <module 'builtins' (built-in)>
_frozen_importlib : <module 'importlib._bootstrap' (frozen)>
_imp : <module '_imp' (built-in)>
_thread : <module '_thread' (built-in)>
_warnings : <module '_warnings' (built-in)>
_weakref : <module '_weakref' (built-in)>
zipimport : <module 'zipimport' (built-in)>
_frozen_importlib_external : <module 'importlib._bootstrap_external' (frozen)>
_io : <module 'io' (built-in)>
marshal : <module 'marshal' (built-in)>
nt : <module 'nt' (built-in)>
winreg : <module 'winreg' (built-in)>
encodings : <module 'encodings' from 'C:\\Users\\ibm\\Anaconda3\\lib\\encodings\\__init__.py'>
codecs : <module 'codecs' from 'C:\\Users\\ibm\\Anaconda3\\lib\\codecs.py'>
_codecs : <module '_codecs' (built-in)>
encodings.aliases : <module 'encodings.aliases' from 'C:\\Users\\ibm\\Anaconda3\\lib\\encodings\\aliases.py'>
encodings.utf_8 : <module 'encodings.utf_8' from 'C:\\Users\\ibm\\Anaconda3\\lib\\encodings\\utf_8.py'>
_signal : <module '_signal' (built-in)>
__main__ : <module '__main__'>
  • 3、sys.path路径中包含的模块
import sys

sys.path
['E:\\ipython', 'C:\\Users\\ibm\\Anaconda3\\python37.zip', 'C:\\Users\\ibm\\Anaconda3\\DLLs', 'C:\\Users\\ibm\\Anaconda3\\lib', 'C:\\Users\\ibm\\Anaconda3', '', 'C:\\Users\\ibm\\AppData\\Roaming\\Python\\Python37\\site-packages', 'C:\\Users\\ibm\\Anaconda3\\lib\\site-packages', 'C:\\Users\\ibm\\Anaconda3\\lib\\site-packages\\win32', 'C:\\Users\\ibm\\Anaconda3\\lib\\site-packages\\win32\\lib', 'C:\\Users\\ibm\\Anaconda3\\lib\\site-packages\\Pythonwin', 'C:\\Users\\ibm\\Anaconda3\\lib\\site-packages\\IPython\\extensions', 'C:\\Users\\ibm\\.ipython']
  • sys.path的第一个路径是当前执行文件所在的文件夹
  • 若需将不在该文件夹内的模块导入,需要将模块的路径添加到sys.path
# import fun3
import sys

sys.path.append("C:\\Users\\ibm\\Desktop")    # 注意是双斜杠

import fun3

fun3.f3()
导入fun3成功

第九章 有益的探索

尝试着潜入水中,往冰山的深处扎一个小小的猛子

9.1 数据类型的底层实现

9.1.1 从奇怪的列表说起

1、错综复杂的复制

list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
  • 浅拷贝
# list_3 = list_1          # 错误!!!
list_2 = list_1.copy()     # 或者list_1[:] \ list(list_1) 均可实习浅拷贝
  • 对浅拷贝前后两列表分别进行操作
list_2[1].append(55)

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]
list_2:   [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]

2、列表的底层实现

引用数组的概念

列表内的元素可以分散的存储在内存中

列表存储的,实际上是这些元素的地址!!!——地址的存储在内存中是连续的

list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
list_2 = list(list_1)   # 浅拷贝   与list_1.copy()功能一样

(1)新增元素

list_1.append(100)
list_2.append("n")

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 100]
list_2:   [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 'n']

(2)修改元素

list_1[0] = 10
list_2[0] = 20

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [10, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 100]
list_2:   [20, [22, 33, 44], (5, 6, 7), {'name': 'Sarah'}, 'n']

(3)对列表型元素进行操作

list_1[1].remove(44)
list_2[1] += [55, 66]

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 100]
list_2:   [20, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 'n']

(4)对元组型元素进行操作

list_2[2] += (8,9)

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah'}, 100]
list_2:   [20, [22, 33, 55, 66], (5, 6, 7, 8, 9), {'name': 'Sarah'}, 'n']

元组是不可变的!!!

(5)对字典型元素进行操作

list_1[-2]["age"] = 18

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [10, [22, 33, 55, 66], (5, 6, 7), {'name': 'Sarah', 'age': 18}, 100]
list_2:   [20, [22, 33, 55, 66], (5, 6, 7, 8, 9), {'name': 'Sarah', 'age': 18}, 'n']

3、引入深拷贝

浅拷贝之后

  • 针对不可变元素(数字、字符串、元组)的操作,都各自生效了

  • 针对不可变元素(列表、集合)的操作,发生了一些混淆

引入深拷贝

  • 深拷贝将所有层级的相关元素全部复制,完全分开,泾渭分明,避免了上述问题
import copy

list_1 = [1, [22, 33, 44], (5, 6, 7), {"name": "Sarah"}]
list_2 = copy.deepcopy(list_1)
list_1[-1]["age"] = 18
list_2[1].append(55)

print("list_1:  ", list_1)
print("list_2:  ", list_2)
list_1:   [1, [22, 33, 44], (5, 6, 7), {'name': 'Sarah', 'age': 18}]
list_2:   [1, [22, 33, 44, 55], (5, 6, 7), {'name': 'Sarah'}]

9.1.2 神秘的字典

1、快速的查找

import time

ls_1 = list(range(1000000))
ls_2 = list(range(500))+[-10]*500

start = time.time()
count = 0
for n in ls_2:
    if n in ls_1:
        count += 1
end = time.time()
print("查找{}个元素,在ls_1列表中的有{}个,共用时{}秒".format(len(ls_2), count,round((end-start),2)))
查找1000个元素,在ls_1列表中的有500个,共用时6.19秒
import time

d = {i:i for i in range(100000)}
ls_2 = list(range(500))+[-10]*500

start = time.time()
count = 0
for n in ls_2:
    try:
        d[n]
    except:
        pass
    else:
        count += 1
end = time.time()
print("查找{}个元素,在ls_1列表中的有{}个,共用时{}秒".format(len(ls_2), count,round(end-start)))
查找1000个元素,在ls_1列表中的有500个,共用时0秒

2、字典的底层实现

通过稀疏数组来实现值的存储与访问

字典的创建过程

  • 第一步:创建一个散列表(稀疏数组 N >> n)
d = {}
  • 第一步:通过hash()计算键的散列值
print(hash("python"))
print(hash(1024))
print(hash((1,2)))
-4771046564460599764
1024
3713081631934410656
d["age"] = 18    # 增加键值对的操作,首先会计算键的散列值hash("age")
print(hash("age")) 
  • 第二步:根据计算的散列值确定其在散列表中的位置

极个别时候,散列值会发生冲突,则内部有相应的解决冲突的办法

  • 第三步:在该位置上存入值
for i in range(2, 2):
    print(i)

键值对的访问过程

d["age"]
  • 第一步:计算要访问的键的散列值

  • 第二步:根据计算的散列值,通过一定的规则,确定其在散列表中的位置

  • 第三步:读取该位置上存储的值

     如果存在,则返回该值  
     如果不存在,则报错KeyError
    

3、小结

(1)字典数据类型,通过空间换时间,实现了快速的数据查找

  • 也就注定了字典的空间利用效率低下

(2)因为散列值对应位置的顺序与键在字典中显示的顺序可能不同,因此表现出来字典是无序的

  • 回顾一下 N >> n
    如果N = n,会产生很多位置冲突

  • 思考一下开头的小例子,为什么字典实现了比列表更快速的查找

9.1.3 紧凑的字符串

通过紧凑数组实现字符串的存储

  • 数据在内存中是连续存放的,效率更高,节省空间

  • 思考一下,同为序列类型,为什么列表采用引用数组,而字符串采用紧凑数组

    ** 因为字符串每个元素大小固定,就是一个字符,列表每个元素大小不固定

9.1.4 是否可变

1、不可变类型:数字、字符串、元组

在生命周期中保持内容不变

  • 换句话说,改变了就不是它自己了(id变了)

  • 不可变对象的 += 操作 实际上创建了一个新的对象

x = 1
y = "Python"

print("x id:", id(x))
print("y id:", id(y))
x id: 140718440616768
y id: 2040939892664
x += 2
y += "3.7"

print("x id:", id(x))
print("y id:", id(y))
x id: 140718440616832
y id: 2040992707056

元组并不是总是不可变的

t = (1,[2])
t[1].append(3)

print(t)
(1, [2, 3])

2、可变类型:列表、字典、集合

  • id 保持不变,但是里面的内容可以变

  • 可变对象的 += 操作 实际在原对象的基础上就地修改

ls = [1, 2, 3]
d = {"Name": "Sarah", "Age": 18}

print("ls id:", id(ls))
print("d id:", id(d))
ls id: 2040991750856
d id: 2040992761608
ls += [4, 5]
d_2 = {"Sex": "female"}
d.update(d_2)            # 把d_2 中的元素更新到d中

print("ls id:", id(ls))
print("d id:", id(d))
ls id: 2040991750856
d id: 2040992761608

9.1.5 列表操作的几个小例子

【例1】 删除列表内的特定元素

  • 方法1 存在运算删除法

缺点:每次存在运算,都要从头对列表进行遍历、查找、效率低

alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
s = "d"
while True:
    if s in alist:
        alist.remove(s)
    else:
        break
print(alist)
['2', '2', '4']
  • 方法2 一次性遍历元素执行删除
alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
for s in alist:
    if s == "d":
        alist.remove(s)      # remove(s) 删除列表中第一次出现的该元素
print(alist)
['2', '2', 'd', 'd', '4']

解决方法:使用负向索引

alist = ["d", "d", "d", "2", "2", "d" ,"d", "4"]
for i in range(-len(alist), 0):
    if alist[i] == "d":
        alist.remove(alist[i])      # remove(s) 删除列表中第一次出现的该元素
print(alist)
['2', '2', '4']

【例2】 多维列表的创建

ls = [[0]*10]*5  #将同一个地址复制了五份
ls
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
ls[0][0] = 1
ls
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

9.2 更加简洁的语法

9.2.1 解析语法

ls = [[0]*10 for i in range(5)]
ls
[[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]
ls[0][0] = 1
ls
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]

1、解析语法的基本结构——以列表解析为例(也称为列表推导)

[expression for value in iterable if conditihon]

  • 三要素:表达式、可迭代对象、if条件(可选)

执行过程

(1)从可迭代对象中拿出一个元素

(2)通过if条件(如果有的话),对元素进行筛选

 若通过筛选:则把元素传递给表达式  
   
 若未通过:  则进入(1)步骤,进入下一次迭代

(3)将传递给表达式的元素,代入表达式进行处理,产生一个结果

(4)将(3)步产生的结果作为列表的一个元素进行存储

(5)重复(1)~(4)步,直至迭代对象迭代结束,返回新创建的列表

# 等价于如下代码
result = []
for value in iterale:
    if condition:
        result.append(expression)

【例】求20以内奇数的平方

squares = []
for i in range(1,21):
    if i%2 == 1:
        squares.append(i**2)
print(squares)   
[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]
squares = [i**2 for i in range(1,21) if i%2 == 1]
print(squares) 
[1, 9, 25, 49, 81, 121, 169, 225, 289, 361]

支持多变量

x = [1, 2, 3]
y = [1, 2, 3]

results = [i*j for i,j in zip(x, y)]
results
[1, 4, 9]

支持循环嵌套

colors = ["black", "white"]
sizes = ["S", "M", "L"]
tshirts = ["{} {}".format(color, size) for color in colors for size in sizes]
tshirts
['black S', 'black M', 'black L', 'white S', 'white M', 'white L']

2、其他解析语法的例子

  • 解析语法构造字典(字典推导)
squares = {i: i**2 for i in range(10)}
for k, v in squares.items():
    print(k, ":  ", v)
0 :   0
1 :   1
2 :   4
3 :   9
4 :   16
5 :   25
6 :   36
7 :   49
8 :   64
9 :   81
  • 解析语法构造集合(集合推导)
squares = {i**2 for i in range(10)}
squares
{0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
  • 生成器表达式
squares = (i**2 for i in range(10))
squares
<generator object <genexpr> at 0x000001DB37A58390>
colors = ["black", "white"]
sizes = ["S", "M", "L"]
tshirts = ("{} {}".format(color, size) for color in colors for size in sizes)
for tshirt in tshirts:
    print(tshirt)
black S
black M
black L
white S
white M
white L

9.2.2 条件表达式

expr1 if condition else expr2

【例】将变量n的绝对值赋值给变量x

n = -10
if n >= 0:
    x = n
else:
    x = -n
x
10
n = -10
x = n if n>= 0 else -n
x
10

条件表达式和解析语法简单实用、运行速度相对更快一些,相信大家会慢慢的爱上它们

9.3 三大神器

9.3.1 生成器

ls = [i**2 for i in range(1, 1000001)]
for i in ls:
    pass

缺点:占用大量内存

生成器

(1)采用惰性计算的方式

(2)无需一次性存储海量数据

(3)一边执行一边计算,只计算每次需要的值

(4)实际上一直在执行next()操作,直到无值可取

1、生成器表达式

  • 海量数据,不需存储
squares = (i**2 for i in range(1000000))
for i in squares:
    pass
  • 求0~100的和

无需显示存储全部数据,节省内存

sum((i for i in range(101)))
5050

2、生成器函数——yield

  • 生产斐波那契数列

数列前两个元素为1,1 之后的元素为其前两个元素之和

def fib(max):
    ls = []
    n, a, b = 0, 1, 1
    while n < max:
        ls.append(a)
        a, b = b, a + b
        n = n + 1
    return ls


fib(10)
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

中间尝试

def fib(max):
    n, a, b = 0, 1, 1
    while n < max:
        print(a)
        a, b = b, a + b
        n = n + 1


fib(10)
1
1
2
3
5
8
13
21
34
55

构造生成器函数

在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行

def fib(max):
    n, a, b = 0, 1, 1
    while n < max:
        yield a
        a, b = b, a + b
        n = n + 1
        

fib(10)
<generator object fib at 0x000001BE11B19048>
for i in fib(10):
    print(i)
1
1
2
3
5
8
13
21
34
55

9.3.2 迭代器

1、可迭代对象

可直接作用于for循环的对象统称为可迭代对象:Iterable

(1)列表、元组、字符串、字典、集合、文件

可以使用isinstance()判断一个对象是否是Iterable对象

from collections import Iterable

isinstance([1, 2, 3], Iterable)
True
isinstance({"name": "Sarah"}, Iterable)
True
isinstance('Python', Iterable)
True

(2)生成器

squares = (i**2 for i in range(5))
isinstance(squares, Iterable)
True

生成器不但可以用于for循环,还可以被next()函数调用

print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
0
1
4
9
16

直到没有数据可取,抛出StopIteration

print(next(squares))
---------------------------------------------------------------------------

StopIteration                             Traceback (most recent call last)

<ipython-input-66-f5163ac9e49b> in <module>
----> 1 print(next(squares))


StopIteration: 

可以被next()函数调用并不断返回下一个值,直至没有数据可取的对象称为迭代器:Iterator

2、迭代器

可以使用isinstance()判断一个对象是否是Iterator对象

(1) 生成器都是迭代器

from collections import Iterator

squares = (i**2 for i in range(5))
isinstance(squares, Iterator)
True

(2) 列表、元组、字符串、字典、集合不是迭代器

isinstance([1, 2, 3], Iterator)
False

可以通过iter(Iterable)创建迭代器

isinstance(iter([1, 2, 3]), Iterator)
True

for item in Iterable 等价于:

先通过iter()函数获取可迭代对象Iterable的迭代器

然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item

当遇到StopIteration的异常后循环结束

(3)zip enumerate 等itertools里的函数是迭代器

x = [1, 2]
y = ["a", "b"]
zip(x, y)
<zip at 0x1be11b13c48>
for i in zip(x, y):
    print(i)
    
isinstance(zip(x, y), Iterator)
(1, 'a')
(2, 'b')





True
numbers = [1, 2, 3, 4, 5]
enumerate(numbers)
<enumerate at 0x1be11b39990>
for i in enumerate(numbers):
    print(i)
    
isinstance(enumerate(numbers), Iterator)
(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, 5)





True

(4) 文件是迭代器

with open("测试文件.txt", "r", encoding = "utf-8") as f:
    print(isinstance(f, Iterator))
True

(5)迭代器是可耗尽的

squares = (i**2 for i in range(5))
for square in squares:
    print(square)
0
1
4
9
16
for square in squares:
    print(square)

(6)range()不是迭代器

numbers = range(10)
isinstance(numbers, Iterator)
False
print(len(numbers))   # 有长度
print(numbers[0])     # 可索引
print(9 in numbers)   # 可存在计算
next(numbers)         # 不可被next()调用
10
0
True



---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-76-7c59bf859258> in <module>
      2 print(numbers[0])     # 可索引
      3 print(9 in numbers)   # 可存在计算
----> 4 next(numbers)         # 不可被next()调用


TypeError: 'range' object is not an iterator
for number in numbers:
    print(number)
0
1
2
3
4
5
6
7
8
9

不会被耗尽

for number in numbers:
    print(number)
0
1
2
3
4
5
6
7
8
9

可以称range()为懒序列

它是一种序列
  
但并不包含任何内存中的内容
  
而是通过计算来回答问题

9.3.3 装饰器

1、需求的提出

(1)需要对已开发上线的程序添加某些功能

(2)不能对程序中函数的源代码进行修改

(3)不能改变程序中函数的调用方式

比如说,要统计每个函数的运行时间

def f1():
    pass


def f2():
    pass


def f3():
    pass

f1()
f2()
f3()

没问题,我们有装饰器!!!

2、函数对象

函数是Python中的第一类对象

(1)可以把函数赋值给变量

(2)对该变量进行调用,可实现原函数的功能

def square(x):
    return x**2

print(type(square))      # square 是function类的一个实例
<class 'function'>
pow_2 = square          # 可以理解成给这个函数起了个别名pow_2
print(pow_2(5))
print(square(5))
25
25

可以将函数作为参数进行传递

3、高阶函数

(1)接收函数作为参数

(2)或者返回一个函数

满足上述条件之一的函数称之为高阶函数

def square(x):
    return x**2


def pow_2(fun):
    return fun


f = pow_2(square)
f(8)
64
print(f == square)
True

4、 嵌套函数

在函数内部定义一个函数

def outer():
    print("outer is running")
    
    def inner():
        print("inner is running")
        
    inner()


outer()
outer is running
inner is running

5、闭包

def outer():
    x = 1
    z = 10
    
    def inner():
        y = x+100
        return y, z
        
    return inner


f = outer()                # 实际上f包含了inner函数本身+outer函数的环境
print(f)
<function outer.<locals>.inner at 0x000001BE11B1D730>
print(f.__closure__)         # __closure__属性中包含了来自外部函数的信息
for i in f.__closure__:
    print(i.cell_contents)
(<cell at 0x000001BE0FDE06D8: int object at 0x00007FF910D59340>, <cell at 0x000001BE0FDE0A98: int object at 0x00007FF910D59460>)
1
10
res = f()
print(res)
(101, 10)

闭包:延伸了作用域的函数

如果一个函数定义在另一个函数的作用域内,并且引用了外层函数的变量,则该函数称为闭包

闭包是由函数及其相关的引用环境组合而成的实体(即:闭包=函数+引用环境)

  • 一旦在内层函数重新定义了相同名字的变量,则变量成为局部变量
def outer():
    x = 1
    
    def inner():
        x = x+100
        return x
        
    return inner


f = outer()             
f()
---------------------------------------------------------------------------

UnboundLocalError                         Traceback (most recent call last)

<ipython-input-87-d2da1048af8b> in <module>
     10 
     11 f = outer()
---> 12 f()


<ipython-input-87-d2da1048af8b> in inner()
      3 
      4     def inner():
----> 5         x = x+100
      6         return x
      7 


UnboundLocalError: local variable 'x' referenced before assignment

nonlocal允许内嵌的函数来修改闭包变量

def outer():
    x = 1
    
    def inner():
        nonlocal x
        x = x+100
        return x  
    return inner


f = outer()             
f()
1





101

6、一个简单的装饰器

嵌套函数实现

import time

def timer(func):
    
    def inner():
        print("inner run")
        start = time.time()
        func()
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
    
    return inner


def f1():
    print("f1 run")
    time.sleep(1)



f1 = timer(f1)             # 包含inner()和timer的环境,如传递过来的参数func
f1()
inner run
f1 run
f1 函数运行用时1.00

语法糖

import time

def timer(func):
    
    def inner():
        print("inner run")
        start = time.time()
        func()
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
    
    return inner

@timer                      # 相当于实现了f1 = timer(f1)
def f1():
    print("f1 run")
    time.sleep(1)
    
    
f1()
inner run
f1 run
f1 函数运行用时1.00

7、装饰有参函数

import time


def timer(func):
    
    def inner(*args, **kwargs):
        print("inner run")
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
    
    return inner


@timer                # 相当于实现了f1 = timer(f1)
def f1(n):
    print("f1 run")
    time.sleep(n)

    
f1(2)
inner run
f1 run
f1 函数运行用时2.00

被装饰函数有返回值的情况

import time


def timer(func):
    
    def inner(*args, **kwargs):
        print("inner run")
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
        return res
    
    return inner


@timer                   # 相当于实现了f1 = timer(f1)
def f1(n):
    print("f1 run")
    time.sleep(n)
    return "wake up"
    
res = f1(2)
print(res)
inner run
f1 run
f1 函数运行用时2.00秒
wake up

8、带参数的装饰器

装饰器本身要传递一些额外参数

  • 需求:有时需要统计绝对时间,有时需要统计绝对时间的2倍
def timer(method):
    
    def outer(func):
    
        def inner(*args, **kwargs):
            print("inner run")
            if method == "origin":
                print("origin_inner run")
                start = time.time()
                res = func(*args, **kwargs)
                end = time.time()
                print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
            elif method == "double":
                print("double_inner run")
                start = time.time()
                res = func(*args, **kwargs)
                end = time.time()
                print("{} 函数运行双倍用时{:.2f}秒".format(func.__name__, 2*(end-start)))
            return res
    
        return inner
    
    return outer


@timer(method="origin")  # 相当于timer = timer(method = "origin")   f1 = timer(f1)
def f1():
    print("f1 run")
    time.sleep(1)
    
    
@timer(method="double")
def f2():
    print("f2 run")
    time.sleep(1)


f1()
print()
f2()
inner run
origin_inner run
f1 run
f1 函数运行用时1.00秒

inner run
double_inner run
f2 run
f2 函数运行双倍用时2.00

理解闭包是关键!!!

9、何时执行装饰器

  • 一装饰就执行,不必等调用
func_names=[]
def find_function(func):
    print("run")
    func_names.append(func)
    return func


@find_function
def f1():
    print("f1 run")
    

@find_function
def f2():
    print("f2 run")
    

@find_function
def f3():
    print("f3 run")
    

run
run
run
for func in func_names:
    print(func.__name__)
    func()
    print()
f1
f1 run

f2
f2 run

f3
f3 run

10、回归本源

  • 原函数的属性被掩盖了
import time

def timer(func):
    def inner():
        print("inner run")
        start = time.time()
        func()
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
    
    return inner

@timer                # 相当于实现了f1 = timer(f1)
def f1():
    time.sleep(1)
    print("f1 run")

print(f1.__name__)    
inner
  • 回来
import time
from functools import wraps

def timer(func):
    @wraps(func)
    def inner():
        print("inner run")
        start = time.time()
        func()
        end = time.time()
        print("{} 函数运行用时{:.2f}秒".format(func.__name__, (end-start)))
    
    return inner

@timer                # 相当于实现了f1 = timer(f1)
def f1():
    time.sleep(1)
    print("f1 run")

print(f1.__name__) 
f1()
f1
inner run
f1 run
f1 函数运行用时1.00

第十章 Python标准库

Python自身提供了比较丰富的生态,拿来即用,可极大的提高开发效率

10.1 time库

Python处理时间的标准库

1、获取现在时间

(1)time.localtime() 本地时间

(2)time.gmtime() UTC世界统一时间

北京时间比时间统一时间UTC早8个小时

import time

t_local = time.localtime()
t_UTC = time.gmtime()
print("t_local", t_local)           # 本地时间
print("t_UTC", t_UTC)               # UTC统一时间
t_local time.struct_time(tm_year=2023, tm_mon=11, tm_mday=1, tm_hour=16, tm_min=49, tm_sec=7, tm_wday=2, tm_yday=305, tm_isdst=0)
t_UTC time.struct_time(tm_year=2023, tm_mon=11, tm_mday=1, tm_hour=8, tm_min=49, tm_sec=7, tm_wday=2, tm_yday=305, tm_isdst=0)
time.ctime()                      # 返回本地时间的字符串
'Thu Aug 29 16:44:52 2019'

2、时间戳与计时器

(1)time.time()   返回自纪元以来的秒数,记录sleep

(2)time.perf_counter()   随意选取一个时间点,记录现在时间到该时间点的间隔秒数,记录sleep

(3)time.process_time()   随意选取一个时间点,记录现在时间到该时间点的间隔秒数,不记录sleep

perf_counter()精度较time()更高一些

t_1_start = time.time()
t_2_start = time.perf_counter()
t_3_start = time.process_time()
print(t_1_start)
print(t_2_start)
print(t_3_start)

res = 0
for i in range(1000000):
    res += i
    
time.sleep(5)
t_1_end = time.time()
t_2_end = time.perf_counter()
t_3_end = time.process_time()

print("time方法:{:.3f}秒".format(t_1_end-t_1_start))
print("perf_counter方法:{:.3f}秒".format(t_2_end-t_2_start))
print("process_time方法:{:.3f}秒".format(t_3_end-t_3_start))
1567068710.7269545
6009.0814064
2.25
time方法:5.128秒
perf_counter方法:5.128秒
process_time方法:0.125

3、格式化

(1)time.strftime 自定义格式化输出

lctime = time.localtime()
time.strftime("%Y-%m-%d %A %H:%M:%S", lctime)
'2019-08-29 Thursday 16:54:35'

4、睡觉觉

(1)time.sleep()

10.2 random库

随机数在计算机应用中十分常见

Python通过random库提供各种伪随机数

基本可以用于除加密解密算法外的大多数工程应用

1、随机种子——seed(a=None)

(1)相同种子会产生相同的随机数

(2)如果不设置随机种子,以系统当前时间为默认值

from random import *

seed(10)
print(random())
seed(10)
print(random())
0.5714025946899135
0.5714025946899135
print(random())
0.20609823213950174

2、产生随机整数

(1)randint(a, b)——产生[a, b]之间的随机整数

numbers = [randint(1,10) for i in range(10)]
numbers
[3, 5, 6, 3, 8, 4, 8, 10, 7, 1]

(2)randrange(a)——产生[0, a)之间的随机整数

numbers = [randrange(10) for i in range(10)]
numbers
[6, 3, 0, 0, 7, 4, 9, 1, 8, 1]

(3)randrange(a, b, step)——产生[a, b)之间以setp为步长的随机整数

numbers = [randrange(0, 10, 3) for i in range(10)]
numbers
[9, 6, 6, 9, 3, 6, 6, 3, 9, 3]

3、产生随机浮点数

(1)random()——产生[0.0, 1.0)之间的随机浮点数

numbers = [random() for i in range(10)]
numbers
[0.9819392547566425,
 0.19092611184488173,
 0.3486810954900942,
 0.9704866291141572,
 0.4456072691491385,
 0.6807895695768549,
 0.14351321471670841,
 0.5218569500629634,
 0.8648825892767497,
 0.26702706855337954]

(2)uniform(a, b)——产生[a, b]之间的随机浮点数

numbers = [uniform(2.1, 3.5) for i in range(10)]
numbers
[2.523598043850906,
 3.0245903649048116,
 3.4202356766870463,
 2.344031169179946,
 2.3465252151503173,
 3.181989084829388,
 2.5592895031615703,
 2.413131937436849,
 2.8627907782614415,
 2.16114212173462]

4、序列用函数

(1)choice(seq)——从序列类型中随机返回一个元素

choice(['win', 'lose', 'draw'])
'draw'
choice("python")
'h'

(2)choices(seq,weights=None, k)——对序列类型进行k次重复采样,可设置权重

choices(['win', 'lose', 'draw'], k=5)
['draw', 'lose', 'draw', 'draw', 'draw']
choices(['win', 'lose', 'draw'], [4,4,2], k=10)
['lose', 'draw', 'lose', 'win', 'draw', 'lose', 'draw', 'win', 'win', 'lose']

(3)shuffle(seq)——将序列类型中元素随机排列,返回打乱后的序列

numbers = ["one", "two", "three", "four"]
shuffle(numbers)
numbers
['four', 'one', 'three', 'two']

(4)sample(pop, k)——从pop类型中随机选取k个元素,以列表类型返回

sample([10, 20, 30, 40, 50], k=3)
[20, 30, 10]

5、概率分布——以高斯分布为例

gauss(mean, std)——生产一个符合高斯分布的随机数

number = gauss(0, 1)
number
0.6331522345532208

多生成几个

import matplotlib.pyplot as plt

res = [gauss(0, 1) for i in range(100000)]

plt.hist(res, bins=1000)
plt.show()

在这里插入图片描述

【例1】用random库实现简单的微信红包分配

import random


def red_packet(total, num):
    for i in range(1, num):
        per = random.uniform(0.01, total/(num-i+1)*2)          # 保证每个人获得红包的期望是total/num
        # 均匀分布的期望是(a+b)/2   0.01~0   i=1: ( [total/(num-i+1)]*2 )/2 = total/num=total/5   
        # i=2 剩余total的期望变成  [total/4]*2/2 = total / 4
        total = total - per
        print("第{}位红包金额: {:.2f}元".format(i, per))
    else:
        print("第{}位红包金额: {:.2f}元".format(num, total))
            
            
red_packet(10, 5)
第1位红包金额: 1.85元
第2位红包金额: 3.90元
第3位红包金额: 0.41元
第4位红包金额: 3.30元
第5位红包金额: 0.54元
import random
import numpy as np


def red_packet(total, num):
    ls = []
    for i in range(1, num):
        per = round(random.uniform(0.01, total/(num-i+1)*2), 2)     # 保证每个人获得红包的期望是total/num
        ls.append(per)
        total = total - per
    else:
        ls.append(total)
        
    return ls
            
            
# 重复发十万次红包,统计每个位置的平均值(约等于期望)
res = []
for i in range(100000):
    ls = red_packet(10,5)
    res.append(ls)

res = np.array(res)
print(res[:10])
np.mean(res, axis=0)
[[1.71 1.57 0.36 1.25 5.11]
 [1.96 0.85 1.46 3.29 2.44]
 [3.34 0.27 1.9  0.64 3.85]
 [1.99 1.08 3.86 1.69 1.38]
 [1.56 1.47 0.66 4.09 2.22]
 [0.57 0.44 1.87 5.81 1.31]
 [0.47 1.41 3.97 1.28 2.87]
 [2.65 1.82 1.22 2.02 2.29]
 [3.16 1.2  0.3  3.66 1.68]
 [2.43 0.16 0.11 0.79 6.51]]





array([1.9991849, 2.0055725, 2.0018144, 2.0022472, 1.991181 ])

【例2】生产4位由数字和英文字母构成的验证码

import random
import string

print(string.digits)
print(string.ascii_letters)

s=string.digits + string.ascii_letters
v=random.sample(s,4) 
print(v)
print(''.join(v))
0123456789
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
['n', 'Q', '4', '7']
nQ47

10.3 collections库——容器数据类型

import collections

1、namedtuple——具名元组

  • 点的坐标,仅看数据,很难知道表达的是一个点的坐标
p = (1, 2)
  • 构建一个新的元组子类

    定义方法如下:typename 是元组名字,field_names 是域名

collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)
Point = collections.namedtuple("Point", ["x", "y"])
p = Point(1, y=2) #位置参数,关键词参数
p  
Point(x=1, y=2)
  • 可以调用属性
print(p.x)
print(p.y)
1
2
  • 有元组的性质
print(p[0])
print(p[1])
x, y = p
print(x)
print(y)
1
2
1
2
  • 确实是元组的子类
print(isinstance(p, tuple))
True

【例】模拟扑克牌

Card = collections.namedtuple("Card", ["rank", "suit"])
ranks = [str(n) for n in range(2, 11)] + list("JQKA")    
suits = "spades diamonds clubs hearts".split()
print("ranks", ranks)
print("suits", suits)
cards = [Card(rank, suit) for rank in ranks
                          for suit in suits]
cards
ranks ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']
suits ['spades', 'diamonds', 'clubs', 'hearts']





[Card(rank='2', suit='spades'), Card(rank='2', suit='diamonds'), Card(rank='2', suit='clubs'), Card(rank='2', suit='hearts'), Card(rank='3', suit='spades'), Card(rank='3', suit='diamonds'), Card(rank='3', suit='clubs'), Card(rank='3', suit='hearts'), Card(rank='4', suit='spades'), Card(rank='4', suit='diamonds'), Card(rank='4', suit='clubs'), Card(rank='4', suit='hearts'), Card(rank='5', suit='spades'), Card(rank='5', suit='diamonds'), Card(rank='5', suit='clubs'), Card(rank='5', suit='hearts'), Card(rank='6', suit='spades'), Card(rank='6', suit='diamonds'), Card(rank='6', suit='clubs'), Card(rank='6', suit='hearts'), Card(rank='7', suit='spades'), Card(rank='7', suit='diamonds'), Card(rank='7', suit='clubs'), Card(rank='7', suit='hearts'), Card(rank='8', suit='spades'), Card(rank='8', suit='diamonds'), Card(rank='8', suit='clubs'), Card(rank='8', suit='hearts'), Card(rank='9', suit='spades'), Card(rank='9', suit='diamonds'), Card(rank='9', suit='clubs'), Card(rank='9', suit='hearts'), Card(rank='10', suit='spades'), Card(rank='10', suit='diamonds'), Card(rank='10', suit='clubs'), Card(rank='10', suit='hearts'), Card(rank='J', suit='spades'), Card(rank='J', suit='diamonds'), Card(rank='J', suit='clubs'), Card(rank='J', suit='hearts'), Card(rank='Q', suit='spades'), Card(rank='Q', suit='diamonds'), Card(rank='Q', suit='clubs'), Card(rank='Q', suit='hearts'), Card(rank='K', suit='spades'), Card(rank='K', suit='diamonds'), Card(rank='K', suit='clubs'), Card(rank='K', suit='hearts'), Card(rank='A', suit='spades'), Card(rank='A', suit='diamonds'), Card(rank='A', suit='clubs'), Card(rank='A', suit='hearts')]
from random import *
# 洗牌
shuffle(cards) 
cards
[Card(rank='J', suit='hearts'), Card(rank='A', suit='hearts'), Card(rank='3', suit='hearts'), Card(rank='8', suit='hearts'), Card(rank='K', suit='hearts'), Card(rank='7', suit='spades'), Card(rank='5', suit='hearts'), Card(rank='A', suit='spades'), Card(rank='10', suit='spades'), Card(rank='J', suit='diamonds'), Card(rank='K', suit='clubs'), Card(rank='4', suit='spades'), Card(rank='2', suit='diamonds'), Card(rank='Q', suit='spades'), Card(rank='A', suit='clubs'), Card(rank='A', suit='diamonds'), Card(rank='6', suit='hearts'), Card(rank='7', suit='diamonds'), Card(rank='5', suit='diamonds'), Card(rank='10', suit='clubs'), Card(rank='8', suit='clubs'), Card(rank='9', suit='clubs'), Card(rank='6', suit='clubs'), Card(rank='6', suit='diamonds'), Card(rank='5', suit='clubs'), Card(rank='3', suit='diamonds'), Card(rank='4', suit='hearts'), Card(rank='3', suit='clubs'), Card(rank='7', suit='hearts'), Card(rank='2', suit='spades'), Card(rank='J', suit='clubs'), Card(rank='9', suit='spades'), Card(rank='J', suit='spades'), Card(rank='10', suit='hearts'), Card(rank='2', suit='clubs'), Card(rank='8', suit='diamonds'), Card(rank='6', suit='spades'), Card(rank='10', suit='diamonds'), Card(rank='9', suit='hearts'), Card(rank='3', suit='spades'), Card(rank='8', suit='spades'), Card(rank='Q', suit='clubs'), Card(rank='Q', suit='hearts'), Card(rank='5', suit='spades'), Card(rank='7', suit='clubs'), Card(rank='4', suit='clubs'), Card(rank='2', suit='hearts'), Card(rank='K', suit='diamonds'), Card(rank='K', suit='spades'), Card(rank='Q', suit='diamonds'), Card(rank='4', suit='diamonds'), Card(rank='9', suit='diamonds')]
# 随机抽一张牌
choice(cards)
Card(rank='4', suit='hearts')
# 随机抽多张牌
sample(cards, k=5)
[Card(rank='4', suit='hearts'), Card(rank='2', suit='clubs'), Card(rank='Q', suit='diamonds'), Card(rank='9', suit='spades'), Card(rank='10', suit='hearts')]

2、Counter——计数器工具

from collections import Counter
s = "牛奶奶找刘奶奶买牛奶"
colors = ['red', 'blue', 'red', 'green', 'blue', 'blue']
cnt_str = Counter(s)
cnt_color = Counter(colors)
print(cnt_str)
print(cnt_color)
Counter({'奶': 5, '牛': 2, '找': 1, '刘': 1, '买': 1})
Counter({'blue': 3, 'red': 2, 'green': 1})
  • 是字典的一个子类
print(isinstance(Counter(), dict))
True
  • 最常见的统计——most_commom(n)
    提供 n 个频率最高的元素和计数
cnt_color.most_common(2)
[('blue', 3), ('red', 2)]
  • 元素展开——elements()
list(cnt_str.elements())
['牛', '牛', '奶', '奶', '奶', '奶', '奶', '找', '刘', '买']
  • 其他一些加减操作
c = Counter(a=3, b=1)
d = Counter(a=1, b=2)
c+d
Counter({'a': 4, 'b': 3})

【例】从一副牌中抽取10张,大于10的比例有多少

cards = collections.Counter(tens=16, low_cards=36)
seen = sample(list(cards.elements()), k=10)
print(seen) 
['tens', 'low_cards', 'low_cards', 'low_cards', 'tens', 'tens', 'low_cards', 'low_cards', 'low_cards', 'low_cards']
seen.count('tens') / 10
0.3

3、deque——双向队列

列表访问数据非常快速

插入和删除操作非常慢——通过移动元素位置来实现

特别是 insert(0, v) 和 pop(0),在列表开始进行的插入和删除操作

双向队列可以方便的在队列两边高效、快速的增加和删除元素

from collections import deque

d = deque('cde') 
d
deque(['c', 'd', 'e'])
d.append("f")            # 右端增加
d.append("g")
d.appendleft("b")        # 左端增加
d.appendleft("a")
d
deque(['a', 'b', 'c', 'd', 'e', 'f', 'g'])
d.pop()           # 右端删除 
d.popleft()       # 左端删除
d
deque(['b', 'c', 'd', 'e', 'f'])

deque 其他用法可参考官方文档

10.4 itertools库——迭代器

1、排列组合迭代器

(1)product——笛卡尔积

import itertools

for i in itertools.product('ABC', '01'):
    print(i)
('A', '0')
('A', '1')
('B', '0')
('B', '1')
('C', '0')
('C', '1')
for i in itertools.product('ABC', repeat=3):
    print(i) 
('A', 'A', 'A')
('A', 'A', 'B')
('A', 'A', 'C')
('A', 'B', 'A')
('A', 'B', 'B')
('A', 'B', 'C')
('A', 'C', 'A')
('A', 'C', 'B')
('A', 'C', 'C')
('B', 'A', 'A')
('B', 'A', 'B')
('B', 'A', 'C')
('B', 'B', 'A')
('B', 'B', 'B')
('B', 'B', 'C')
('B', 'C', 'A')
('B', 'C', 'B')
('B', 'C', 'C')
('C', 'A', 'A')
('C', 'A', 'B')
('C', 'A', 'C')
('C', 'B', 'A')
('C', 'B', 'B')
('C', 'B', 'C')
('C', 'C', 'A')
('C', 'C', 'B')
('C', 'C', 'C')

(2) permutations——排列

for i in itertools.permutations('ABCD', 3):   # 3 是排列的长度
    print(i)
('A', 'B', 'C')
('A', 'B', 'D')
('A', 'C', 'B')
('A', 'C', 'D')
('A', 'D', 'B')
('A', 'D', 'C')
('B', 'A', 'C')
('B', 'A', 'D')
('B', 'C', 'A')
('B', 'C', 'D')
('B', 'D', 'A')
('B', 'D', 'C')
('C', 'A', 'B')
('C', 'A', 'D')
('C', 'B', 'A')
('C', 'B', 'D')
('C', 'D', 'A')
('C', 'D', 'B')
('D', 'A', 'B')
('D', 'A', 'C')
('D', 'B', 'A')
('D', 'B', 'C')
('D', 'C', 'A')
('D', 'C', 'B')
for i in itertools.permutations(range(3)):
    print(i)
(0, 1, 2)
(0, 2, 1)
(1, 0, 2)
(1, 2, 0)
(2, 0, 1)
(2, 1, 0)

(3)combinations——组合

for i in itertools.combinations('ABCD', 2):  # 2是组合的长度
    print(i)
('A', 'B')
('A', 'C')
('A', 'D')
('B', 'C')
('B', 'D')
('C', 'D')
for i in itertools.combinations(range(4), 3):
    print(i)
(0, 1, 2)
(0, 1, 3)
(0, 2, 3)
(1, 2, 3)

(4)combinations_with_replacement——元素可重复组合

for i in itertools.combinations_with_replacement('ABC', 2):  # 2是组合的长度
    print(i)
('A', 'A')
('A', 'B')
('A', 'C')
('B', 'B')
('B', 'C')
('C', 'C')
for i in itertools.product('ABC',repeat=2):
    print(i)
('A', 'A')
('A', 'B')
('A', 'C')
('B', 'A')
('B', 'B')
('B', 'C')
('C', 'A')
('C', 'B')
('C', 'C')

2、拉链

(1)zip——短拉链

for i in zip("ABC", "012", "xyz"):
    print(i)
('A', '0', 'x')
('B', '1', 'y')
('C', '2', 'z')

长度不一时,执行到最短的对象处,就停止

for i in zip("ABC", [0, 1, 2, 3, 4, 5]):          # 注意zip是内置的,不需要加itertools
    print(i)
('A', 0)
('B', 1)
('C', 2)

(2)zip_longest——长拉链

长度不一时,执行到最长的对象处,就停止,缺省元素用None或指定字符替代

for i in itertools.zip_longest("ABC", "012345"):
    print(i)
('A', '0')
('B', '1')
('C', '2')
(None, '3')
(None, '4')
(None, '5')
for i in itertools.zip_longest("ABC", "012345", fillvalue = "?"):
    print(i)
('A', '0')
('B', '1')
('C', '2')
('?', '3')
('?', '4')
('?', '5')

3、无穷迭代器

(1)count(start=0, step=1)——计数

创建一个迭代器,它从 start 值开始,返回均匀间隔的值
itertools.count(10)
10
11
12
.
.
.

(2)cycle(iterable)——循环

创建一个迭代器,返回 iterable 中所有元素,无限重复
itertools.cycle("ABC")
A
B
C
A
B
C
.
.
.

(3)repeat(object [, times])——重复

创建一个迭代器,不断重复 object 。除非设定参数 times ,否则将无限重复
for i in itertools.repeat(10, 3):
    print(i)
10
10
10

4、其他

(1)chain(iterables)——锁链

把一组迭代对象串联起来,形成一个更大的迭代器
for i in itertools.chain('ABC', [1, 2, 3]):
    print(i)
A
B
C
1
2
3

(2)enumerate(iterable, start=0)——枚举(Python内置)

产出由两个元素组成的元组,结构是(index, item),其中index 从start开始,item从iterable中取
for i in enumerate("Python", start=1):
    print(i)
(1, 'P')
(2, 'y')
(3, 't')
(4, 'h')
(5, 'o')
(6, 'n')

(3)groupby(iterable, key=None)——分组

创建一个迭代器,按照key指定的方式,返回 iterable 中连续的键和组
一般来说,要预先对数据进行排序
key为None默认把连续重复元素分组
for key, group in itertools.groupby('AAAABBBCCDAABBB'):
    print(key, list(group))
A ['A', 'A', 'A', 'A']
B ['B', 'B', 'B']
C ['C', 'C']
D ['D']
A ['A', 'A']
B ['B', 'B', 'B']
animals = ["duck", "eagle", "rat", "giraffe", "bear", "bat", "dolphin", "shark", "lion"]
animals.sort(key=len)
print(animals)
['rat', 'bat', 'duck', 'bear', 'lion', 'eagle', 'shark', 'giraffe', 'dolphin']
for key, group in itertools.groupby(animals, key=len):
    print(key, list(group))
3 ['rat', 'bat']
4 ['duck', 'bear', 'lion']
5 ['eagle', 'shark']
7 ['giraffe', 'dolphin']
animals = ["duck", "eagle", "rat", "giraffe", "bear", "bat", "dolphin", "shark", "lion"]
animals.sort(key=lambda x: x[0])
print(animals)
for key, group in itertools.groupby(animals, key=lambda x: x[0]):
    print(key, list(group))
['bear', 'bat', 'duck', 'dolphin', 'eagle', 'giraffe', 'lion', 'rat', 'shark']
b ['bear', 'bat']
d ['duck', 'dolphin']
e ['eagle']
g ['giraffe']
l ['lion']
r ['rat']
s ['shark']

itertools 其他函数可参考官方文档

再谈编程

15.1 Python之禅

import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
  • Beautiful is better than ugly

整齐、易读胜过混乱、晦涩

  • Simple is better than complex

简约胜过复杂

  • Complex is better than complicated

复杂胜过晦涩

  • Flat is better than nested

扁平胜过嵌套

  • Now is better than never.

  • Although never is often better than right now.

理解一:先行动起来,编写行之有效的代码,不要企图一开始就编写完美无缺的代码

理解二:做比不做要好,但是盲目的不加思考的去做还不如不做

  • If the implementation is hard to explain, it's a bad idea.
  • If the implementation is easy to explain, it may be a good idea.

如果方案很难解释,很可能不是有一个好的方案,反之亦然

【个人感悟】

1、首先要行动起来,编写行之有效的代码;

2、如果都能解决问题,选择更加简单的方案;

3、整齐、易读、可维护性、可扩展性好;

4、强壮、健壮、鲁棒性好;

5、响应速度快,占用空间少。

有些时候,鱼和熊掌不可兼得,根据实际情况进行相应的取舍

15.2 时间复杂度分析

【1】代数分析

求最大值和排序

import numpy as np
x = np.random.randint(100, size=10)
x
array([13, 14, 33, 79, 18, 26, 17, 65, 87, 63])
  • 寻找最大值的时间复杂度为O(n)

  • 选择排序时间复杂度O(n^2)

代数分析

def one(x):
    """常数函数"""
    return np.ones(len(x))

def log(x):
    """对数函数"""
    return np.log(x)

def equal(x):
    """线性函数"""
    return x

def n_logn(x):
    """nlogn函数"""
    return x*np.log(x)

def square(x):
    """平方函数"""
    return x**2

def exponent(x):
    """指数函数"""
    return 2**x
import matplotlib.pyplot as plt
plt.style.use("seaborn-whitegrid")

t = np.linspace(1, 20, 100)
methods = [one, log, equal, n_logn, square, exponent]
method_labels = ["$y = 1$", "$y = log(x)$", "$y = x$", "$y = xlog(x)$", "$y = x^2$", "$y = 2^x$"]
plt.figure(figsize=(12, 6))
for method, method_label in zip(methods, method_labels):
    plt.plot(t, method(t), label=method_label, lw=3)
plt.xlim(1, 20)
plt.ylim(0, 40)
plt.legend()
<matplotlib.legend.Legend at 0x22728098e80>

在这里插入图片描述

我们的最爱:常数函数和对数函数

勉强接受:线性函数和nlogn函数

难以承受:平方函数和指数函数

【2】三集不相交问题

问题描述: 假设有A、B、C三个序列,任一序列内部没有重复元素,欲知晓三个序列交集是否为空

import random
def creat_sequence(n):
    A = random.sample(range(1, 1000), k=n)
    B = random.sample(range(1000, 2000), k=n)
    C = random.sample(range(2000, 3000), k=n)
    return A, B, C
A, B, C = creat_sequence(100)
def no_intersection_1(A, B, C):
    for a in A:
        for b in B:
            for c in C:
                if a == b == c:
                    return False
    return True

%timeit no_intersection_1(A, B, C)
no_intersection_1(A, B, C)
36.7 ms ± 2.12 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)





True
def no_intersection_2(A, B, C):
    for a in A:
        for b in B:
            if a == b:
                for c in C:
                    if a == c:
                        return False
    return True

%timeit no_intersection_2(A, B, C)
301 µs ± 37.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
import time

res_n_3 = []
res_n_2 = []

for n in [10, 20, 100]:
    A, B, C = creat_sequence(n)
    start_1 = time.time() 
    for i in range(100):
        no_intersection_1(A, B, C)
    end_1 = time.time()
    for i in range(100):
        no_intersection_2(A, B, C)
    end_2 = time.time()
    res_n_3.append(str(round((end_1 - start_1)*1000))+"ms")
    res_n_2.append(str(round((end_2 - end_1)*1000))+"ms")

print("{0:<23}{1:<15}{2:<15}{3:<15}".format("方法", "n=10", "n=20", "n=100"))
print("{0:<25}{1:<15}{2:<15}{3:<15}".format("no_inte rsection_1", *res_n_3))
print("{0:<25}{1:<15}{2:<15}{3:<15}".format("no_intersection_2", *res_n_2))
方法                     n=10           n=20           n=100          
no_inte rsection_1       6ms            42ms           4001ms         
no_intersection_2        0ms            1ms            24ms           

【3】元素唯一性问题

问题描述:A 中的元素是否唯一

def unique_1(A):
    for i in range(len(A)):
        for j in range(i+1, len(A)):
            if A[i] == A[j]:
                return False
    return True
def unique_2(A):
    A_sort = sorted(A)  # n*log(n)
    for i in range(len(A_sort)-1):
            if A[i] == A[i+1]:
                return False
    return True
import random
res_n_2 = []
res_n_log_n = []

for n in [100, 1000]:
    A = list(range(n))
    random.shuffle(A)
    start_1 = time.time()
    for i in range(100):
        unique_1(A)
    end_1 = time.time()
    for i in range(100):
        unique_2(A)
    end_2 = time.time()
    res_n_2.append(str(round((end_1 - start_1)*1000))+"ms")
    res_n_log_n.append(str(round((end_2 - end_1)*1000))+"ms")

print("{0:<13}{1:<15}{2:<15}".format("方法", "n=100", "n=1000"))
print("{0:<15}{1:<15}{2:<15}".format("unique_1", *res_n_2))
print("{0:<15}{1:<15}{2:<15}".format("unique_2", *res_n_log_n))
方法           n=100          n=1000         
unique_1       49ms           4044ms         
unique_2       1ms            21ms           

【4】第n个斐波那契数

a(n+2) = a(n+1) + a(n)

def bad_fibonacci(n):
    if n <= 1:
        return n
    else:
        return  bad_fibonacci(n-2)+ bad_fibonacci(n-1)

O(2^n)

def good_fibonacci(n):
    i, a, b = 0, 0, 1
    while i < n:
        a, b = b, a+b
        i += 1
    return a
3

O(n)

%timeit  bad_fibonacci(10)
20.6 µs ± 1.15 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit good_fibonacci(10)
875 ns ± 24.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

【5】最大盛水容器(leetcode第11题)

暴力求解——双循环

def max_area_double_cycle(height):
    """暴力穷举双循环"""
    i_left, i_right, max_area = 0,0,0
    for i in range(len(height)-1):
        for j in range(i+1, len(height)):
            area = (j-i) * min(height[j], height[i])
            if area > max_area:
                i_left, i_right, max_area = i, j, area
    return  i_left, i_right, max_area
height = np.random.randint(1, 50, size=10)
print(height)
max_area_double_cycle(height)
[10 11 41 26  2 44 26 43 36 30]





(2, 8, 216)
import matplotlib.pyplot as plt

plt.bar(range(10), height, width=0.5)
plt.xticks(range(0, 10, 1))
([<matplotlib.axis.XTick at 0x22728e01b00>,
  <matplotlib.axis.XTick at 0x227289ce518>,
  <matplotlib.axis.XTick at 0x22728e01358>,
  <matplotlib.axis.XTick at 0x22728f38c50>,
  <matplotlib.axis.XTick at 0x22728f38b00>,
  <matplotlib.axis.XTick at 0x22728f4f4a8>,
  <matplotlib.axis.XTick at 0x22728f4f978>,
  <matplotlib.axis.XTick at 0x22728f4fe48>,
  <matplotlib.axis.XTick at 0x22728f60358>,
  <matplotlib.axis.XTick at 0x22728f60828>],
 <a list of 10 Text xticklabel objects>)

在这里插入图片描述

双向指针

def max_area_bothway_points(height):
    """双向指针法"""
    
    i = 0
    j = len(height)-1
    i_left, j_right, max_area=0, 0, 0
    while i < j:
        area = (j-i) * min(height[i], height[j])
        if area > max_area:
            i_left, j_right, max_area = i, j, area
        if height[i] == min(height[i], height[j]):
            i += 1
        else:
            j -= 1
    return i_left, j_right, max_area
 max_area_bothway_points(height)
(2, 8, 216)
double_cycle = []
bothway_points = []

for n in [5, 50, 500]:
    height = np.random.randint(1, 50, size=n)
    start_1 = time.time()
    for i in range(100):
        max_area_double_cycle(height)
    end_1 = time.time()
    for i in range(100):
        max_area_bothway_points(height)
    end_2 = time.time()
    double_cycle.append(str(round((end_1 - start_1)*1000))+"ms")
    bothway_points.append(str(round((end_2 - end_1)*1000))+"ms")

print("{0:<15}{1:<15}{2:<15}{3:<15}".format("方法", "n=5", "n=50", "n=500"))
print("{0:<13}{1:<15}{2:<15}{3:<15}".format("暴力循环", *double_cycle))
print("{0:<13}{1:<15}{2:<15}{3:<15}".format("双向指针", *bothway_points))   
方法             n=5            n=50           n=500          
暴力循环         3ms            97ms           7842ms         
双向指针         2ms            8ms            56ms           

【6】是不是时间复杂度低就一定好?

100000n VS 0.00001n^2

【7】影响运算速度的因素

  • 硬件

  • 软件

  • 算法