(三十三)补充Python经典面试题(吸收高级编程特性)

第一题:

def func(a, b=[]): pass

一、上题讲解:

这个函数定义有一个默认参数b,它的默认值是一个空列表[]。这道面试题涉及到Python中函数参数默认值的一些重要概念和陷阱

首先,当你调用这个函数时,如果不传递参数b的值,它将使用默认的空列表[]。例如:

func(1)  # 这会将a设置为1,b设置为默认的空列表[]

但是,这里有一个陷阱。默认参数b(即空列表[])在函数定义时只会被创建一次,而不是每次函数调用时都会创建一个新的空列表。这就意味着,如果你在一个函数调用中修改了b的值,那么下一次调用该函数时,b将保留上一次的修改。

例如:

func(1)  # a=1, b=[]
b.append(2)
func(3)  # a=3, b=[2]

在上面的例子中,我们首先调用了func(1),然后在b上执行了append(2)操作,导致b变成了[2]。接下来,我们调用了func(3),此时a被设置为3,但b仍然是[2],而不是一个新的空列表[]。

这种行为可能会导致一些不直观的问题和bug,因为使用者可能期望每次调用函数时都会得到一个独立的空列表。

为了避免这种问题,我们可以使用None作为默认值,并在函数内部检查b是否为None,然后在需要时创建一个新的空列表。例如:

def func(a, b=None):
    if b is None:
        b = []
    # 现在每次函数调用都会得到一个新的空列表
    # 其他函数逻辑

这样做可以确保每次调用函数时都会得到一个新的空列表,避免了默认参数共享的问题。

二、是什么导致的上述问题:

这是因为在Python中,默认参数在函数定义时只会被创建一次,并且在函数的整个生命周期内都会保留它们的状态。这是为了提高函数的性能和效率。

当你定义一个函数时,Python会在函数的定义阶段创建默认参数的值,然后将这些值存储在函数的代码对象中。这意味着每次调用函数时,不会重新创建默认参数的新实例,而是会重用已经存在的默认参数。

这种行为有一些优点和一些潜在的陷阱:

优点:

  1. 提高了函数的性能,因为不需要每次函数调用都创建新的默认参数对象。
  2. 可以实现一些有用的功能,例如在多次函数调用之间共享状态。这可以在某些情况下很有用。

潜在的陷阱:

  1. 如果默认参数是可变对象(如列表或字典),并且在函数内部进行了修改,那么这些修改会在后续函数调用中保留下来,可能导致不直观的行为。
  2. 开发者需要谨慎处理默认参数,以避免意外共享状态的问题。

要避免默认参数共享状态的问题,可以使用None作为默认参数的值,并在函数内部检查并创建新的实例,如上解决的方法所示。

总之,Python的默认参数在函数定义时只会被创建一次,这是出于性能和实现的考虑,但在使用可变对象作为默认参数时需要特别小心,以避免不希望的副作用。

第二题:

val = [lambda: i + 1 for i in range(10)]

data = val[0]()
print(data)

这道面试题涉及到Python中的lambda函数和列表推导式,并且可能会引发一个常见的陷阱,即闭包与变量作用域的问题。

将上述代码拆开来看:

def a():
    return i + 1


s = []
for i in range(10):
    s.append(a)

print(s[0]())

相信很多小伙伴看到上述拆开的代码都已经能理解本道面试题的精髓所在了。
但也请继续看下原理,是否和你想的一样~

讲解原理:

首先,列表推导式 [lambda: i + 1 for i in range(10)] 创建了一个包含 10 个 lambda 表达式的列表,每个 lambda 表达式在调用时都会返回 i + 1 的值。**需要注意的是这里每个 lambda 表达式都是一个闭包,它们“记住”了变量 i 的值。 然而,关键之处在于 lambda 表达式记住的是变量 i 而非 i 当时的值。**由于列表推导式内的 i 是在单个作用域内循环的,因此当列表推导式结束时,i 的值将停留在最后一次循环的值,即 9。 之后的代码 data = val[0]选择列表中的第一个 lambda 函数并调用它。因为所有的 lambda 闭包都是对同一个 i 的引用,这时 i 的值是循环结束时的值 9。因此,无论调用列表中的哪一个 lambda 表达式,它都能返回 9 + 1,即 10。

这是因为,**在 Python 的 for 循环中,循环变量 i 会被绑定到列表推导式的外部作用域,而不是每次迭代都创建一个新的作用域。**所以,所有的 lambda 表达式都引用着同一个 i 变量,而在循环结束时,i 的值为 9。 要让每个 lambda 表达式保留它被定义时的 i 值,可以使用默认参数来捕获i的值,以确保每个lambda函数都捕获到不同的值:

