热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

python基础补充(三)

python基础补充讲解(3)迭代器和生成器迭代器生成器python的其他便利特点解析语法序列类型的打包和解包打包解包同时分配作用域与命名空间第一类对象


python基础补充讲解(3)

  • 迭代器和生成器
    • 迭代器
    • 生成器
  • python的其他便利特点
    • 解析语法
    • 序列类型的打包和解包
      • 打包
      • 解包
      • 同时分配
  • 作用域与命名空间
    • 第一类对象
  • 模块和import



前面两篇内容,我们详细讲述了python的对象,表达式,控制流程和异常处理,这次我们来补充最后的一点基础内容,迭代器和生成器以及python其他便利的特点。


迭代器和生成器

对于python来说,函数仍然是非常重要的一个内容,但是有些时候函数并不能很好地解决问题,比如调用一个函数需要占用太多的时间空间,然而运行的结果又只有一小部分有用,就会造成很大的浪费。针对这样的问题,有没有比函数更好的解决问题的方法呢?


迭代器

迭代器听着很陌生,但实际上已经是我们的老朋友了。比如,我们有这样一个循环:

a=5
for i in a:print(i)

就会获得一个如下错误:
在这里插入图片描述
错误提示为a是int类型,不是可迭代对象。说到这里,你是不是就清楚了?作为for循环的循环条件,一定是可迭代对象,而迭代器便是生成这种对象的。有一些基本的容器类型如列表,元组和集合,都可以定义为可迭代类型,文件可以产生行迭代,用户的自定义类型也是可支持迭代的。
由于我们常用for进行迭代,所以使用迭代器iter()生成可迭代对象并不多用。迭代的机制有以下规定:


  1. 如果对象a可迭代,我们可以通过iter(a)生成一个迭代器类型,这个迭代器类型会存储a的全部有效内容;
  2. 迭代器是一种对象,这类对象是由可迭代对象生成的。迭代器有一个专属的内置方法next(),他可以找到当前下标(首次调用为0)的值,并将下标进行+1操作:

a=range(5)
print(type(a))
b=iter(a)
print(next(b))
print(b.__next__())
# next是一个特殊的方法,这两种使用方式都是python认可的
print()
for i in b:print(i,end=' ')
# 输出结果为:
# 0
# 1
#
# 2 3 4

这个例子可以证明两点,一是 for循环的循环条件是一个可迭代的类型生成的迭代器类型,不同于C语言,python的for不需要通过计数器+1并判断是否满足循环条件来进行,而是不断对迭代器类型调用next函数,直到遍历到迭代器的最后一个元素。二是 next调用过后,下标信息会得以保存。如果已经对迭代器类型调用过next,再将该迭代器作为for的循环体,那么将从该迭代器的当前下标继续遍历下去。
利用这个特点,我们可以查看被打断的for便利到了什么位置:

a=range(10)
i=1
b=iter(a)
for i in b:print(i,end=' ')if i==2:breakprint('\n',next(b),sep='') # 检查for循环停止的位置
# 输出为:0 1 2
# 3

需要注意的是,普通的可迭代对象是不可以调用next的,只有迭代器对象才可以。


生成器

学习过了迭代器,我们来看一看生成器。首先看看这段代码:

a=range(10)
print(a)
# 输出结果为:range(0, 10)

这个结果是不是有点出乎意料?其实博主第一次接触range时以为它生成的是一个列表或者元组的类型,但实际上并非如此。举个例子,for i in range(100000)这个语法是python可以执行的,但是如果这个循环在过程中被打断,而range(100000)生成了100000个元素的列表,是不是造成了时间和空间的浪费呢?为了解决这种问题,python设置了“偷懒”的机制,即这里的range(100000)是在循环运行之中一个一个的生成下一个数字,如果for被打断了,那后面的数字就不会被生成。这个思路在python中很常见,这也就是生成器功能的一个雏形。
生成器的语法实现类似于函数,但它不会返回值。为了显示序列中的每一个元素,我们会使用yield语句。下面我们看一个例子:

def fun1(n,a):for i in range(n):a.append(i)return a
def fun2(n,a):for i in range(n):a.append(i)yield aa=[]
b=[]
print(fun1(10,a))
print(fun2(10,b))
j=0 # 设置计数器,记录for循环运行次数
for i in fun2(10,b):print(i)j+=1
print(j)
# 输出结果为:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
#
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 1

