本文给大家带来的百面算法工程师是深度学习python解释器面试总结,文章内总结了常见的提问问题,旨在为广大学子模拟出更贴合实际的面试问答场景。在这篇文章中,我们还将介绍一些常见的python用法,并提供参考的回答及其理论基础,以帮助求职者更好地准备面试。通过对这些问题的理解和回答,求职者可以展现出自己的算法语法领域的专业知识、解决问题的能力以及对实际应用场景的理解。同时,这也是为了帮助求职者更好地应对深度学习目标检测岗位的面试挑战,提升面试的成功率和竞争力。
目录
2.1 Python的装饰器的作用是什么,为什么要这么做
2.2 什么是解释性语言,什么是编译性语言
2.3 python程序的执行过程
2.4 python的作用域
2.5 python的数据结构
2.6 python多线程
2.7 python多进程
2.8 Python互斥锁与死锁
2.9 Python的深拷贝与浅拷贝
2.10 hasattr() getattr() setattr() 函数使用详解
2.11 __init__.py 文件的作用以及意义
2.12 点积和矩阵相乘的区别
欢迎大家订阅我的专栏一起学习共同进步
祝大家早日拿到offer! let's go
🚀🚀🚀http://t.csdnimg.cn/dfcH3🚀🚀🚀
2.1 Python的装饰器的作用是什么,为什么要这么做
Python中的装饰器是一种高级功能,它允许你在不改变函数代码的情况下,动态地修改函数的行为。装饰器本质上是一个函数,它接受一个函数作为参数,并返回一个新的函数。这种技术常用于在函数执行前后添加额外的功能,例如日志记录、性能监控、权限检查等。
装饰器的主要作用有几个方面:
- 代码复用和简化:装饰器可以将与函数相关的功能从函数中抽取出来,使得代码更简洁、易读,并且可以在多个函数之间共享相同的功能。
- 增强函数功能:通过装饰器,你可以在不修改原始函数的情况下,为函数添加新的功能,例如日志记录、性能监控、缓存等,从而增强函数的功能和灵活性。
- 分离关注点:装饰器可以将关注点分离开来,使得代码更易于维护和理解。原始函数只需要关注核心逻辑,而其他非核心功能则可以由装饰器来处理。
- 可重用性:装饰器本身是可重用的,它可以应用于多个函数上,从而避免了重复编写相同功能的代码。
为什么要使用装饰器呢?主要原因在于它可以提高代码的可维护性、可扩展性和可读性。通过将非核心功能与原始函数分离,使得代码结构更清晰,易于理解和修改。此外,装饰器还能够促进代码复用,提高开发效率。
静态装饰器可以直接使用函数名进行调用,而不需要通过类名因为静态装饰器本身就是函数
# 类装饰器,添加额外的功能
class LogCalls:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
print(f"Calling function {self.func.__name__} with args {args} and kwargs {kwargs}")
return self.func(*args, **kwargs)
@LogCalls
def multiply(x, y):
return x * y
result = multiply(4, 6)
print("Result:", result)
2.2 什么是解释性语言,什么是编译性语言
解释性语言和编译性语言是两种不同的编程语言类型,它们的主要区别在于代码执行的方式。
- 解释性语言:
- 在解释性语言中,源代码通过解释器一行一行地被解释执行。解释器读取源代码并直接将其转换为机器代码或者其他形式的中间代码,然后立即执行。因此,解释性语言的执行是逐行进行的。
- 解释性语言的典型代表包括Python、JavaScript和Ruby等。
- 编译性语言:
- 在编译性语言中,源代码首先需要通过编译器被转换成机器码或者中间代码,然后再由计算机执行。编译过程将源代码转换成目标代码,这个目标代码可以在不同的计算机上运行,而不需要重新编译。编译性语言的执行是在编译完成后进行的。
- 编译性语言的典型代表包括C、C++、Java等。
总的来说,解释性语言的优势在于其灵活性和跨平台性,因为它们不需要事先的编译步骤,而编译性语言通常执行速度更快,但需要额外的编译步骤。
2.3 python程序的执行过程
Python 是一种解释性语言,因此 Python 程序的运行过程如下:
- 编写代码:
开发者编写 Python 代码,通常保存为以 .py 结尾的文件。 - 解释器执行:
当要运行 Python 代码时,通过命令行或集成开发环境(IDE)等工具调用 Python 解释器,将代码传递给解释器执行。解释器按照代码的顺序逐行解释执行,将代码转换为计算机可以理解的指令并执行。 - 词法分析和语法分析:
解释器首先进行词法分析和语法分析,将源代码分解成标记(tokens)和语法树。 - 字节码生成:
解释器将源代码转换为字节码(bytecode),这是一种中间形式的代码,不是直接的机器码。 - 解释执行字节码:
解释器逐行执行字节码指令,将其翻译成机器码并执行。
整个过程中,Python 解释器负责将代码转换为可执行的指令序列,并负责管理内存、处理异常等运行时任务。这种解释性语言的方式使得 Python 具有跨平台性和灵活性,但也可能导致一些性能上的损失。
2.4 python的作用域
在 Python 中,作用域(Scope)指的是变量和函数的可访问性和可见性范围。Python 中的作用域可以分为以下几种:
- 全局作用域(Global Scope):
在 Python 程序的最外层定义的变量和函数处于全局作用域中,它们可以在代码中的任何地方被访问。全局作用域在整个程序执行过程中都是有效的。 - 局部作用域(Local Scope):
在函数内部定义的变量和函数处于局部作用域中,它们只能在函数内部被访问。当函数执行结束后,局部作用域中的变量通常会被销毁。 - 嵌套作用域(Enclosing Scope):
如果在一个函数内部定义了另一个函数,内部函数就处于嵌套作用域中。内部函数可以访问外部函数中定义的变量,但外部函数不能直接访问内部函数的变量。 - 内置作用域(Built-in Scope):
Python 内置的函数和对象的作用域属于内置作用域,这些函数和对象可以在任何地方被访问。例如,print() 和 len() 等函数就是内置作用域中的内容。
Python 使用 LEGB【每个域的首字母】 规则来确定变量的作用域:
- L(Local):局部作用域
- E(Enclosing):嵌套作用域
- G(Global):全局作用域
- B(Built-in):内置作用域
按照 LEGB 规则,Python 在查找变量时,首先在局部作用域中查找,然后逐级向上查找直到找到为止,如果都没有找到,则会抛出 NameError 异常。
2.5 python的数据结构
数据结构 (Data Structure) | 可变性 (Mutability) | |
列表 (List) | 列表是一种有序的数据集合,可以包含不同类型的元素,并且可以动态地修改。列表使用方括号 [ ] 定义,元素之间使用逗号 , 分隔。 | 可变 (Mutable) |
元组 (Tuple) | 元组类似于列表,但是元组是不可变的,一旦创建就不能被修改。元组使用圆括号 ( ) 定义,元素之间使用逗号 , 分隔。 | 不可变 (Immutable) |
集合 (Set) | 集合是一种无序且不重复的数据集合。集合中的元素没有索引,不支持通过索引访问,但可以进行集合运算,如并集、交集和差集等。集合使用花括号 { } 或 set() 函数定义。 | 可变 (Mutable) |
字典 (Dictionary) | 字典是一种无序的键值对集合,每个键值对用冒号 : 分隔,键值对之间使用逗号 , 分隔。字典中的键必须是唯一的,但值可以重复。字典使用花括号 { } 定义。 | 可变 (Mutable) |
字符串 (String) | 字符串是由字符组成的有序序列,Python 中的字符串是不可变的,不能被修改。字符串可以使用单引号 ' '、双引号 " " 或三引号 ''' ''' 定义。 | 不可变 (Immutable) |
2.6 python多线程
在 Python 中,多线程是一种同时执行多个线程的机制,每个线程都可以执行不同的任务。Python 提供了多种方式来实现多线程编程,其中最常用的有以下两种:
- 使用 threading 模块:
Python 内置了 threading 模块,可以使用该模块来创建和管理线程。通过创建 Thread 类的实例来定义新线程,然后调用 start() 方法启动线程的执行。线程可以是执行函数或者可调用对象。虽然 Python 的多线程可以在同一个进程中执行并发任务,但由于 GIL(全局解释器锁)的存在,多线程并不能实现真正的并行执行,适用于 I/O 密集型任务,但不适用于 CPU 密集型任务。 - 使用 concurrent.futures 模块:
concurrent.futures 模块提供了 ThreadPoolExecutor 和 ProcessPoolExecutor 两个类,分别用于创建线程池和进程池,以实现多线程和多进程编程。与 threading 模块相比,concurrent.futures 模块提供了更高级别的接口,并且可以通过使用进程池来规避 GIL 的限制,实现真正的并行执行。
无论是使用 threading 模块还是 concurrent.futures 模块,多线程编程都需要注意线程安全性(如使用锁、信号量等机制保护共享资源)、避免死锁(如避免资源竞争)、线程间通信等问题。
Python 的多线程使用的是操作系统的原生线程(例如在 Windows 上是使用的是 Windows 线程,而在 Unix/Linux 上是使用的 POSIX 线程),这些线程是由操作系统调度的。但是,Python 中的多线程受到 GIL(全局解释器锁)的限制。
GIL 是 Python 解释器中的一个全局锁,它确保同一时刻只有一个线程在解释器中执行 Python 字节码。因此,尽管 Python 的多线程可以在多个 CPU 上并行执行 I/O 密集型任务,但在 CPU 密集型任务上并不能真正实现并行执行,因为同一时刻只有一个线程能够执行 Python 代码。
如果想要充分利用多核 CPU,可以考虑使用多进程而不是多线程。Python 的多进程可以使用 multiprocessing 模块来实现,并且每个进程都有自己独立的 GIL,因此可以在多个 CPU 上并行执行 CPU 密集型任务。
总的来说,Python 的多线程适合于 I/O 密集型任务,但对于 CPU 密集型任务,应该考虑使用多进程来充分利用多核 CPU。
2.7 python多进程
在 Python 中,多进程是一种同时执行多个进程的机制,每个进程都有自己独立的内存空间和系统资源。Python 提供了多种方式来实现多进程编程,其中最常用的有以下两种:
- 使用 multiprocessing 模块:
Python 内置了 multiprocessing 模块,可以使用该模块来创建和管理进程。通过创建 Process 类的实例来定义新进程,然后调用 start() 方法启动进程的执行。与多线程编程类似,进程可以是执行函数或者可调用对象。multiprocessing 模块可以充分利用多核 CPU,实现真正的并行执行,适用于 CPU 密集型任务。
multiprocessing import Process
def worker():
print("Worker process")
if __name__ == "__main__":
p = Process(target=worker)
p.start()
p.join()
- 使用 concurrent.futures 模块:
除了用于多线程编程,concurrent.futures 模块还可以用于多进程编程。通过使用 ProcessPoolExecutor 类来创建进程池,以实现多进程并行执行任务。与 threading 模块类似,concurrent.futures 模块提供了更高级别的接口来管理进程池,简化了多进程编程的复杂性。
concurrent.futures import ProcessPoolExecutor
def worker():
return "Worker process"
if __name__ == "__main__":
with ProcessPoolExecutor() as executor:
future = executor.submit(worker)
print(future.result())
多进程编程需要注意的问题包括进程间通信(如使用队列、管道等)、进程池管理(如限制最大进程数)、避免资源竞争等。
2.8 Python互斥锁与死锁
互斥锁和死锁是多线程编程中常见的两个概念,它们之间存在一定的关系。
- 互斥锁(Mutex):
互斥锁是一种同步机制,用于确保在任意时刻只有一个线程可以访问共享资源。当一个线程获得了互斥锁,其他线程就必须等待该线程释放锁之后才能访问共享资源,从而避免了多个线程同时修改共享资源而导致的数据竞争问题。Python 中的 threading 模块提供了 Lock 类来实现互斥锁。 - 死锁(Deadlock):
死锁是指两个或多个线程互相持有对方所需的资源,并且等待对方释放资源,从而导致所有线程都无法继续执行的情况。死锁通常发生在多个线程同时尝试获取多个互斥锁时,如果不恰当地管理锁的获取顺序,可能会导致死锁的发生。
互斥锁通常用于避免死锁的发生,但在编写多线程程序时,需要特别注意以下几点来避免死锁的发生:
- 避免循环等待:确保线程在获取锁时不会发生循环等待,即所有线程都以相同的顺序获取锁。
- 尽量减少锁的持有时间:获取锁后应尽快释放,以减少其他线程等待锁的时间。
- 使用超时机制:在获取锁时使用超时机制,避免线程长时间等待锁而无法执行。
虽然互斥锁可以帮助避免死锁的发生,但它并不能完全消除死锁的可能性。因此,在编写多线程程序时,需要仔细考虑并谨慎处理锁的获取和释放,以尽量减少死锁的发生。
2.9 Python的深拷贝与浅拷贝
在 Python 中,深拷贝(deep copy)和浅拷贝(shallow copy)是用于复制对象的两种不同方式,它们有不同的行为和应用场景。
- 浅拷贝(Shallow Copy):
浅拷贝是创建一个新的对象,其中包含原始对象中的所有元素的引用。换句话说,浅拷贝只复制了对象的一层,如果对象中包含了可变对象(如列表或字典),则原始对象和拷贝对象会共享这些可变对象。在 Python 中,可以使用 copy() 方法或 copy 模块的 copy() 函数来实现浅拷贝。
original_list = [1, [2, 3], 4]
shallow_copy = copy.copy(original_list)
# 修改原始对象中的可变对象
original_list[1][0] = 'a'
print(original_list) # Output: [1, ['a', 3], 4]
print(shallow_copy) # Output: [1, ['a', 3], 4]
- 深拷贝(Deep Copy):
深拷贝是创建一个新的对象,其中包含原始对象中的所有元素的副本,包括所有嵌套对象。换句话说,深拷贝会递归地复制所有嵌套对象,确保原始对象和拷贝对象彼此独立,互不影响。在 Python 中,可以使用 copy() 方法或 copy 模块的 deepcopy() 函数来实现深拷贝。
original_list = [1, [2, 3], 4]
deep_copy = copy.deepcopy(original_list)
# 修改原始对象中的可变对象
original_list[1][0] = 'a'
print(original_list) # Output: [1, ['a', 3], 4]
print(deep_copy) # Output: [1, [2, 3], 4]
总的来说,浅拷贝只复制对象的一层,而深拷贝则会递归地复制所有嵌套对象。因此,在需要完全独立的拷贝时,应使用深拷贝。
2.10 hasattr() getattr() setattr() 函数使用详解
hasattr(), getattr(), 和 setattr() 是 Python 中用于操作对象属性的三个内置函数。它们的主要作用如下:
- hasattr(object, name):
- hasattr() 函数用于检查一个对象是否具有指定的属性(attribute)或者方法(method)。
- 如果对象中存在指定名称的属性或方法,则返回 True,否则返回 False。
- 语法:hasattr(object, name),其中 object 是要检查的对象,name 是要检查的属性或方法名称。
MyClass:
attr = 123
obj = MyClass()
print(hasattr(obj, 'attr')) # Output: True
print(hasattr(obj, 'method')) # Output: False
- getattr(object, name[, default]):
- getattr() 函数用于获取对象中指定名称的属性或方法的值。
- 如果对象中存在指定名称的属性或方法,则返回其值;如果指定名称不存在,并且提供了默认值,则返回默认值;如果未提供默认值,则会引发 AttributeError 异常。
- 语法:getattr(object, name[, default]),其中 object 是要获取属性或方法的对象,name 是要获取的属性或方法名称,default 是可选的默认值。
MyClass:
attr = 123
obj = MyClass()
print(getattr(obj, 'attr')) # Output: 123
print(getattr(obj, 'method', 'default')) # Output: 'default'
- setattr(object, name, value):
- setattr() 函数用于设置对象中指定名称的属性或方法的值。
- 如果对象中存在指定名称的属性或方法,则将其值设置为指定的值;如果指定名称不存在,则会创建新的属性或方法并设置其值。
- 语法:setattr(object, name, value),其中 object 是要设置属性或方法的对象,name 是要设置的属性或方法名称,value 是要设置的值。
MyClass:
pass
obj = MyClass()
setattr(obj, 'attr', 123)
print(obj.attr) # Output: 123
这些函数对于动态地操作对象的属性和方法非常有用,特别是在编写通用代码时,可以根据需要动态地获取、设置或检查对象的属性和方法。
2.11 __init__.py 文件的作用以及意义
__init__.py 文件是 Python 包中一个特殊的文件,用于指示 Python 解释器该目录是一个包,它具有以下几个作用和意义:
- 包标识符:
__init__.py 文件的存在表示该目录是一个 Python 包。在 Python 3.3 之前的版本中,必须存在 __init__.py 文件才能将目录视为包。从 Python 3.3 开始,如果没有 __init__.py 文件,仍然可以将目录视为包,但是 __init__.py 文件仍然被用作包标识符的标准方式。 - 初始化包:
__init__.py 文件可以包含包的初始化代码,例如初始化模块、定义包级别的变量和函数等。当导入包时,Python 解释器会执行 __init__.py 文件中的代码。 - 导入子模块:
在 __init__.py 文件中可以使用 import 语句来导入子模块,从而使得在导入包时同时导入子模块,使得用户可以更方便地访问包中的功能。 - 控制包的导入行为:
__init__.py 文件还可以控制包的导入行为,例如限制导入的内容、导入时执行特定的操作等。通过在 __init__.py 中设置 __all__ 变量,可以指定导入时包外部可见的模块列表。 - 向后兼容性:
尽管从 Python 3.3 开始,不再需要 __init__.py 文件来标识包,但为了向后兼容性和与旧代码的兼容性,建议仍然在包目录中包含 __init__.py 文件。
总的来说,__init__.py 文件是 Python 包中的一个重要组成部分,它用于标识包、初始化包、导入子模块以及控制包的导入行为,对于组织和管理 Python 项目和模块非常有用。
2.12 点积和矩阵相乘的区别
点积(内积)和矩阵相乘是线性代数中两个不同的运算,它们之间有一些区别。
- 点积(内积):
- 点积,也称为内积或数量积,是两个向量之间的运算。对于两个长度相等的向量 a和b,它们的点积定义为:
a⋅b=a1⋅b1+a2⋅b2+⋯+an⋅bn
其中ai和bi 分别表示向量 a和 b 中的第 ( i ) 个元素。 - 点积的结果是一个标量,表示两个向量之间的相似程度。如果点积为零,则表示两个向量正交(垂直);如果点积大于零,则表示两个向量之间的夹角小于 90∘,反之则大于90∘。
- 点积,也称为内积或数量积,是两个向量之间的运算。对于两个长度相等的向量 a和b,它们的点积定义为:
- 矩阵相乘:
- 矩阵相乘是针对两个矩阵进行的运算。对于两个矩阵 ( A ) 和 ( B ),如果 ( A ) 的列数等于 ( B ) 的行数,则它们可以相乘,结果矩阵 ( C ) 的维度为m×n,其中 ( m ) 是 ( A ) 的行数,( n ) 是 ( B ) 的列数。
- 矩阵相乘的定义为:
- 其中 Cij表示结果矩阵 ( C ) 中第 ( i ) 行第 ( j ) 列的元素,( p ) 是 ( A ) 的列数或 ( B ) 的行数。
- 矩阵相乘的结果是一个新的矩阵,表示了两个矩阵之间的线性变换关系。它通常用于描述多个线性变换的组合效果。
总的来说,点积是针对向量的运算,结果是一个标量,表示向量之间的相似程度;而矩阵相乘是针对矩阵的运算,结果是一个新的矩阵,表示了两个矩阵之间的线性变换关系。