val = [lambda i=i: i + 1 for i in range(10)]

data = val[0]()
print(data)

上面修改后的代码中,lambda i=i: i + 1 为每个 lambda 函数创建了一个默认参数 i,它的值在定义 lambda 函数时就被确定下来了。这时,val[0]会输出 1,因为它将使用列表推导式中第一次迭代时 i 的值,即 0,然后加 1。

第三题:

老生常谈,请讲一讲迭代器,生成器,可迭代对象,装饰器,并讲一下它们各自的应用场景。

首先,迭代器(Iterator)、生成器(Generator)、可迭代对象(Iterable)都与遍历数据集合相关,但各有特点,所以放一起讲:

1.1 迭代器(Iterators):

迭代器是遵循迭代器协议的对象,这意味着迭代器对象需要实现两个方法:__iter__()__next__()__iter__() 返回迭代器对象本身,而 __next__() 方法返回容器中的下一个元素。当迭代器中没有更多元素时,__next__()应该抛出一个 StopIteration 异常。迭代器允许一个对象对一组数据进行遍历,但不需要此数据在内存中完全展开。

  • Python的内置容器类型:

    如列表、元组、字典等,都提供了迭代器。例如,当你在列表上调用 __iter__()函数时,会返回一个迭代器,该迭代器可以遍历列表的所有元素。

  • 使用场景:

    当需要访问集合中的元素而不暴露底层表示时;

    当需要一个能够记住遍历位置的对象时,以便在需要时能够从同一位置继续。

1.2 生成器(Generators):

生成器是一种特殊的迭代器,更容易编写。**当需要一次一个地按顺序生成一个序列的值时,使用生成器是非常有用的。**生成器函数使用 yield 语句,每次产生(yield)一个值,函数的状态会被挂起,直到下一个值被请求时再恢复。 生成器表达式是另一种构建生成器的方式,它看起来像列表推导式,但使用圆括号而不是方括号。

  • 使用场景:

    当需要一个懒序列(lazy sequence),该序列按需计算元素而不是预先计算,并且不希望一次性加载所有元素到内存中;

    当处理的是流式数据或大数据集合,只需要一次处理一部分数据;

    当需要一个函数来生成无穷序列下的元素。

1.3 可迭代对象(Iterables):

可迭代对象是实现了 __iter__() 方法的任何 Python 对象,__iter__() 需要返回一个迭代器。另外,可迭代对象也可以实现 __getitem__() 方法,以便按照索引访问元素。字符串、列表等 Python 标准类型都是可迭代的。

class Demo(object):
	def __iter__(self):
		return iter([1, 2, 3])


obj = Demo()
  • 使用场景:

    在使用 for 循环时,你通常会迭代一个可迭代对象;

    当需要一种方式可以一次访问一组元素,而无需将它们全部保存在内存中;

    在使用 map()、filter()、sum()、min()、max() 等内置函数时,这些函数接受一个可迭代对象作为参数

1.4 总结一下:

在 Python 中,迭代器、生成器和可迭代对象是集合数据访问的三个基本概念。迭代器提供了一种通用的遍历集合数据的方法,而生成器提供了一种生成迭代数据的简洁方式,可迭代对象则定义了可以生成迭代器的对象。它们的共同目的是为了在保持代码简洁的同时,有效地处理数据集合,尤其是在数据量非常大或者是无限的情况下。 理解并掌握这些概念对于编写高效和可读性高的 Python 代码非常重要。每个概念都在数据处理和控制流的抽象中扮演着关键角色,并广泛应用于数据分析领域、系统操作领域和网络编程等领域。

2.0 装饰器(Decorator):

