【Python】笔试面试题之生成器、闭包、字典

news/2025/1/24 1:58:49/

文章目录

  • 一、生成器是什么
    • 1. 最简单的生成器
    • 2. 在函数中使用yield关键字
  • 二、闭包是什么?怎么理解?
    • 1. 案例1
    • 2. 案例2:实现项目的日志处理
    • 3. 案例3:装饰器就是一种闭包
    • 4. 总结
  • 三、Python中字典的底层是怎么实现的
    • 1. 相关概念
    • 2. Python中字典的操作
    • 3. 哈希冲突
    • 4. 总结

个人主页:道友老李
欢迎加入社区:道友老李的学习社区

一、生成器是什么

我们来看看为什么python会添加生成器这个功能:

  • 我们知道我们可以用列表储存数据,可是当我们的数据特别大的时候建立一个列表的储存数据就会很占内存的。这时生成器就派上用场了。它可以说是一个不怎么占计算机资源的一种方法。

定义:

  • 跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
  • 调用一个生成器函数,返回的是一个迭代器对象。

1. 最简单的生成器

我们可以用列表推导式来创建一个生成器:

python">gen = (x for x in range(5))print(gen) 
#output: <generator object <genexpr> at 0x0000000000AA20F8>from collections import Iterable, Iteratorprint(isinstance(gen, Iterable))#output:Trueprint(isinstance(gen, Iterator))
#output:True

2. 在函数中使用yield关键字

  • 在函数中使用yield关键字,函数就变成了一个generator。
  • 函数里有了yield后,执行到yield就会停住,当需要再往下算时才会再往下算。所以生成器函数即使是有无限循环也没关系,它需要算到多少就会算多少,不需要就不往下算。
  • 其作用和return的功能差不多,就是返回一个值给调用者,只不过有yield的函数返回值后函数依然保持调用yield时的状态,当下次调用的时候,在原先的基础上继续执行代码,直到遇到下一个yield或者满足结束条件结束函数为止。
python">def fib():a,b = 0,1while True:yield aa, b = b, a + b
# 迭代器是无限
def fib_d(times):# 初始化n = 0a, b = 0, 1while n < times:yield (b)a, b = b, a + bn += 1return 'ok'# 迭代器是有限

总结:生成器可以避免不必要的计算,带来性能上的提升;而且会节约空间,可以实现无限循环(无穷大的)的数据结构。

二、闭包是什么?怎么理解?

在一个外函数中定义了一个内嵌函数,内嵌函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。

一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

三个条件,缺一不可:

  1. 必须有一个内嵌函数(函数里定义的函数)——这对应函数之间的嵌套
  2. 内嵌函数必须引用一个定义在闭合范围内(外部函数里)的变量——内部函数引用外部变量
  3. 外部函数必须返回内嵌函数——必须返回那个内部函数

1. 案例1

python">#闭包函数的实例# outer是外部函数 a和b都是外函数的临时变量def outer( a ):b = 10# inner是内函数def inner():#在内函数中 用到了外函数的临时变量print(a+b)b = b+1# 外函数的返回值是内函数的引用return inner
if __name__ == '__main__':# 在这里我们调用外函数传入参数5#此时外函数两个临时变量 a是5 b是10 ,并创建了内函数,然后把内函数的引用返回存给了demo# 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数demo = outer(5)# 我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量# demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数demo() # 15demo2 = outer(7)demo2()#17

修改外部函数的变量:

python">def outer( a ):b = 10# inner是内函数def inner():# 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。nonlocal bprint(b)#在内函数中 用到了外函数的临时变量b = b+1print(a+b)# 外函数的返回值是内函数的引用return inner
if __name__ == '__main__':# 在这里我们调用外函数传入参数5#此时外函数两个临时变量 a是5 b是11 ,并创建了内函数,然后把内函数的引用返回存给了demo# 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数demo = outer(5)# 我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量# demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数demo() # 15# demo2 = outer(7)demo()#17

2. 案例2:实现项目的日志处理