可以看到,生成器函数会首先占住一个地址,但是仅当需要使用生成器生成内容时,里面的指令才会被执行。而生成器的“返回值”同样也是可迭代类型,所以我们可以把yield放到一个循环之中:

def fibonacci(): # 构建斐波那切数列生成器a=0b=1while True:yield a # 将a放入生成器数据future=a+ba=bb=future
j=0
a=[]
for i in fibonacci():a.append(i) # 由一串元素组成的某类型实例才能转换成列表,元组等类型j+=1if j==10:break # 没有终止条件,fibonacci数列会一直生成下去
print(a)
# 输出为:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

在这个生成器中,我们如果不设置终止条件,生成器会一直给出下一个值。如果把生成器换成函数,那么这样调用就会陷入死循环了。当然,我们还可以对生成器做个改进:

def fibonacci(n): # 让生成器可以决定生成数字的上限,# 避免调用时没有设置终止条件导致死循环a=0b=1for i in range(n):a,b=b,a+b # 这种写法将在后面打包解包中介绍yield a

这样的修改就会让生成器更合理。


python的其他便利特点

在这一节,我会继续给大家介绍一些Python中可以让代码变得简洁的其他写法。


解析语法

一种很常见的编程任务是基于另一个序列产生一系列的值。它的一般形式如下:

a=[expression for value in iterable if condition]

这句话在逻辑上就等价于:

a=[]
for value in iterable:if condition:a.append(expression) # expression是基于value的表达式

举个例子,假如我们需要输出1到10中所有奇数的平方,就可以这样写:

a=[i**2 for i in range(10) if i%2!=0]
print(a)
# 输出为:[1, 9, 25, 49, 81]

以上的内容是简单的解析语法,它是一个列表解析。我们还可以用类似的语法生成集合解析,生成器解析,字典解析:

result1=(i**2 for i in range(10) if i%2!=0) # 生成器解析
print(result1)
result2={i:i**2 for i in range(10) if i%2!=0} # 字典解析
print(result2)
result3={i**2 for i in range(10) if i%2!=0} # 集合解析
print(result3)
# 输出结果为: at 0x000001CEE7F24BA0>
# {1: 1, 3: 9, 5: 25, 7: 49, 9: 81}
# {1, 9, 81, 49, 25}

当然,我们也可以把生成器解析当成可迭代对象当成for循环条件,大家可以自行实验。


序列类型的打包和解包


打包

不知道大家有没有尝试过给一个变量赋多个值呢?其实这种复制方法也是python所允许的,我们来看一下:

data=1,2,3,4,5
print(data,type(data))
# 输出为:(1, 2, 3, 4, 5)

我们给data赋了五个值,而打印出来data是一个元组类型。这是由于python语法会把这五个数字自动打包成一个元组。有了这个设定,我们也就可以使用return返回多个值了,因为python会把它们自动打包成元组类型:

def tul(a,b):return a,b
print(tul(10,20))
# 输出结果为:(10, 20)

讲到这里,我们来稍作回忆。不知大家还是否记得可变参数传参呢?这实际上就是一种打包的实际应用,正常情况下,一个类型的元素只能赋值给一个函数参数,然而自动打包可以让一个参数接收多个被打包好的元素,这也就是为什么使用可变参数传递多个参数,函数中会收到一个元组类型的元素的原因:

def sum (*a):sum=0print(type(a))for i in a:sum+=ireturn sum
print(sum(1,3,5,7,9))
# 输出为:
# 25

大家是不是更理解可变参数传参了呢?


解包

了解了打包,我们接下来看看对应的解包:

a,b,c=fibonacci(3) # 这里沿用之前的改进版斐波那切数列生成器
print(a,b,c)
# 输出为:1 1 2

对应的,我们用多个标识符去接收元组或生成器的内容(前提是标识符数量和元组或迭代器的长度必须相同),那么他们也会从左至右依次被赋予对应值。


同时分配

那么,如果我们把一系列的数字分配给一系列的标识符是不是可以呢?我们来看:

a,b,c=1,2,3
print(a,b,c)
# 输出为:1 2 3

这个就叫做同时分配,它会将右面的值打包成一个元组,然后将元祖解包,分给左面的标识符。有了这个技术,我们可以用简单的语句实现诸如交换变量之类的关联值:

a=1
b=2
a,b=b,a+b
print(a,b)
# 输出为:2 3