装饰器是Python中的一种高级编程特性,**它允许你在不修改原始函数代码的情况下,动态地增强或修改函数的行为。**装饰器通常用于代码重用、添加功能、修改函数的输入/输出等方面,它是Python函数式编程的一部分,非常强大和灵活。

  1. 函数装饰器

    • 装饰器本质上是一个Python函数,它接受一个函数作为参数,并返回一个新的函数。
    • 装饰器函数通常在函数定义之前使用@符号来装饰目标函数。
    • 装饰器的主要作用是在不修改原函数代码的情况下,为函数添加额外的功能或修改其行为。
  2. 装饰器示例
    下面是一个简单的装饰器示例,它用于测量函数的执行时间:

    import time
    
    
    def timing_decorator(func):
        def wrapper(*args, **kwargs):
            start_time = time.time()
            result = func(*args, **kwargs)
            end_time = time.time()
            print(f"{func.__name__} took {end_time - start_time} seconds to execute.")
            return result
    
        return wrapper
    
    
    @timing_decorator
    def my_function():
        # Some time-consuming task
        time.sleep(2)
    
    
    my_function()
    
    

    在这个示例中,timing_decorator装饰器测量了my_function函数的执行时间,而不需要修改my_function的源代码。

  3. 多个装饰器
    你可以为一个函数应用多个装饰器,它们按照从上到下的顺序执行。这允许你将不同的功能组合在一起,以增强函数的行为。

    @decorator1
    @decorator2
    def my_function():
        # ...
    
    # 等效于
    my_function = decorator1(decorator2(my_function))
    
  4. 内置装饰器
    Python提供了一些内置装饰器,如@staticmethod@classmethod,用于定义静态方法和类方法。这些装饰器可以用于类中的方法,以提供不同类型的方法调用。

    class MyClass:
        def __init__(self, value):
            self.value = value
    
        @staticmethod
        def static_method():
            print("This is a static method")
    
        @classmethod
        def class_method(cls):
            print("This is a class method")
    
    
    obj = MyClass(42)
    obj.static_method()
    obj.class_method()
    
    
  5. 自定义装饰器
    你可以自己编写装饰器函数,以满足特定需求。通常,自定义装饰器需要接受函数作为参数,并返回一个包装函数。装饰器函数可以在包装函数的前后执行自定义逻辑。

装饰器是Python中强大而灵活的工具,它们用于增强函数的功能、提供代码重用和简化代码结构。常见的装饰器包括日志记录、性能分析、权限验证、缓存等。理解和熟练使用装饰器是成为高级Python开发人员的关键一步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/257042.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

字节开源的netPoll多路复用器源码解析

字节开源的netPoll多路复用器源码解析 引言NetPollepoll API原生网络库实现netpoll 设计思路netpoll 对比 go net数据结构 源码解析多路复用池初始化Epoll相关API可读事件处理server启动accept 事件客户端连接初始化客户端连接建立 可读事件等待读取数据 可写事件处理客户端启动…

ADC Buffer数据格式和readDCA1000.m

参考文献 mmwave_sensor_raw_data_capture_using_dca1000_v02Mmwave Radar Device ADC Raw Data CaptureAWR18xx,16xx,14xx,68xx Technical Reference ManualADC Buffer中数据格式有两种,分别是: Interleaved data format (supported only in the 14xx) 按照采样的点数存放,…

深度卷积神经网络的剪枝方法

深度学习领域的一种剪枝技术是“网络瘦身(Network Slimming)”,这是一种旨在深度卷积神经网络(CNN)中实现通道级稀疏性的简单方案。该技术利用批量归一化中的放缩层来有效识别并修剪网络中不重要的通道。本文将详细介绍…

【Linux】进程状态、进程优先级和进程切换

> 作者简介:დ旧言~,目前大二,现在学习Java,c,c,Python等 > 座右铭:松树千年终是朽,槿花一日自为荣。 > 目标:了解冯诺依曼体系结构与操作系统,掌握…

C : DS二叉排序树之删除(详细思路解答)

Description 给出一个数据序列,建立二叉排序树,并实现删除功能 对二叉排序树进行中序遍历,可以得到有序的数据序列 Input 第一行输入t,表示有t个数据序列 第二行输入n,表示首个序列包含n个数据 第三行输入n个数据…

机器学习:增强式学习Reinforcement learning

收集有标签数据比较困难的时候同时也不知道什么答案是比较好的时候可以考虑使用强化学习通过互动,机器可以自己知道什么结果是好的,什么结果是坏的 Outline 什么是RL Action就是一个functionEnvironment就是告诉这个Action是好的还是坏的 例子 Space i…

01-从JDK源码级别彻底剖析JVM类加载机制

文章目录 类加载运行全过程类加载器和双亲委派机制类加载器初始化过程双亲委派机制为什么要设计双亲委派机制?全盘负责委托机制自定义类加载器 打破双亲委派机制Tomcat打破双亲委派机制Tomcat自定义加载器详解模拟实现Tomcat的JasperLoader热加载 补充:H…

二进制枚举算法

二进制 : 也就是只有0和1的进制表示 ; 二进制枚举算法 一个二进制数 x 可以表示 S 的一个子集,某个二进制位i上为0表示没有选i元素,为1表示选了该元素放入子集,比如13为1101就表示选了0,2,3号元素;对于一个长度为N的序列(也就是包含N个元素)有2^N个子…