python">import loggingdef log_header(logger_name):logging.basicConfig(level=logging.DEBUG, format='%(asctime)s [%(name)s] %(levelname)s  %(message)s',datefmt='%Y-%m-%d %H:%M:%S')logger = logging.getLogger(logger_name)def _logging(something, level='debug'):if level == 'debug':logger.debug(something)elif level == 'warning':logger.warning(something)elif level == 'error':logger.error(something)else:raise Exception("I dont know what you want to do?")return _loggingproject_1_logging = log_header('project_1')project_2_logging = log_header('project_2')def project_1():# do somethingproject_1_logging('this is a debug info', 'debug')# do somethingproject_1_logging('this is a warning info', 'warning')# do somethingproject_1_logging('this is a error info', 'error')def project_2():# do somethingproject_2_logging('this is a debug info', 'debug')# do somethingproject_2_logging('this is a warning info', 'warning')# do somethingproject_2_logging('this is a critical info', 'error')project_1()
project_2()

3. 案例3:装饰器就是一种闭包

python">def makebold(fn):def wrapped():return "<b>" + fn() + "</b>"return wrappeddef makeitalic(fn):def wrapped():return "<i>" + fn() + "</i>"return wrapped@makebold@makeitalicdef hello():return "hello world"
print(hello())

4. 总结

  • 优点和作用:

    • 闭包似优化了变量,原来需要类对象完成的⼯作,闭包也可以完成; 大家回想一下类对象的情况,对象有好多类似的属性和方法,所以我们创建类,用类创建出来的对象都具有相同的属性方法。 闭包也是实现面向对象的方法之一。
    • 装饰器就是一种闭包,装饰器有的功能,闭包也有;
    • 闭包可以实现单例模式和工厂模式;
    • 当闭包执行完后,仍然能够保持住当前的运行环境。以便于下次调用;
  • 缺点:

    • 每次调用函数时,都得在全局作用域申明变量。别人调用函数时还得查看函数内部代码。
    • 当函数在多个地方被调用并且同时记录着很多状态时,会造成非常地混乱。
    • 由于闭包引⽤了外部函数的局部变量,则外部函数的局部变量没有及时释放,消耗内存

三、Python中字典的底层是怎么实现的

1. 相关概念

Python 字典的底层实现是哈希表。调用python内置的哈希函数,将键(Key)作为参数进行转换(哈希运算+取余运算),得到一个唯一的地址(地址的索引),然后将值(Value)存放到对应地址中(给相同的键赋值会直接覆盖原值,因为相同的键转换后的地址是一样的)。

哈希表(Hash Table,又称为散列表)是一种线性表的存储结构。由一个直接寻址表 T (假设大小为m) 和一个哈希函数 h(k) 组成。对于任意可哈希对象,通过哈希函数(一般先进行哈希计算,然后对结果进行取余运算),将该对象映射为寻址表的索引 [0,1,2,…m-1],然后在该索引所对应的空间 T[0,1,2,…,m-1] 进行变量的存储/读取等操作。如下图所示。
([1,2],[3,4,5])

在这里插入图片描述
在这里插入图片描述

注:键(Key)必须是可哈希的,即,通过哈希函数可为此键计算出唯一地址。

对于 Python 来说,变量,列表、字典、集合这些都是可变的,所以都不能做为键(Key)来使用。因为元祖里边可以存放列表这类可变因素,所以如果实在想拿元祖当字典的键(Key),那必须对元祖做限制:元组中只包括像数字和字符串这样的不可变元素时,才可以作为字典中有效的键(Key)。另外还需要注意的一点是,Python 的哈希算法对相同的值计算得到的结果是一样的,也就是说 12315 和 12315.0 的值相同,他们被认为是相同的键(Key)。

2. Python中字典的操作

  • 插入: 对键进行哈希和取余运算,得到一个哈希表的索引,如果该索引所对应的表地址空间为空,将键值对存入该地址空间;
  • 查询/更新: 对健进行哈希和取余运算,得到一个哈希表的索引,如果该索引所对应的地址空间中健与要查询/更新的健一致,那么就将该键值对取出来 / 更新该键所对应的值;
  • 扩容: 字典初始化的时候,会对应初始化一个有k个空间的表,等空间不够用的时候,系统就会自动扩容,这时候会对已经存在的键值对 重新进行哈希取余运算(重新进行插入操作)保存到其它位置;

