当前位置: 首页 > article >正文

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

文章目录

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

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

一、生成器是什么

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

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

定义:

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

1. 最简单的生成器

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

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或者满足结束条件结束函数为止。
def fib():
    a,b = 0,1
    while True:
        yield a
        a, b = b, a + b
# 迭代器是无限
def fib_d(times):
    # 初始化
    n = 0
    a, b = 0, 1
    while n < times:
        yield (b)
        a, b = b, a + b
        n += 1
	return 'ok'# 迭代器是有限

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

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

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

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

三个条件,缺一不可:

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

1. 案例1

#闭包函数的实例# 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() # 15

    demo2 = outer(7)
    demo2()#17

修改外部函数的变量:

def outer( a ):
    b = 10
    # inner是内函数
    def inner():
        # 表示这个变量不是局部变量空间的变量,需要向上一层变量空间找这个变量。
        nonlocal b
        print(b)
        #在内函数中 用到了外函数的临时变量
        b = b+1
        print(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:实现项目的日志处理

import logging

def 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 _logging


project_1_logging = log_header('project_1')

project_2_logging = log_header('project_2')

def project_1():
    # do something
    project_1_logging('this is a debug info', 'debug')
    # do something
    project_1_logging('this is a warning info', 'warning')
    # do something
    project_1_logging('this is a error info', 'error')

def project_2():
    # do something
    project_2_logging('this is a debug info', 'debug')
    # do something
    project_2_logging('this is a warning info', 'warning')
    # do something
    project_2_logging('this is a critical info', 'error')


project_1()
project_2()

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

def makebold(fn):
    def wrapped():
        return "<b>" + fn() + "</b>"
    return wrapped

def 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.kler.cn/a/514735.html

相关文章:

  • 使用Sum计算Loss和解决梯度累积(Gradient Accumulation)的Bug
  • 最小距离和与带权最小距离和
  • Saas Paas Iaas服务区别
  • 医学图像分析工具09.1:Brainstorm安装教程
  • transformers使用过程问题
  • html简单项目案例
  • Java设计模式 九 桥接模式 (Bridge Pattern)
  • 【软件测试项目实战 】淘宝网:商品购买功能测试
  • Spring Boot 使用 Micrometer 集成 Prometheus 监控 Java 应用性能
  • R 语言科研绘图 --- 散点图-汇总
  • C++:将字符数组rkpryyrag,每个字母转换为其前面第13个字母后输出,如果超过a则从z再继续接着数。例如:b前面第1个字母是a。a前面第3个字母是x。
  • 【华为路由的arp配置】
  • 利用 SoybeanAdmin 实现前后端分离的企业级管理系统
  • 隐私保护+性能优化,RyTuneX 让你的电脑更快更安全
  • 麒麟操作系统服务架构保姆级教程(十三)tomcat环境安装以及LNMT架构
  • 【Qt】窗口
  • windows下本地部署安装hadoop+scala+spark-【不需要虚拟机】
  • 中国认知作战研究中心:从认知战角度分析2007年iPhone发布
  • HTTPS的加密原理
  • 基于RK3576/RK3588+FPGA+AI深度学习的轨道异物检测技术研究
  • spring ApplicationContext的事件监听机制
  • 「全网最细 + 实战源码案例」设计模式——简单工厂模式
  • 循环队列(C语言版)
  • AGI发展的现实约束与定义困境
  • 学习记录-统计记录场景下的Redis写请求合并优化实践
  • [矩阵扩散]