建筑模板怎么选?

在建筑领域,选择合适的模板材料对于确保工程质量、提高施工效率和控制成本至关重要。目前,常见的建筑模板主要有钢模板、塑料模板和木模板三种类型,每种都有其独特的优势和局限性。本文将对这些模板类型进行分析,并特别推荐广西生…

沉浸式数字文旅黑科技!用AI数字人升级景区体验

这年头文旅界也太卷了! 在国家文化数字化战略的深入实施下,各地方文旅纷纷打造新型消费场景,以数字文旅提升消费产品的互动性和社交性,增强用户沉浸式体验。 其中,数字人乘着AI大语言模型的东风,被文旅品牌…

【数据结构】使用循环链表结构实现约瑟夫环问题

目录 1.循环链表的定义 2.约瑟夫环问题 3.创建循环链表 4.删除节点操作 5.打印所有节点 6.实现约瑟夫环问题的完整程序代码 🌈嗨!我是Filotimo__🌈。很高兴与大家相识,希望我的博客能对你有所帮助。 💡本文由Filotimo_…

OpenAI 增强安全团队并赋予董事会对危险人工智能的否决权

OpenAI 在扩展其内部安全流程方面的举措以应对有害 AI 的威胁,OpenAI高层推出了一份更新的“准备框架”。OpenAI 的目标是识别、分析和决定如何应对他们正在开发的模型中的“灾难性”风险。他们通过对模型的四个风险类别进行评估,并根据风险级别制定相应…

在Android手机设置中启动ESIM

eSIM测试配置文件仅用于实验室测试。 1.到“设置”->“网络和互联网”->“SIM”,确认没有eSIM配置文件。 2.启动拨号程序,然后按短代码****#3746878#**#*(****#ESIMTEST#**#**) 有一个弹出通知“eSIM测试模式已启用” 3.返…

【JavaScript设计模式】Singleton Pattern

单例是可以被实例化一次的类,并且可以被全局访问。这个实例可以在整个应用程序中共享,这使得singleton非常适合管理应用程序中的全局状态。 首先,让我们看看使用ES2015类的单例是什么样子的。在这个例子中,我们将构建一个Counter…

ASP.NET MVC+EntityFramework图片头像上传

1&#xff0c;先展示一下整体的效果 2&#xff0c;接下来展示用户添加以及上传头像代码、添加用户界面 前端代码如下&#xff1a; <div class"form-group">Html.LabelFor(model > model.img, "头像&#xff1a;", htmlAttributes: new { class &…

【Linux】ip命令使用

ip命令 用于管理与配置网络接口和路由表。 ip命令的安装 ip 命令来自 iproute2 软件包&#xff0c;在 CentOS 7 中默认已安装。 yum install -y iproute 语法 ip [ OPTIONS ] OBJECT { COMMAND | help }ip [ -force ] -batch filename选项及作用 执行令 &#xff1a; ip …

计算机组成原理第4章-(存储器)【上】

存储器分类 对于计算机中存储器的分类&#xff0c;方法有很多&#xff0c;不同的方法之间是独立的&#xff0c;为此我们简单讲述两种分类 方法&#xff1a;“存取方式分类”、“在计算机中作用分类”。 -->按存取方式分类 按存取方式分类可以将存储器分为&#xff1a;“…

水经微图Web新版发布

水经微图Web新版已经上线&#xff0c;在该版本中主要新增了态势箭头标绘、文本要素标注和显示网页气泡等功能。 在本文中&#xff0c;我们将为大家分享新增的功能项&#xff0c;以及原有功能作的一些优化等。 当前版本 当前版本号为&#xff1a;1.4.0-beta 如果你发现该版…

JavaSE 泛型

目录 1 泛型类的定义1.1 为什么需要泛型1.2 泛型的概念1.3 泛型的分类 2 泛型类2.1 泛型类的定义2.2 泛型类的例子2.3 泛型类的实例化2.3.1 实例化语法2.3.2 裸类型(Raw Type) 2.4 泛型类的定义-类型边界2.5 泛型类的使用-通配符(Wildcards)2.5.1 基本概念2.5.2 通配符-上界2.5…

14、Kafka 请求是怎么被处理的

Kafka 请求是怎么被处理的 1、处理请求的 2 种常见方案1.1、顺序处理请求1.2、每个请求使用单独线程处理 2、Kafka 是如何处理请求的&#xff1f;3、控制类请求和数据类请求分离 无论是 Kafka 客户端还是 Broker 端&#xff0c;它们之间的交互都是通过 “请求 / 响应” 的方式完…
最新文章