3. 哈希冲突

由于哈希表的大小是有限的,而要存储的值的总数量是无限的,因此对于任何哈希函数,都会出现两个不同元素映射到同一个位置上的情况,这种情况叫做哈希冲突。字典使用了开放寻址法来解决冲突。

开放寻址法:如果哈希函数返回的位置已经有值,则可以探查新的空位置来存储这个值。
在这里插入图片描述
在这里插入图片描述

4. 总结

在这里插入图片描述


http://www.ppmy.cn/news/1565648.html

相关文章

金融波动率的多模型建模研究:GARCH族与HAR模型的Python实现与对比分析

金融资产波动率建模在现代金融工程中具有重要地位&#xff0c;其应用涵盖风险管理、衍生品定价和投资组合优化等核心领域。本文着重探讨三种主流波动率建模方法&#xff1a;广义自回归条件异方差模型(GARCH)、Glosten-Jagannathan-Runkle-GARCH模型(GJR-GARCH)以及异质自回归模…

docker启动服务占用172.18网段怎么改成其他网段?和网桥有关吗?或者怎么改docker-compose启动用的yml文件

要修改 Docker 服务使用的网段(如从 172.18 改为其他网段),可以通过以下步骤实现: 1. 修改 Docker 默认网桥的网段 Docker 默认使用 docker0 网桥,其网段通常为 172.17.0.0/16。你可以通过修改 Docker 的配置文件来更改默认网段。 修改 Docker 配置文件 编辑 Docker 的…

初始JavaEE篇 —— 快速上手 SpringBoot

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 SpringBoot 相关介绍与解惑 SpringBoot 项目的创建 通过 官方提供的网页 来创建 通过 IDEA 来创建 SpringBoot 项目的介…

深度学习:大模型Decoding+MindSpore NLP分布式推理详解

大模型推理流程 1. 用户输入提示词&#xff08;Prompt&#xff09; 假设用户输入为&#xff1a;“从前&#xff0c;有一只小猫&#xff0c;它喜欢……” 我们的目标是让模型生成一段完整的故事。 2. 模型处理用户输入 2.1 分词&#xff1a;输入提示被分词为模型可以理解的…

MAC 地址转换为标准大写格式

// ConvertToStandardMac 将 MAC 地址转换为标准格式&#xff0c;确保每个字节都是两位&#xff0c;并且字母是大写的 func ConvertToStandardMac(mac string) (string, error) { // 分割 MAC 地址的每一部分 parts : strings.Split(mac, ":") // 确保每部分是两…

基于SpringBoot和PostGIS的各国及所属机场信息检索及可视化实现

目录 前言 一、空间数据简介 1、全球国家信息表 2、机场信息表 3、国家机场检索实现 二、SpringBoot后台实现 1、模型层实现 2、控制层实现 三、WebGIS可视化实现 1、Leaflet界面实现 2、国家及其机场可视化成果 3、全球机场数量排行榜 四、总结 前言 新春佳节即将…

Vue3数据响应式原理

什么是数据响应式 当数据变化时&#xff0c;引用数据的函数&#xff08;副作用函数&#xff09;自动重新执行。 即数据触发了函数的响应&#xff0c;如&#xff1a;视图渲染中使用了某数据&#xff0c;数据改变后&#xff0c;视图跟着自动更新。 触发者&#xff1a;数据 响应者…

【Elasticsearch】inference ingest pipeline

Elasticsearch 的 Ingest Pipeline 功能允许你在数据索引之前对其进行预处理。通过使用 Ingest Pipeline&#xff0c;你可以执行各种数据转换和富化操作&#xff0c;包括使用机器学习模型进行推理&#xff08;inference&#xff09;。这在处理词嵌入、情感分析、图像识别等场景…