这段代码熟悉吗?这是我们对斐波那契数列生成器的改进代码。应用的就是自动分配技术,有了这项技术,我们就可以在不借助中间变量的情况下完成同时对两个或是多个变量值的修改。


作用域与命名空间

Python中,如果我们需要计算两个数的和,那么在计算之前,一定需要将这两个标识符与作为值的对象进行关联,这就是我们通常所理解的赋值,而这个赋值的过程实际上被称为名称解析
标识符都有一个有效的作用范围,最高级的赋值通常是全局变量,而如果在类似函数体内使用的普通标识符,它的有效作用范围只在函数体内,即局部变量。Python中每一个定义域使用了一个抽象名称,称为命名空间。命名空间管理当前作用域内的所有标识符。我们分析一下这段代码:

def space1(grades):gpa=3.56major='CS'
def space2(data):n=2target='A'item=data[2]space1(['A-','B+','A-']) # 调用space1,自动生成命名空间
space2(['A-','B+','A-']) # 调用space2,自动生成命名空间

以下这是对命名空间的分析:
在这里插入图片描述
例子中这些标识符的作用域除列表外的作用域都只有各自的函数体。而如果写成这样的形式:

def space():arguments.append('B-')print(arguments)arguments=['A-','B+','A-']
space()
print(arguments)
# 输出为:['A-', 'B+', 'A-', 'B-']
# ['A-', 'B+', 'A-', 'B-']

从结果可以看出,全局变量可以直接在函数中使用。


第一类对象

第一类对象是指可以分配给标识符的类型实例,可以作为参数传递,或由一个函数返回。我们现在所知道的基本数据类型,基本都是第一类对象,函数本身也作为第一类对象处理。我们现在只需要理解作为第一类对象,是可以将其内容传递给其他的第一类对象或作为参数传递给函数即可。


模块和import

大家都应该知道我们调用了某个模块后,想要引用其中的函数,需要以下的语法:

import math
print(math.sqrt(2))
# 输出为:1.4142135623730951

如果我们不想写math.,可以用以下方式引用:

from math import sqrt
print(sqrt(2))

其实,还有一种更简单的方法,可以一下把一个模块中的所有函数以及变量全都引用过来,供我们直接调用:

from math import *
print(sqrt(2))

但是这种方法是有风险的,风险在于如果模块中有一些与当前命名空间冲突的函数,或与另一个模块导入的函数名重复,函数的内容就会彼此覆盖:

from math import *
def sin(n):return n
print(sin(pi))
# 输出为:3.141592653589793

覆盖的顺序是按照导入或定义的顺序来的,也就是说,我们先导入了sin,然后再定义一个sin,新定义的sin会覆盖掉引入的sin,反之自定义的sin就会被覆盖掉。
其实,使用from import虽然也能够产生覆盖的现象,但是由于引用的函数有限且明确,非常方便我们进行检查。因此,我更推荐使用from import进行引入。
下面,给大家介绍一些数据结构和算法相关的现有python模块:


模块名描述
array为原始类型提供了紧凑的数组存储
collection定义额外的数据结构和包括对象集合的抽象基类
copy定义通用函数来复制对象
heapq提供基于堆的优先队列函数
math定义常见的数学常数和函数
os提供与操作系统交互
Re对处理正则表达式提供支持
sys提供了与Python解释器交互的额外等级
time对测量时间或延迟程序提供支持
random提供随机数生成

说到随机数,伪随机数也是实际中很常用的。大家可以看一看伪随机和真随机的区别。
本节的结尾,再给大家仔细介绍一下Random类的实力支持的方法和random模块的顶级函数:


语法描述
seed(hashable)基于参数的散列值初始化伪随机数生成器
random()在开区间(0.0,1.0)返回一个伪随机浮点值
randint(a,b)在闭区间[a,b]返回一个为随机整数
randrange(start,stop,step)在参数指定的python标准范围内返回一个伪随机整数
choice(seq)返回一个伪随机选择的给定序列中的元素
shuffle(seq)重新排列给定的伪随机序列中的元素

这些用法光听说是不够的,大家下去要自行练习。本节课给大家介绍了迭代器生成器,解析语法,打包解包,作用域空间和模块,理解这些对我们后续的数据结构学习以及编程能力的提升都有很大帮助。通过这三节,基本给大家补充了足够多的python基础知识,下节开始我们会继续对面对对象编程做一个补充,大家做好准备~


