热门标签 | 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基础知识,下节开始我们会继续对面对对象编程做一个补充,大家做好准备~


推荐阅读
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了在CentOS上安装Python2.7.2的详细步骤,包括下载、解压、编译和安装等操作。同时提供了一些注意事项,以及测试安装是否成功的方法。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 本文介绍了在Linux下安装Perl的步骤,并提供了一个简单的Perl程序示例。同时,还展示了运行该程序的结果。 ... [详细]
  • Python正则表达式学习记录及常用方法
    本文记录了学习Python正则表达式的过程,介绍了re模块的常用方法re.search,并解释了rawstring的作用。正则表达式是一种方便检查字符串匹配模式的工具,通过本文的学习可以掌握Python中使用正则表达式的基本方法。 ... [详细]
  • 本文探讨了C语言中指针的应用与价值,指针在C语言中具有灵活性和可变性,通过指针可以操作系统内存和控制外部I/O端口。文章介绍了指针变量和指针的指向变量的含义和用法,以及判断变量数据类型和指向变量或成员变量的类型的方法。还讨论了指针访问数组元素和下标法数组元素的等价关系,以及指针作为函数参数可以改变主调函数变量的值的特点。此外,文章还提到了指针在动态存储分配、链表创建和相关操作中的应用,以及类成员指针与外部变量的区分方法。通过本文的阐述,读者可以更好地理解和应用C语言中的指针。 ... [详细]
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 31.项目部署
    目录1一些概念1.1项目部署1.2WSGI1.3uWSGI1.4Nginx2安装环境与迁移项目2.1项目内容2.2项目配置2.2.1DEBUG2.2.2STAT ... [详细]
  • 这篇文章主要介绍了Python拼接字符串的七种方式,包括使用%、format()、join()、f-string等方法。每种方法都有其特点和限制,通过本文的介绍可以帮助读者更好地理解和运用字符串拼接的技巧。 ... [详细]
  • 本文介绍了在Windows系统上使用C语言命令行参数启动程序并传递参数的方法,包括接收参数程序的代码和bat文件的编写方法,同时给出了程序运行的结果。 ... [详细]
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社区 版权所有