深度学习面试题专栏14-python
01 python标准数据类型
02 双下划线和单下划线的区别
03 自省解释一下
04 迭代器和生成器的区别
05 装饰器怎么用?装饰器解释下,基本要求是什么?
06 深拷贝和浅拷贝的区别
07 多线程和多进程的区别?
08 is ==
09 +和join的区别
10 字典和json字符串相互转化方法
01 python标准数据类型
数字 (Numbers)
整型 (Integers): 如
1
,-10
,10000
。浮点型 (Floats): 如
3.14
,-0.001
,2.71
。复数 (Complex Numbers): 如
3+4j
,-1.2+0.5j
。字符串 (Strings)
示例:
'hello'
,"world"
,'''This is a multi-line string.'''
列表 (Lists)
是一个有序集合,可以修改,并且可以包含任意类型的元素。
示例:
[1, 2, 3]
,['apple', 'banana', 'cherry']
,[1, 'apple', 3.14]
元组 (Tuples)
与列表类似,但它是不可变的,这意味着你不能增加、删除或修改元组中的元素。
示例:
(1, 2, 3)
,('apple', 'banana', 'cherry')
集合 (Sets)
是一个无序的,不重复的元素集。
示例:
{1, 2, 3}
,{'apple', 'banana', 'cherry'}
字典 (Dictionaries)
由键值对组成,键必须是唯一的。
示例:
{'name': 'John', 'age': 25}
,{1: 'apple', 2: 'banana'}
布尔 (Booleans)
表示真或假的值,主要用于条件测试。
只有两个布尔值:
True
和False
。None 类型
这是一个特殊的常量,表示空值或者无值。
通常用于函数中返回一个空的结果或为变量指定一个初始的“空”状态。
02 双下划线和单下划线的区别
单下划线前缀
_variable
:这是一个约定,用于指示该变量是“私有的”。这意味着它只应在该类或模块内部使用,不应该从外部访问。然而,这仅仅是一个约定,实际上并不会阻止你从外部访问它。
例如:
_private_var
单下划线后缀
variable_
:当变量名与 Python 关键字冲突时,通常使用这种命名方式。
例如,如果你想命名一个变量为
class
(这是一个 Python 关键字),你可以命名它为class_
。双下划线前缀
__variable
:当一个变量以两个下划线为前缀但不以两个下划线为后缀时,它会在内部名称改变(Name Mangling)。这是为了确保它在子类中不会被意外覆盖。
这意味着,如果你有一个名为
__private_var
的变量在一个名为MyClass
的类中,那么实际上它可以通过_MyClass__private_var
来访问。这种特性通常用于确保类的私有属性和方法不被子类意外覆盖或访问。
双下划线前缀和后缀
__variable__
:这样的变量通常是 Python 的特殊方法或属性,例如
__init__
或__len__
。这些方法有特定的意义和用途。通常不建议在自定义的属性或方法中使用这种命名约定,除非你确实想定义自己的特殊方法。
03 自省解释一下
在编程中,自省(或反射)是指在运行时检查对象的类型和内部信息的能力。Python 作为一门动态语言,为开发者提供了强大的自省功能。通过自省,开发者可以在运行时知道对象是什么、有哪些属性和方法。
Python 提供了多种自省工具和函数,以下是一些常见的自省工具:
type(): 返回对象的类型。
x = [1, 2, 3]
print(type(x)) # <class 'list'>
dir(): 返回对象的属性列表。对于模块,它返回模块的属性、函数和类名。
x = [1, 2, 3]
print(dir(x))
id(): 返回对象的唯一标识符,这通常是对象在内存中的地址。
x = [1, 2, 3]
print(id(x))
getattr(): 获取对象的属性值。
class Example:
def __init__(self):
self.data = "example data"
e = Example()
print(getattr(e, 'data')) # example data
hasattr(): 检查对象是否有给定的属性。
class Example:
def __init__(self):
self.data = "example data"
e = Example()
print(hasattr(e, 'data')) # True
.........
04 迭代器和生成器的区别
定义:
迭代器 (Iterator): 迭代器是一个实现了
__iter__()
和__next__()
方法的对象。__iter__()
返回迭代器自身,而__next__()
返回序列中的下一个值。生成器 (Generator): 生成器是一种使用
yield
关键字的函数,该函数在被调用时返回一个生成器迭代器。生成器可以产生一个值序列,而不需要在内存中存储整个序列。实现:
迭代器通常需要更多的工作来创建,因为你需要实现
__iter__()
和__next__()
方法。生成器使用更简洁的语法,只需一个函数和
yield
关键字。内存使用:
生成器是更内存友好的,因为它只在每次迭代时产生一个值,而不是存储整个序列。
迭代器可能根据其实现而变化,但通常也是按需生成值。
使用场景:
如果你需要一个对象来支持自定义的迭代操作,并可能包含其他功能,那么迭代器可能更合适。
如果你只需要一个简单的工具来迭代一系列的值,生成器是一个更简洁、更直观的选择。
生命周期:
生成器函数的执行可以在任何步骤中被暂停,并且可以从停止的地方继续执行,直到生成器被完全耗尽。
迭代器通常维持其状态,直到迭代完成。
创建方式:
使用
yield
关键字的函数。使用生成器表达式,它是列表推导式的简化版本,使用圆括号而不是方括号。
迭代器:使用类来实现
__iter__()
和__next__()
。生成器:可以通过两种方式创建:
05 装饰器怎么用?装饰器解释下,基本要求是什么?
装饰器是 Python 中非常强大的工具,它允许开发者在不修改现有函数或方法代码的情况下,增加或修改函数的功能。这是一种遵循开闭原则的编程技巧:对修改关闭、对扩展开放。
装饰器的基本要求:
装饰器本身是一个函数。
装饰器函数的主要参数是你想要“装饰”的函数或方法。
装饰器函数返回一个新的函数,这个新函数通常是一个在原始函数上执行了一些操作的包装函数。
使用
@
符号将装饰器应用到目标函数上。
使用装饰器的好处:
代码重用:可以将通用功能抽象到装饰器中,从而避免代码重复。
代码组织:能够将业务逻辑与其他关注点(如日志、计时、访问控制等)分离,提高代码的可读性和可维护性。
灵活性:可以根据需要为函数动态地添加或删除功能。
06 深拷贝和浅拷贝的区别
深拷贝与浅拷贝是在复制复合对象(如列表、字典等)时常会遇到的概念。它们的主要区别在于如何复制对象内部的子对象。以下是这两种拷贝方法之间的主要区别:
浅拷贝 (Shallow Copy):
当执行浅拷贝时,会创建一个新的复合对象,但不会为原始对象内部的子对象创建新的对象。而是将新复合对象中的引用指向原始对象的子对象。
如果你修改新复合对象中的子对象,原始对象中的相应子对象也会被修改,反之亦然。
在 Python 中,
copy
模块的copy()
函数可以实现浅拷贝。深拷贝 (Deep Copy):
当执行深拷贝时,除了复制原始对象,还会为原始对象内部的所有子对象创建新的对象。这样,新复合对象与原始对象在内存中是完全独立的。
修改新复合对象或其子对象不会影响到原始对象及其子对象,反之亦然。
在 Python 中,
copy
模块的deepcopy()
函数可以实现深拷贝。
深拷贝与浅拷贝是在复制复合对象(如列表、字典等)时常会遇到的概念。它们的主要区别在于如何复制对象内部的子对象。以下是这两种拷贝方法之间的主要区别:
浅拷贝 (Shallow Copy):
当执行浅拷贝时,会创建一个新的复合对象,但不会为原始对象内部的子对象创建新的对象。而是将新复合对象中的引用指向原始对象的子对象。
如果你修改新复合对象中的子对象,原始对象中的相应子对象也会被修改,反之亦然。
在 Python 中,
copy
模块的copy()
函数可以实现浅拷贝。深拷贝 (Deep Copy):
当执行深拷贝时,除了复制原始对象,还会为原始对象内部的所有子对象创建新的对象。这样,新复合对象与原始对象在内存中是完全独立的。
修改新复合对象或其子对象不会影响到原始对象及其子对象,反之亦然。
在 Python 中,
copy
模块的deepcopy()
函数可以实现深拷贝。
浅拷贝只复制对象的最外层,内部的子对象仍然是引用。
深拷贝会复制对象及其所有嵌套的子对象。
07 多线程和多进程的区别?
多线程(Multithreading)和多进程(Multiprocessing)都是并发执行技术,但它们有一些基本的区别。以下是多线程和多进程之间的主要区别:
基本单位:
线程:线程是程序中的执行单元,通常被称为轻量级的进程。一个进程可以拥有一个或多个线程,这些线程共享进程的内存空间、文件句柄等资源。
进程:进程是操作系统的基本执行单元,是一个独立的运行实体。每个进程都有自己的内存空间、资源和代码段。
资源开销:
线程:由于线程共享相同的内存空间,创建、维护和上下文切换通常比进程要快、开销更小。
进程:进程有自己独立的内存空间,所以它们的创建、维护和上下文切换需要更多的开销。
隔离性:
线程:由于线程在同一进程内共享内存和资源,一个线程的崩溃或错误可能会影响到其他线程。
进程:进程之间是彼此隔离的,一个进程的崩溃或错误不会影响其他进程。
通信:
线程:由于线程共享内存,它们之间的通信通常更为简单,可以使用共享变量、数组等进行通信。
进程:进程间通信(IPC)需要特定的机制,如管道、消息队列、套接字等。
同步:
线程:线程同步往往比进程同步更为复杂,因为线程之间的资源共享可能导致数据竞态条件,需要使用锁、信号量等机制来同步。
进程:由于进程彼此独立,数据竞态条件较少,但仍然需要同步机制,特别是在IPC中。
全局变量:
线程:线程共享进程的全局变量。
进程:每个进程都有其自己的全局变量。
08 is ==
is:
is
操作符用于比较两个对象的身份,即它们是否是同一个对象或是否位于同一个内存地址。a is b
为真,意味着a
和b
指向同一对象。==:
==
操作符用于比较两个对象的值是否相等。对于大多数内置类型,这意味着它们的内容是否相等。
类可以通过定义
__eq__()
方法来自定义==
的行为。
09 +和join的区别
使用 + 运算符:
当使用
+
运算符连接字符串时,会创建一个新的字符串对象。如果在循环中多次使用
+
连接字符串,这会导致大量的临时字符串对象被创建和丢弃,从而降低性能。
result = str1 + str2 + str3
使用 str.join() 方法:
str.join()
方法是将一个字符串列表连接成一个单一的字符串。相比于
+
运算符,使用str.join()
在多个字符串连接时更为高效,因为它一次性在一个操作中创建一个新的字符串。
result = "".join([str1, str2, str3])
性能对比:
考虑以下示例,其中我们想要连接大量的小字符串:
# 使用 + 运算符
result = ""
for s in list_of_strings:
result += s
# 使用 join 方法
result = "".join(list_of_strings)
在第一个示例中,每次迭代都会创建一个新的字符串对象。但在第二个示例中,只会创建一个字符串对象,因此使用 str.join()
方法通常更为高效。
当连接大量字符串时,尤其是在循环中,使用
str.join()
通常是一个更好的选择,因为它更为高效。当只连接少数几个字符串时,使用
+
运算符可能更为直观。
10 字典和json字符串相互转化方法
字典转为 JSON 字符串:
使用 json.dumps()
方法可以将字典转为 JSON 字符串。
SON 字符串转为字典:
使用 json.loads()
方法可以将 JSON 字符串转为字典。