推荐阅读
  • Ihavetwomethodsofgeneratingmdistinctrandomnumbersintherange[0..n-1]我有两种方法在范围[0.n-1]中生 ... [详细]
  • 本文对比了杜甫《喜晴》的两种英文翻译版本:a. Pleased with Sunny Weather 和 b. Rejoicing in Clearing Weather。a 版由 alexcwlin 翻译并经 Adam Lam 编辑,b 版则由哈佛大学的宇文所安教授 (Prof. Stephen Owen) 翻译。 ... [详细]
  • 重要知识点有:函数参数默许值、盈余参数、扩大运算符、new.target属性、块级函数、箭头函数以及尾挪用优化《深切明白ES6》笔记目次函数的默许参数在ES5中,我们给函数传参数, ... [详细]
  • poj 3352 Road Construction ... [详细]
  • 如何将Python与Excel高效结合:常用操作技巧解析
    本文深入探讨了如何将Python与Excel高效结合,涵盖了一系列实用的操作技巧。文章内容详尽,步骤清晰,注重细节处理,旨在帮助读者掌握Python与Excel之间的无缝对接方法,提升数据处理效率。 ... [详细]
  • 遗传算法中选择算子为何置于交叉算子和变异算子之前?本文探讨了这一问题,并详细介绍了遗传算法中常用的选择算子类型及其作用机制。此外,还分析了不同选择算子对算法性能的影响,为实际应用提供了理论依据。 ... [详细]
  • 在机器学习领域,深入探讨了概率论与数理统计的基础知识,特别是这些理论在数据挖掘中的应用。文章重点分析了偏差(Bias)与方差(Variance)之间的平衡问题,强调了方差反映了不同训练模型之间的差异,例如在K折交叉验证中,不同模型之间的性能差异显著。此外,还讨论了如何通过优化模型选择和参数调整来有效控制这一平衡,以提高模型的泛化能力。 ... [详细]
  • 泰波那契数列与斐波那契数列类似,但其计算方法有所不同。本文详细解析了如何高效计算第 N 个泰波那契数,并提供了一种基于动态规划的优化算法。通过使用数组记录中间结果,避免了重复计算,显著提高了算法的执行效率。代码示例展示了具体的实现方法,帮助读者更好地理解和应用这一算法。 ... [详细]
  • POJ 2482 星空中的星星:利用线段树与扫描线算法解决
    在《POJ 2482 星空中的星星》问题中,通过运用线段树和扫描线算法,可以高效地解决星星在窗口内的计数问题。该方法不仅能够快速处理大规模数据,还能确保时间复杂度的最优性,适用于各种复杂的星空模拟场景。 ... [详细]
  • 如何精通编程语言:全面指南与实用技巧
    如何精通编程语言:全面指南与实用技巧 ... [详细]
  • Netty框架中运用Protobuf实现高效通信协议
    在Netty框架中,通过引入Protobuf来实现高效的通信协议。为了使用Protobuf,需要先准备好环境,包括下载并安装Protobuf的代码生成器`protoc`以及相应的源码包。具体资源可从官方下载页面获取,确保版本兼容性以充分发挥其性能优势。此外,配置好开发环境后,可以通过定义`.proto`文件来自动生成Java类,从而简化数据序列化和反序列化的操作,提高通信效率。 ... [详细]
  • 投融资周报 | Circle 达成 4 亿美元融资协议,唯一艺术平台 A 轮融资超千万美元 ... [详细]
  • 在托管C++中开发应用程序时,遇到了如何声明和操作字符串数组的问题。本文详细探讨了字符串数组在托管C++中的应用与实现方法,包括声明、初始化、遍历和常见操作技巧,为开发者提供了实用的参考和指导。 ... [详细]
  • 深入解析Spring Boot启动过程中Netty异步架构的工作原理与应用
    深入解析Spring Boot启动过程中Netty异步架构的工作原理与应用 ... [详细]
  • openGauss行存储核心架构及其页面组织详解
    行存储的核心架构和页面组织是实现DML操作、可见性判断及多种管理功能的基础。作为基于磁盘的存储引擎,行存储在设计上采用了段页式结构,以优化数据的存储和访问效率。这种设计不仅确保了数据的高效存储,还为行存储的各种高级功能提供了坚实的技术支持。 ... [详细]
author-avatar
暗罗华怡
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有