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

这篇具有很好参考价值的文章主要介绍了(三十三)补充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开发人员的关键一步。文章来源地址https://www.toymoban.com/news/detail-763463.html

到了这里,关于(三十三)补充Python经典面试题(吸收高级编程特性)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【随笔】Git 高级篇 -- 推送主分支 git rebase & git fetch(三十三)

    💌 所属专栏:【Git】 😀 作  者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! 💖 欢迎大家:这里是CSDN,我总结知识的地方,喜欢的话请三连,有问题请私信 😘 😘 😘 您的点赞、关注、收藏、评论,是对我最大

    2024年04月23日
    浏览(40)
  • 每日三个JAVA经典面试题(四十三)

    在大数据环境下优化Java性能涉及多个方面,包括调整JVM设置、代码优化和选择合适的工具和框架。以下是一些具体的优化建议: 调整JVM参数 : 增加堆内存 :通过调整 -Xms (堆起始大小)和 -Xmx (堆最大大小)参数,为Java应用程序提供足够的内存空间,以减少垃圾回收的频

    2024年04月26日
    浏览(33)
  • Python工具箱系列(三十三)

    Timescaledb 在物联网时代,出现了大量以时间为中心海量产生的传感器数据,称为时序数据。这类数据的特点是: 数据记录总有一个时间戳。 数据几乎总是追加,不更新也不删除。 大量使用近期的数据。很少更新或者回填时间间隔的缺失数据。 与时间间隔频率关系不大。但累

    2024年02月06日
    浏览(50)
  • 【跟小嘉学 Rust 编程】三十三、Rust的Web开发框架之一: Actix-Web的基础

    【跟小嘉学 Rust 编程】一、Rust 编程基础 【跟小嘉学 Rust 编程】二、Rust 包管理工具使用 【跟小嘉学 Rust 编程】三、Rust 的基本程序概念 【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念 【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据 【跟小嘉学 Rust 编程】六、枚举

    2024年02月04日
    浏览(43)
  • 突破编程_C++_面试(高级特性(1))

    线程( Thread )是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 线程是独立调度和分派的基本单位,同一进程中的多条

    2024年02月20日
    浏览(30)
  • 一个月学通Python(三十):Python并发编程(上)

    结合自身经验和内部资料总结的Python教程,每天3-5章,最短1个月就能全方位的完成Python的学习并进行实战开发,学完了定能成为大佬!加油吧!卷起来! 全部文章请访问专栏:《Python全栈教程(0基础)》 再推荐一下最近热更的:《大厂测试高频面试题详解》 该专栏对近年

    2024年02月14日
    浏览(30)
  • python:并发编程(二十三)

    本文将和大家一起探讨python并发编程的实际项目:win图形界面应用 (篇五,共八篇) ,系列文章将会从零开始构建项目,并逐渐完善项目,最终将项目打造成适用于高并发场景的应用。 本文为python并发编程的第二十三篇,上一篇文章地址如下: python:并发编程(二十二)

    2024年02月11日
    浏览(34)
  • python:并发编程(十三)

    本文将和大家一起探讨python提供高级接口(进程池、线程池)的并发编程,使用内置基本库concurrent.futures来实现并发,先通过官方来简单使用这个模块。先打好基础,能够有个基本的用法与认知,后续文章,我们再进行详细使用。为什么说是concurrent.futures,而不是concurrent呢?

    2024年02月09日
    浏览(30)
  • 第三十九天 Java基础学习(三十三)

    一、Servlet Java类。由Servlet容器(Tomcat)进行编译-.class -运行 产生响应结果返回给客户端浏览器。 生命周期:(方法调用流程) init:初始化方法。在第一次访问servlet时被调用一次。 service:完成servlet所做功能。每次访问servlet时都会被调用。 doGet0:只有get请求。才能访问。 doPos

    2024年02月15日
    浏览(35)
  • 第十三单元 补充知识

    泛指某种类型。 1、使用参数形式定义 2、使用时传入具体类型 3、编译时检查类型安全 4、逻辑上是多个不同类型 泛型与非泛型之间的区别 性能高:可以避免装箱和拆箱操作 类型安全 :在进行类型转换的时候不会抛出异常 代码重用:定义一次,用许多种不同类型实例化 代

    2024年02月06日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包