Python面试题
2023-03-01 / 可可西里

Python常见面试题分享,涵盖了常见的Python面试八股文

Python语言特点

  1. 易学易用:Python语法简单明了,易于学习和上手,代码可读性强,使得开发效率高。
  2. 面向对象:Python支持面向对象编程(OOP),包括封装、继承、多态等OOP特性,可以更方便地组织代码和抽象问题。
  3. 解释型:Python是解释型语言,不需要编译过程,直接运行源代码,这使得开发和调试变得更加容易
  4. 动态类型:Python是动态类型语言,不需要声明变量类型,可以根据需要动态改变变量类型,使得代码更加灵活。
  5. 跨平台:Python可以在各种操作系统上运行,包括Windows、Linux、Mac OS等,具有很强的跨平台性。
  6. 强大的标准库:Python标准库提供了大量的模块和函数,涵盖了网络编程、GUI编程、多线程编程、正则表达式等各种方面,开发者可以直接使用标准库来完成常见的任务,不需要从零开始编写
  7. 第三方库丰富:Python拥有众多的第三方库和框架,如NumPy、Pandas、Django等,可以快速实现各种功能,提高开发效率
  8. 可扩展性:Python可以通过C/C++扩展模块来提高性能,还可以与其他语言进行混合编程。
  9. 开放源代码:Python是一种开放源代码语言,拥有大量的贡献者和用户社区,可以获得免费的开发工具和技术支持

1. Python的数据类型有哪些

  1. 数字(number):整数、浮点数和复数
  2. 字符串(string):由字符组成的序列
  3. 列表(list):由一组有序的值组成的序列,可修改
  4. 元组(tuple):由一组有序的值组成的序列,不可修改
  5. 集合(set):由一组唯一的、无序的值组成
  6. 字典(dict):由一组键-值对组成的映射表
  7. 布尔值(bool):表示True或False的值
  8. 空值(None):表示没有值的对象,用None表示

2. Python可变与不可变数据类型的区别

  • 可变类型:可以修改其内容的数据类型,包括列表、集合和字典等。修改这些类型的值时,会直接在原始对象上进行修改,而不是创建一个新对象。例如,当向一个列表中添加一个元素时,列表的长度会发生变化,但其身份标识不会改变
  • 不可变类型:一旦创建就不能更改其内容的数据类型,包括整数、浮点数、布尔值、元组和字符串等。如果对这些类型进行修改,将会创建一个新对象。例如,当对一个字符串进行切片或拼接时,会创建一个新的字符串对象

注意:可变类型的修改操作是原地修改,不可变类型的修改操作是创建一个新对象并返回

3. Python列表和元组的区别

  1. 可变性:列表是可变的,可以在原地添加、删除或修改元素,而元组是不可变的,无法在原地进行修改操作。
  2. 语法:列表使用方括号 [] 来表示,元素之间用逗号分隔;元组使用圆括号 () 来表示,元素之间也用逗号分隔。如果元组只包含一个元素,需要在该元素后面添加一个逗号来表示它是一个元组而不是一个普通的值。
  3. 性能:元组相对于列表来说,在创建、遍历和访问元素时具有更高的性能,因为元组的结构是不可变的,因此在创建后不需要再进行修改,不会出现额外的开销
  4. 用途:列表通常用于需要添加、删除或修改元素的场景,如缓存数据、维护计数器、记录用户输入等;而元组通常用于存储一些固定的、不可变的数据,如坐标点、日期、时间等

4. Python列表的基本操作

  1. 创建列表:使用方括号 [] 将一组元素括起来即可创建一个列表,例如:a = [1, 2, 3, 4, 5]
  2. 索引和切片:可以使用索引和切片操作来访问列表中的元素,例如:a[0] 返回列表中的第一个元素,a[1:3] 返回列表中第二个到第四个元素。
  3. 修改元素:可以通过索引来修改列表中的元素,例如:a[0] = 0 将列表中的第一个元素修改为0
  4. 添加元素:可以使用 append() 方法向列表末尾添加一个元素,使用 insert() 方法在指定位置插入一个元素,例如:a.append(6)a.insert(0, 0)
  5. 删除元素:可以使用 del 语句、remove() 方法或 pop() 方法来删除列表中的元素,例如:del a[0] 删除列表中的第一个元素,a.remove(3) 删除列表中值为3的元素,a.pop() 删除并返回列表中的最后一个元素。
  6. 合并列表:可以使用 + 运算符将两个列表合并成一个新列表,例如:a + [6, 7, 8]
  7. 复制列表:可以使用切片或 copy() 方法来复制一个列表,例如:b = a[:]b = a.copy()
  8. 获取列表长度:可以使用 len() 函数来获取列表的长度,例如:len(a) 返回列表 a 中元素的个数

5. Python中的列表推导式

Python中的列表推导式(List Comprehension)是一种简洁而强大的语法,可以快速地创建一个新的列表,语法形式为:

1
[expression for item in iterable if condition]

其中,expression 是一个表达式,用于计算新列表中每个元素的值;item 是 iterable(可迭代对象)中的一个元素;if condition 是一个可选的条件语句,用于过滤 iterable 中的元素。

举个例子,假设我们需要创建一个列表,其中包含从 0 到 9 的所有偶数的平方,可以使用列表推导式来实现:

1
squares = [x**2 for x in range(10) if x % 2 == 0]

上述代码首先使用 range(10) 函数生成一个从 0 到 9 的整数序列,然后通过 if 语句过滤出其中的偶数,最后对每个偶数进行平方运算并添加到新列表中。因此,squares 列表的值为 [0, 4, 16, 36, 64]

除了基本形式外,列表推导式还支持嵌套、多个 for 循环和多个 if 条件语句的组合,可以根据需要进行组合和使用,以满足不同的需求。列表推导式具有简洁、高效和易读的特点

6. Python删除list里的重复元素有几种方法

  1. 使用 set() 函数去重:将列表转换为 set 集合,再将其转换为列表即可,但是这种方法会改变列表元素的顺序

    1
    2
    3
    lst = [1, 2, 3, 3, 4, 4, 5]
    lst = list(set(lst))
    print(lst) # 输出 [1, 2, 3, 4, 5]
  2. 使用列表推导式去重:利用列表推导式,遍历原列表,将不重复的元素添加到新列表中,缺点是需要额外的空间

    1
    2
    3
    4
    lst = [1, 2, 3, 3, 4, 4, 5]
    new_lst = []
    [new_lst.append(i) for i in lst if i not in new_lst]
    print(new_lst) # 输出 [1, 2, 3, 4, 5]
  3. 使用 for 循环去重:利用 for 循环遍历原列表,将不重复的元素添加到新列表中,这种方法比较简单但速度较慢

    1
    2
    3
    4
    5
    6
    lst = [1, 2, 3, 3, 4, 4, 5]
    new_lst = []
    for i in lst:
    if i not in new_lst:
    new_lst.append(i)
    print(new_lst) # 输出 [1, 2, 3, 4, 5]
  4. 使用字典的 fromkeys() 方法去重:利用字典的键不能重复的特性,将列表中的元素作为字典的键,再将字典的键转换为列表即可,但是这种方法也会改变列表元素的顺序

    1
    2
    3
    lst = [1, 2, 3, 3, 4, 4, 5]
    lst = list(dict.fromkeys(lst))
    print(lst) # 输出 [1, 2, 3, 4, 5]

注意:这些方法各有优缺点,可以根据实际情况选择使用,常见的方法有 set() 函数和列表推导式,因为它们简单、直观且效率高

7. Python类型转换

  1. int(x):将 x 转换为一个整数。如果 x 无法转换为整数,则会抛出 ValueError 异常
  2. float(x):将 x 转换为一个浮点数。如果 x 无法转换为浮点数,则会抛出 ValueError 异常。
  3. str(x):将 x 转换为一个字符串。如果 x 无法转换为字符串,则会抛出 TypeError 异常
  4. bool(x):将 x 转换为一个布尔值。如果 x 为假值(如空字符串、0、False),则返回 False,否则返回 True。
  5. list(x):将 x 转换为一个列表。如果 x 不可迭代或没有实现 iter 方法,则会抛出 TypeError 异常
  6. tuple(x):将 x 转换为一个元组。如果 x 不可迭代或没有实现 iter 方法,则会抛出 TypeError 异常
  7. set(x):将 x 转换为一个集合。如果 x 不可迭代或没有实现 iter 方法,则会抛出 TypeError 异常。
  8. dict(x):将 x 转换为一个字典。如果 x 不是映射类型(如字典或实现了 getitem 方法的类),则会抛出 TypeError 异常

注意:类型转换时可能会出现异常,因此在进行类型转换时需要注意错误处理,避免程序崩溃。同时,Python 中还有其他一些类型转换函数,如 complex() 用于将字符串或数字转换为复数、bytes() 用于将字符串或整数转换为字节串等,可以根据需要进行使用

8. Python字典以及基本操作

Python 中的字典是一种无序的可变集合,它包含键和对应的值。字典中的键必须是不可变的类型,例如整数、字符串、元组等,而值可以是任何类型

  1. 创建字典:可以使用花括号 {} 或 dict() 函数来创建一个空字典,或者使用键值对的方式来初始化一个字典

    1
    2
    3
    4
    5
    6
    7
    # 创建空字典
    my_dict = {}
    my_dict = dict()

    # 初始化字典
    my_dict = {'name': 'Alice', 'age': 20}
    my_dict = dict(name='Alice', age=20)
  2. 添加或更新键值对:可以使用赋值运算符或 update() 方法来添加或更新字典中的键值对

    1
    2
    3
    4
    5
    6
    # 添加键值对
    my_dict['gender'] = 'female'

    # 更新键值对
    my_dict['age'] = 21
    my_dict.update({'gender': 'female', 'age': 21})
  3. 删除键值对:可以使用 del 关键字或 pop() 方法来删除字典中的键值对

    1
    2
    3
    # 删除键值对
    del my_dict['gender']
    my_dict.pop('age')
  4. 访问键值对:可以使用键来访问字典中的值,如果键不存在则会抛出 KeyError 异常。可以使用 get() 方法来避免这种情况,并返回一个默认值(默认为 None

    1
    2
    3
    4
    # 访问键值对
    name = my_dict['name']
    age = my_dict.get('age')
    gender = my_dict.get('gender', 'unknown')
  5. 遍历字典:可以使用 for 循环来遍历字典的键或值,或者使用 items() 方法来遍历键值对

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 遍历键
    for key in my_dict:
    print(key)

    # 遍历值
    for value in my_dict.values():
    print(value)

    # 遍历键值对
    for key, value in my_dict.items():
    print(key, value)
  6. 其他方法:字典还提供了一些其他常用的方法,例如 keys() 方法用于获取所有键的视图,values() 方法用于获取所有值的视图,clear() 方法用于清空字典等

    1
    2
    3
    4
    5
    6
    7
    8
    # 获取键的视图
    keys = my_dict.keys()

    # 获取值的视图
    values = my_dict.values()

    # 清空字典
    my_dict.clear()

注意:字典是一种无序的数据结构,因此不能通过下标来访问字典中的元素,而是需要使用键来访问相应的值。另外,字典中的键必须是唯一的,如果有多个相同的键,则后面的键值对会覆盖前面的

9. Python中的list和dict是怎么实现的

  1. 列表的实现方式:Python 中的列表是一种动态数组,它实际上是一个由一系列连续的内存块组成的结构。每个元素都存储在内存中的一个单独的位置,通过索引可以直接访问相应位置的元素。当列表的长度发生变化时,Python 会重新分配一块更大或更小的内存空间来存储新的元素,然后将原来的元素拷贝到新的内存空间中。由于这种实现方式需要频繁地分配和拷贝内存,因此当列表较大时会带来一定的性能开销。
  2. 字典:Python 中的字典采用了一种哈希表的实现方式,它通过将键映射到内存地址来实现快速的键值查找。具体来说,字典实际上是一个由哈希桶组成的数组,每个哈希桶中存储着一条链表,链表中的每个节点都包含了一个键值对。当插入一个新的键值对时,Python 会根据键的哈希值将其插入到对应的哈希桶中,如果发现冲突则会将新的节点插入到链表的末尾。当查找一个键值对时,Python 首先计算键的哈希值,然后在相应的哈希桶中查找对应的链表,最后在链表中遍历查找相应的节点。由于哈希表的查找操作复杂度为 O(1),因此字典在查找键值对时具有很高的效率。当字典中的键值对数量变化时,Python 会根据需要动态调整哈希表的大小,以保证哈希桶的装载因子在一个合理的范围内

注意:列表和字典的实现方式不仅影响它们的性能,还影响了它们的特性和用法。例如,由于列表是一种连续的内存结构,因此可以使用切片和排序等操作来修改和排序列表中的元素;而由于字典是一种哈希表结构,因此它不支持切片和排序等操作,但支持键值查找和更新等操作

10. Python字符串格式化的几种方式

  1. 使用 % 运算符:可以使用 % 运算符将变量插入到字符串中

    1
    2
    3
    name = "Alice"
    age = 20
    print("My name is %s and I am %d years old." % (name, age))

    输出结果为:My name is Alice and I am 20 years old.

  2. 使用 format() 方法:可以使用 format() 方法将变量插入到字符串中

    1
    2
    3
    name = "Bob"
    age = 25
    print("My name is {} and I am {} years old.".format(name, age))

    输出结果为:My name is Bob and I am 25 years old.

  3. 使用 f-string:可以使用 f-string 将变量插入到字符串中

    1
    2
    3
    name = "Charlie"
    age = 30
    print(f"My name is {name} and I am {age} years old.")

    输出结果为:My name is Charlie and I am 30 years old.

  4. 使用模板字符串:可以使用模板字符串来格式化字符串

    1
    2
    3
    4
    5
    6
    7
    from string import Template

    name = "Dave"
    age = 35

    template = Template("My name is $name and I am $age years old.")
    print(template.substitute(name=name, age=age))

    输出结果为:My name is Dave and I am 35 years old.

11. Python中*args和**kwargs

Python 中,*args**kwargs 用于在函数定义时接受任意数量的参数,只能放在参数的最后位置

  1. *args*args 表示接受任意数量的位置参数(Positional Arguments),这些参数将被作为元组传递给函数。具体来说,当函数定义时使用 *args 时,它可以接受任意数量的位置参数,这些参数将被打包成一个元组

    1
    2
    3
    4
    5
    6
    def my_func(*args):
    for arg in args:
    print(arg)

    my_func(1, 2, 3) # 输出结果为:1 2 3
    my_func("a", "b", "c", "d") # 输出结果为:a b c d

    在上面的例子中,函数 my_func() 使用了 *args,它可以接受任意数量的位置参数,并将它们打包成一个元组。在函数内部,我们可以使用 for 循环遍历元组中的每个元素

  2. **kwargs**kwargs 表示接受任意数量的关键字参数(Keyword Arguments),这些参数将被作为字典传递给函数。具体来说,当函数定义时使用 **kwargs 时,它可以接受任意数量的关键字参数,这些参数将被打包成一个字典

    1
    2
    3
    4
    5
    6
    def my_func(**kwargs):
    for key, value in kwargs.items():
    print(f"{key} = {value}")

    my_func(name="Alice", age=20) # 输出结果为:name = Alice, age = 20
    my_func(country="USA", city="New York", language="English") # 输出结果为:country = USA, city = New York, language = English

    在上面的例子中,函数 my_func() 使用了 **kwargs,它可以接受任意数量的关键字参数,并将它们打包成一个字典。在函数内部,我们可以使用字典的 items() 方法遍历字典中的每个键值对

  3. 除了在函数定义时使用 *args**kwargs 外,它们还可以在函数调用时使用。当在函数调用时使用 *args**kwargs 时,它们的作用是将一个序列或字典拆包成多个参数

    1
    2
    3
    4
    5
    6
    7
    8
    def my_func(a, b, c):
    print(f"a = {a}, b = {b}, c = {c}")

    args = (1, 2, 3)
    my_func(*args) # 等价于 my_func(1, 2, 3)

    kwargs = {"a": 1, "b": 2, "c": 3}
    my_func(**kwargs) # 等价于 my_func(a=1, b=2, c=3)

    在上面的例子中,我们首先定义了一个函数 my_func(),它接受三个位置参数。然后,我们定义了一个元组 args 和一个字典 kwargs,分别包含三个元素和三个键值对

12. Python中深拷贝和浅拷贝的区别

Python 中的拷贝操作分为浅拷贝(Shallow Copy)和深拷贝(Deep Copy),它们的主要区别在于复制后的对象是否共享内存

  1. 浅拷贝:指创建一个新的对象,但是这个新对象只是原始对象的一个副本,它们共享相同的内存地址。也就是说,当我们修改其中一个对象时,另一个对象也会被修改。在 Python 中,可以使用 copy() 方法或切片操作来进行浅拷贝

    1
    2
    3
    4
    5
    6
    7
    # 浅拷贝示例
    list1 = [1, 2, [3, 4]]
    list2 = list1.copy() # 浅拷贝
    list1[0] = 100
    list1[2][0] = 300
    print(list1) # [100, 2, [300, 4]]
    print(list2) # [1, 2, [300, 4]]

    在上面的示例中,我们创建了一个包含三个元素的列表 list1,其中第三个元素是一个嵌套列表。然后,我们对 list1 进行了浅拷贝,得到了一个新的列表 list2。接着,我们修改了 list1 的第一个元素和第三个元素的第一个元素,然后打印出了 list1list2。可以看到,虽然 list1list2 是不同的对象,但是它们共享了第三个元素的内存地址,所以修改其中一个对象的值也会影响另一个对象的值

  2. 深拷贝:指创建一个新的对象,并且递归地复制它所包含的所有对象。也就是说,当我们修改其中一个对象时,另一个对象不会受到影响。在 Python 中,可以使用 copy.deepcopy() 方法进行深拷贝

    1
    2
    3
    4
    5
    6
    7
    8
    9
    # 深拷贝示例
    import copy

    list1 = [1, 2, [3, 4]]
    list2 = copy.deepcopy(list1) # 深拷贝
    list1[0] = 100
    list1[2][0] = 300
    print(list1) # [100, 2, [300, 4]]
    print(list2) # [1, 2, [3, 4]]

    在上面的示例中,我们使用 copy.deepcopy() 方法对 list1 进行了深拷贝,得到了一个新的列表 list2。然后,我们修改了 list1 的第一个元素和第三个元素的第一个元素,然后打印出了 list1list2。可以看到,虽然 list1list2 是不同的对象,并且它们不共享任何内存地址,所以修改其中一个对象的值不会影响另一个对象的值

13. Python中的单引号和双引号的区别

单引号和双引号都可以用来表示字符串,它们的主要区别在于字符串中是否包含了引号本身

单引号用来表示包含双引号的字符串,双引号用来表示包含单引号的字符串

1
2
3
4
5
6
7
# 使用单引号表示包含双引号的字符串
str1 = 'I said, "Hello!"'
print(str1) # I said, "Hello!"

# 使用双引号表示包含单引号的字符串
str2 = "It's a beautiful day."
print(str2) # It's a beautiful day.

在 Python 中,还有一种特殊的字符串格式,称为三引号(Triple quotes)。它可以用来表示包含多行文本的字符串,不需要在每行文本中使用转义字符

1
2
3
4
5
6
7
# 使用三引号表示多行字符串
str3 = '''Hello,
world!
'''
print(str3)
# Hello,
# world!

上面的示例中,我们使用三引号来表示包含多行文本的字符串,这样可以避免在每行文本中使用转义字符,使代码更加简洁易读

14. Python中append、insert和extend的区别

在 Python 中,append()insert()extend() 都是用来向列表(list)中添加元素的方法

  1. append: 在列表末尾添加一个元素

    1
    2
    3
    lst = [1, 2, 3]
    lst.append(4)
    print(lst) # [1, 2, 3, 4]
  2. insert: 在指定位置插入一个元素

    1
    2
    3
    lst = [1, 2, 3]
    lst.insert(1, 4)
    print(lst) # [1, 4, 2, 3]
  3. extend: 将一个列表的所有元素添加到另一个列表的末尾

    1
    2
    3
    4
    lst1 = [1, 2, 3]
    lst2 = [4, 5, 6]
    lst1.extend(lst2)
    print(lst1) # [1, 2, 3, 4, 5, 6]

15. Python中break、continue、pass是什么

在 Python 中,breakcontinuepass 都是控制语句,用于控制循环的执行流程

  • break 语句用于终止循环,并跳出循环体。当循环条件不成立或者执行 break 语句时,循环会立即停止执行。
  • continue 语句用于跳过当前循环中的某些语句,直接进入下一次循环的判断。当 continue 语句执行时,循环体中后续的语句都不会执行,而是直接跳到下一次循环的判断
  • pass 语句用于占位,表示一个空语句。当需要在代码中添加一个空语句,但是又不能让 Python 报错时,就可以使用 pass 语句

16. Python中的remove、del和pop

在 Python 中,removedelpop 都是用来删除列表元素的方法

  1. remove :列表的内置函数,用于删除列表中指定的元素。如果列表中有多个相同的元素,它只会删除第一个匹配项,如果元素不存在则会抛出 ValueError 异常。如果要删除所有匹配项,可以使用循环或列表推导式

    1
    2
    3
    4
    lst = [1, 2, 3, 4, 5]
    lst.remove(3) # 从列表中删除元素 3
    print(lst) # [1, 2, 4, 5]
    lst.remove(6) # 抛出 ValueError 异常,因为元素 6 不存在于列表中
  2. del :Python的关键字,用于删除列表中指定位置的元素。它可以删除单个元素,也可以删除切片。如果删除的是切片,则删除的是切片中的所有元素

    1
    2
    3
    4
    lst = [1, 2, 3, 4, 5]
    del lst[2] # 从列表中删除索引为 2 的元素,即元素 3
    print(lst) # [1, 2, 4, 5]
    del lst[10] # 抛出 IndexError 异常,因为索引 10 超出了列表的长度
  3. pop :列表的内置函数,用于删除列表中指定位置的元素并返回该元素。如果没有指定位置,则默认删除最后一个元素。如果要删除的位置不存在,则会引发IndexError异常

    1
    2
    3
    4
    5
    6
    7
    lst = [1, 2, 3, 4, 5]
    x = lst.pop(2) # 删除索引为 2 的元素,即元素 3,并将其赋值给变量 x
    print(x) # 3
    print(lst) # [1, 2, 4, 5]
    x = lst.pop() # 删除最后一个元素,即元素 5,并将其赋值给变量 x
    print(x) # 5
    print(lst) # [1, 2, 4]

注意:如果需要删除列表中所有元素,可以使用 lst.clear() 方法

17. Python中==和is的区别

  1. == 运算符用于比较两个对象的值是否相等,它会比较对象的内容而不是它们的身份标识(内存地址)
  2. is 运算符用于比较两个对象的内存地址是否相等,也就是它们是否指向内存中的同一块地址

18. Python中!=和is not的区别

  1. != 运算符用于比较两个对象的值是否不相等,它与 == 运算符的作用相反
  2. is not 运算符用于比较两个对象的内存地址是否不相等,它与 is 运算符的作用相反

19. Python中iterables和iterators的区别

  1. Iterables(可迭代对象)是指那些可以被迭代的对象,例如列表、元组、字典等。这些对象可以通过 for 循环进行迭代,或者使用 iter() 函数将其转换为一个迭代器对象。
  2. Iterators(迭代器)是指那些实现了 __iter__()__next__() 方法的对象。__iter__() 方法返回迭代器对象自身,而 __next__() 方法返回下一个迭代值。迭代器可以用于访问集合中的元素,并且只能向前移动,一旦到达末尾就不能再次迭代

因此,iterables 是一类对象,它们可以被迭代;而 iterators 是一类对象,它们是可迭代对象的具体实现,可以用于遍历可迭代对象中的元素。可以使用 iter() 函数将 iterables 转换为 iterators

注意:只有实现了 __iter__() 方法的对象才是可迭代对象,而实现了 __next__() 方法的对象才是迭代器。如果一个对象既可以通过 iter() 函数转换为迭代器,又可以通过 __iter__() 方法返回迭代器对象自身,那么它就是一个迭代器

20. Python解释器种类以及特点

  1. CPython:CPython 是 Python 官方实现,使用 C 语言编写。它是最常用的 Python 解释器,也是默认的解释器。CPython 的特点是运行速度较快,支持多种操作系统和平台,可以调用 C/C++ 库,但占用资源较多。
  2. Jython:Jython 是一种基于 Java 平台的 Python 解释器,它将 Python 代码转换为 Java 字节码执行。Jython 的特点是具有与 Java 平台相关的优点,例如垃圾回收、多线程等,但速度较慢,不支持一些 Python 特性和 C/C++ 库。
  3. IronPython:IronPython 是一种基于 .NET 平台的 Python 解释器,它将 Python 代码转换为 .NET 代码执行。IronPython 的特点是具有与 .NET 平台相关的优点,例如可重用性、可扩展性等,但速度较慢,不支持一些 Python 特性和 C/C++ 库
  4. PyPy:PyPy 是一种基于 Python 实现的解释器,它使用了即时编译技术,可以使 Python 代码的执行速度比 CPython 快 5-10 倍。PyPy 的特点是速度快,支持多种操作系统和平台,但不支持一些 Python 特性和 C/C++ 库。
  5. MicroPython:MicroPython 是一种专为嵌入式系统开发的 Python 解释器,它可以在资源受限的系统中运行 Python 代码。MicroPython 的特点是占用资源少、运行速度较快、支持硬件编程和网络编程等

21. Python面向对象三大特性

  1. 封装:封装是将数据和行为封装在一个单元中,通过接口来控制外部对内部的访问。Python 中的封装通过类的定义实现,将数据和方法定义在类中,并通过访问控制符号(public、private、protected)来控制访问权限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Person:
    def __init__(self, name, age):
    self.name = name
    self.__age = age # 私有属性,外部无法直接访问

    def speak(self):
    print("My name is {0}, and I'm {1} years old.".format(self.name, self.__age))

    p = Person("Tom", 20)
    print(p.name) # 可以访问公有属性 name
    print(p.__age) # 无法直接访问私有属性 __age,会抛出 AttributeError 异常
  2. 继承:继承是指一个类可以从另一个类中继承属性和方法,从而实现代码的复用和扩展。Python 中的继承通过在类定义中指定父类实现,子类可以继承父类的属性和方法,并可以在此基础上添加自己的属性和方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Animal:
    def __init__(self, name):
    self.name = name

    def speak(self):
    pass

    class Dog(Animal):
    def speak(self):
    print("Woof!")

    class Cat(Animal):
    def speak(self):
    print("Meow!")

    dog = Dog("Rufus")
    cat = Cat("Fluffy")
    dog.speak() # 输出 "Woof!"
    cat.speak() # 输出 "Meow!"
  3. 多态:多态是指同一种操作或函数可以有多种不同的实现方式,提高了代码的灵活性和可扩展性。Python 中的多态通过继承和方法重写实现,子类可以重写父类的方法,从而实现不同的行为

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class Shape:
    def draw(self):
    pass

    class Circle(Shape):
    def draw(self):
    print("Draw a circle.")

    class Square(Shape):
    def draw(self):
    print("Draw a square.")

    class Triangle(Shape):
    def draw(self):
    print("Draw a triangle.")

    shapes = [Circle(), Square(), Triangle()]
    for shape in shapes:
    shape.draw() # 多态,根据不同的对象调用不同的实现方式

22. Python多重继承

Python多重继承是指一个类可以同时继承多个父类的特性。在Python中,多重继承可以通过在类定义时在括号内列出多个父类来实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A:
def method_a(self):
print("A")

class B:
def method_b(self):
print("B")

class C(A, B): # 多重继承
def method_c(self):
print("C")

c = C()
c.method_a() # 输出 "A"
c.method_b() # 输出 "B"
c.method_c() # 输出 "C"
  1. 方法解析顺序(MRO):当一个类继承多个父类时,Python会按照一定的顺序来查找方法,这个顺序被称为方法解析顺序(MRO),MRO的计算方式是通过C3算法来实现的
  2. 调用父类方法:当一个类继承多个父类时,如果这些父类中有同名的方法,Python会按照MRO的顺序来调用这些方法。如果需要调用指定父类的方法,可以使用super()函数来实现。
  3. Diamond继承问题:当一个类继承多个父类时,如果这些父类之间存在继承关系,就会出现Diamond继承问题,这个问题可以通过使用抽象基类(ABC)来解决

注意:多重继承也可能会导致一些问题,例如父类中有同名方法或属性时,可能会产生歧义;还可能会增加代码的复杂性和维护难度。因此,在使用多重继承时,需要谨慎设计和管理类的层次结构

23. Python变量、函数、类的命名规则

  1. 只能包含字母、数字和下划线,不能以数字开头;
  2. 不允许使用 Python 的关键字和保留字,如 if、while、class、def 等
  3. 变量和函数名使用小写字母,多个单词用下划线连接,如 my_var、my_function
  4. 类名使用大写字母开头,多个单词使用驼峰命名法,如 MyClass、MyClassExample;
  5. 前导下划线表示私有属性或方法,双前导下划线表示强制私有,后续加上类名的前导下划线表示受保护的属性或方法,如 my_var、my_var、MyClass_my_var

24. Python中迭代器和生成器

  1. 迭代器:是一种访问集合元素的方式,迭代器对象从集合的第一个元素开始访问,直到所有元素被访问完毕。迭代器只能往前不会后退,而且在迭代过程中无法修改集合元素

    Python中的迭代器有两个基本的方法:iter() 和 next()

    • iter(object[, sentinel]) 函数用来生成迭代器,object 是可迭代对象,sentinel 是可选的,如果传递了第二个参数,则参数 object 必须是一个可调用的对象(如函数),此时,iter 创建了一个迭代器对象,每次调用这个迭代器对象的 __next__() 方法时,都会调用 object
    • next(iterator[, default]) 函数用来获取迭代器的下一个元素,如果迭代器已经到了最后一个元素,再次调用 next() 函数会抛出 StopIteration 异常,default 是可选的,如果迭代器已经到了最后一个元素,返回 default 值
    1
    2
    3
    4
    5
    6
    7
    # 迭代器示例
    my_list = [1, 2, 3]
    my_iterator = iter(my_list)

    print(next(my_iterator)) # 输出 1
    print(next(my_iterator)) # 输出 2
    print(next(my_iterator)) # 输出 3
  2. 生成器:是一种特殊的迭代器,它是通过函数来实现的,生成器函数在执行时会生成一个生成器对象,生成器对象是一个可迭代对象,每次调用生成器对象的 __next__() 方法时,都会执行生成器函数中的代码,直到遇到 yield 语句,yield 语句会返回一个值,并暂停生成器函数的执行,下次调用 __next__() 方法时,生成器函数会从 yield 语句暂停的位置继续执行

    Python中的生成器有两种实现方式:生成器函数和生成器表达式

    生成器函数是通过 def 关键字定义的函数,函数中包含 yield 语句,生成器函数在执行时会生成一个生成器对象

    生成器可以帮助我们节省内存,因为生成器每次只会返回一个值,而不会一次性将所有值都存储在内存中。另外,生成器还可以用于实现协程等高级特性,提高代码的灵活性和可维护性

    1
    2
    3
    4
    5
    6
    7
    8
    # 生成器示例
    def my_generator():
    yield 1
    yield 2
    yield 3

    for value in my_generator():
    print(value) # 输出 1, 2, 3

25. Python中猴子补丁是什么

猴子补丁是指在运行时动态修改类或模块的行为的技术。它允许在程序运行时更改代码,而不需要修改原始源代码。这种技术通常用于在测试或调试期间临时修改代码,或者在第三方库中添加或修改功能。使用猴子补丁时需要注意,它可能会导致代码的不稳定性和难以维护性,因此应该谨慎使用

26. Python中的垃圾回收机制

Python中的垃圾回收机制使用引用计数技术和垃圾回收器两种技术来实现。引用计数是一种轻量级的内存管理机制,当一个对象的引用计数变为0时,Python会立即回收它的内存。垃圾回收器是一种更高级别的机制,它会跟踪对象之间的引用,并回收不再使用的对象

Python中的垃圾回收器有两种实现方式:分代垃圾回收和循环垃圾回收。分代垃圾回收机制将对象根据其生命周期分成三代:0代、1代和2代。当一个对象经历了多次垃圾回收,其代数就会逐渐增加。Python垃圾回收器会根据代数来选择合适的回收策略

循环垃圾回收机制则会跟踪对象之间的引用关系,找到不再使用的对象,并将它们回收。这种机制需要更多的计算资源,因此只在必要时才会使用

27. Python中的lambda表达式

Lambda表达式是一种匿名函数,它可以在Python中快速定义简单的函数。它通常由一个单独的表达式组成,该表达式在调用时返回结果。Lambda表达式的语法如下:

1
lambda arguments: expression

其中,arguments是函数的参数,可以是任意数量的参数,用逗号分隔。expression是函数的返回值,通常是一个简单的表达式。

Lambda表达式通常用于需要一个简单函数的地方,例如在map()、filter()和reduce()等函数中。它们也可以用于定义回调函数和排序函数等

1
2
3
4
5
6
# 定义一个lambda表达式
lambda_func = lambda x, y: x + y

# 调用lambda函数
result = lambda_func(3, 4)
print(result) # 输出 7

注意:虽然lambda表达式可以用来定义小型函数,但是如果函数体过于复杂,建议使用普通的函数定义语法,以提高代码可读性和可维护性

28. Python中的反射

Python中的反射是一种动态访问和修改对象属性和方法的机制,可以通过字符串的方式来访问对象的属性和方法。在Python中,每个对象都有一些基本的属性和方法,而反射机制可以通过这些属性和方法来实现动态访问和修改。

反射机制主要通过内置函数getattr()setattr()hasattr()delattr()来实现。具体来说,这些函数的作用如下:

  • getattr(object, name[, default]): 获取对象的属性值。如果属性不存在,则会抛出AttributeError异常,或者返回default参数指定的默认值(如果提供了该参数)。
  • setattr(object, name, value): 设置对象的属性值。如果属性不存在,则会创建一个新属性
  • hasattr(object, name): 检查对象是否有指定的属性。如果对象有该属性,返回True,否则返回False。
  • delattr(object, name): 删除对象的指定属性。

通过这些函数,可以实现访问、修改、创建和删除对象的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class MyClass:
def __init__(self):
self.x = 10
self.y = 20

def add(self, a, b):
return a + b

obj = MyClass()

# 使用getattr获取对象属性
x = getattr(obj, 'x')
print(x) # 输出10

# 使用setattr设置对象属性
setattr(obj, 'y', 30)
print(obj.y) # 输出30

# 使用hasattr检查对象属性是否存在
print(hasattr(obj, 'z')) # 输出False

# 使用delattr删除对象属性
delattr(obj, 'x')
print(obj.__dict__) # 输出 {'y': 30}

注意:反射机制可以让代码更加灵活,但同时也会增加代码的复杂性和运行开销,因此在实际使用中需要谨慎使用

29. Python中的__new____init__的区别

在Python中,每个类都有两个特殊方法__new__()__init__()。这两个方法都是用来创建类实例的,但是它们的作用不同。

__new__()方法是一个类方法,用于创建并返回一个新的类实例。它通常被用来控制对象的创建过程,可以在对象实例化之前做一些自定义的操作,比如修改对象的属性、改变对象的类型等。__new__()方法的返回值是一个对象实例,这个实例会传递给__init__()方法作为第一个参数self。

__init__()方法是一个实例方法,用于初始化一个已经存在的对象实例。它通常被用来对对象的属性进行初始化和赋值。__init__()方法没有返回值,它只是对self进行修改,因为self已经被创建了并传递给这个方法了。

需要注意的是,__new__()方法是在__init__()方法之前调用的,所以在__init__()方法中可以使用self对象已经存在的属性,但是不能修改这些属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass:
def __new__(cls, *args, **kwargs):
print('__new__ called')
instance = super().__new__(cls)
instance.name = 'MyClass'
return instance

def __init__(self, age):
print('__init__ called')
self.age = age

obj = MyClass(18)
print(obj.name) # 输出 MyClass
print(obj.age) # 输出 18

在上面的例子中,__new__()方法被重写,创建了一个新的对象实例,并将name属性设置为MyClass,最后将实例返回给__init__()方法。__init__()方法接收到这个实例之后,对它进行了初始化,将age属性赋值为传入的参数18

30. Python闭包

Python闭包是指在函数内部定义的函数,该函数可以访问外部函数的变量和参数,并且可以在外部函数返回后继续访问这些变量和参数。闭包可以用来实现一些高级的编程技巧,例如装饰器和函数工厂

在Python中,闭包是通过函数对象和函数属性来实现的。当一个函数定义在另一个函数内部时,它就可以访问外部函数的变量和参数。这些变量和参数被称为自由变量和自由参数。当外部函数返回时,闭包函数仍然可以访问这些自由变量和自由参数,因为它们被保存在闭包函数的函数对象中。这使得闭包函数可以在外部函数返回后继续执行,并且可以访问外部函数的状态

1
2
3
4
5
6
7
8
def outer_func(x):
def inner_func(y):
return x + y
return inner_func

closure = outer_func(10)
print(closure(5)) # 输出 15
print(closure(10)) # 输出 20

31. Python元类

Python中的元类是用于创建类的类。元类允许我们控制类的创建过程,可以在类被创建之前或之后修改类。元类是一种高级的Python编程技巧,通常用于框架和库的开发中,比如Django、Flask、Tornado等。

元类可以用来实现单例模式、注册表、插件系统等功能。在Python中,所有的类都是由type类创建的,因此type类是Python中的默认元类

32. Python中的GIL

GIL(Global Interpreter Lock)是Python解释器中的一个特性,它是一种锁机制,用于保证在同一时刻只有一个线程可以执行Python字节码。这个锁是解释器级别的锁,也称为全局锁。在多线程环境下,GIL会导致线程无法真正并行执行,因为只有一个线程可以拥有GIL

GIL的存在是因为CPython解释器的内存管理不是线程安全的,当多个线程同时访问和修改Python对象的引用计数时,可能会出现竞争条件和数据一致性问题。因此,为了避免这些问题,CPython引入了GIL来限制同一时刻只有一个线程可以执行Python字节码。

GIL的存在会影响Python多线程程序的性能,因为多线程程序无法真正利用多核CPU的性能优势。在CPU密集型任务中,GIL会成为瓶颈,导致多线程程序的运行时间比单线程程序更长。但在I/O密集型任务中,GIL的影响较小,因为线程在等待I/O操作完成时,GIL会被释放,其他线程就可以执行Python字节码。

为了避免GIL的影响,可以使用多进程代替多线程,因为多个进程之间是相互独立的,各自拥有自己的解释器和GIL,可以真正并行执行。也可以使用其他语言编写CPU密集型任务的模块,然后在Python程序中调用这些模块,避免GIL的影响。另外,Python还提供了一些并发编程库和工具,如multiprocessing、concurrent.futures、asyncio等,可以在一定程度上缓解GIL的影响

33. Python类和对象的区别

在Python中,类是一种数据类型,用于定义对象的属性和方法。对象是类的实例,也就是类的具体化。类是对象的抽象,而对象是类的具体实现。类是一种模板或者蓝图,用于创建对象。

具体来说,Python中的类是由属性和方法组成的,属性是类的变量,用于存储对象的状态,方法是类的函数,用于定义对象的行为。类可以看作是一种特殊的字典,类的属性和方法都保存在类的命名空间中。类属性是所有实例共享的,而实例属性是每个实例独有的。

对象是类的实例,是根据类创建的具体实体,每个对象都有自己的状态和行为。对象包含属性和方法,属性是对象的变量,用于存储对象的状态,方法是对象的函数,用于定义对象的行为。对象是在程序运行时创建的,每个对象都是独立的,有自己的属性和方法

在Python中,类和对象的关系可以用以下表格来描述:

对象
模板 具体实体
定义 创建
属性和方法 属性和方法
类变量 实例变量
类方法 实例方法
类属性 实例属性
继承 实例化

总的来说,类是一种抽象的概念,对象是类的实例化,是具体的实体。类描述了对象的属性和方法,而对象则是类的具体实现

34. Python中的self的作用

在Python中,self是一个约定俗成的命名方式,用于表示类实例对象本身。self作为第一个参数出现在类的方法中,它表示对类实例对象本身的引用。

当一个类的方法被调用时,它会自动传入类实例对象本身作为第一个参数,通常使用self作为参数名。这样,在方法内部就可以使用self来引用对象本身,从而访问对象的属性和调用对象的方法

1
2
3
4
5
6
7
8
9
10
class Person:
def __init__(self, name, age):
self.name = name
self.age = age

def say_hello(self):
print("Hello, my name is", self.name, "and I am", self.age, "years old.")

p = Person("John", 30)
p.say_hello() # 输出:Hello, my name is John and I am 30 years old.

在上面的示例中,__init__方法和say_hello方法都有一个self参数,它们用于引用类实例对象本身。在__init__方法中,使用self来初始化类实例对象的属性;在say_hello方法中,使用self来引用对象的属性和方法,从而输出对象的信息

注意:self不是Python的关键字,可以使用其他名称代替,但是约定俗成的做法是使用self。另外,self只在类的方法中出现,而在类的其他地方,如属性和方法的定义中,不需要使用self

35. Python2和Python3的区别

  1. 语法:Python3对语言的语法进行了一些改进,如print函数变成了print()函数,除法运算符变成了真正的浮点除法符号/,新增了非本地变量声明nonlocal
  2. 编码:Python3默认采用UTF-8编码,而Python2采用的是ASCII编码。
  3. 兼容性:Python3不兼容Python2的代码,而Python2中的大多数代码可以在Python3中运行,但是需要进行一些修改
  4. 标准库:Python3的标准库中增加了一些新模块,如asyncio、enum、ipaddress等,同时还更新了一些模块,如pickle、tkinter等。
  5. Unicode字符串:在Python3中,字符串是默认采用Unicode编码,而在Python2中,字符串采用的是ASCII编码。
  6. 整数除法:在Python2中,两个整数相除得到的是整数结果,而在Python3中,两个整数相除得到的是浮点数结果
  7. range函数:在Python2中,range函数返回的是一个列表,而在Python3中,range函数返回的是一个可迭代对象
  8. 异常处理:在Python3中,异常处理语句需要使用as关键字来接收异常对象。
  9. print函数:在Python2中,print语句可以不用括号,而在Python3中,print函数必须要用括号。

36. Python如何提高运行效率

  1. 使用算法和数据结构:在编写Python代码时,使用高效的算法和数据结构可以大大提高代码的运行效率。例如,在需要对大量数据进行排序时,使用快速排序算法比使用冒泡排序算法更加高效。
  2. 使用生成器:生成器是Python中的一种特殊类型的函数,可以逐个生成值,而不是一次生成所有值。使用生成器可以避免在内存中生成大量数据,从而提高运行效率。
  3. 使用列表推导式和生成器表达式:列表推导式和生成器表达式可以在一行代码中生成列表或生成器。它们通常比使用循环和条件语句生成列表或生成器更加高效
  4. 使用Cython或Numba等工具:Cython是一种将Python代码转换为C代码的工具,可以提高Python代码的运行效率。Numba是一种Python库,可以使用JIT(即时编译)技术将Python代码转换为机器码,从而提高代码的运行速度。
  5. 使用并行编程:Python提供了一些模块,如multiprocessing和concurrent.futures等,可以使用多进程或多线程并行执行代码,从而提高运行效率。
  6. 避免使用循环:在Python中,循环的执行效率较低,应尽量避免使用循环。可以使用NumPy等库中的矩阵运算等高效操作来替代循环
  7. 避免使用全局变量:在Python中,全局变量的访问速度较慢,应尽量避免使用全局变量,而使用局部变量来代替。

37. Python异常处理

  1. 异常处理是什么?
    异常处理是指在程序运行过程中,当出现错误或异常时,程序能够捕获并处理这些异常,从而保证程序的正常运行。

  2. Python中的异常类型有哪些?
    Python中有很多内置的异常类型,比如NameError、TypeError、ValueError等等,还可以自定义异常类型。

  3. 如何捕获异常?
    在Python中,可以使用try-except语句来捕获异常。try语句块中放置可能会出现异常的代码,如果出现异常,则会跳转到except语句块中进行处理

  4. 如何处理异常?
    在except语句块中可以对异常进行处理,比如输出错误信息、记录日志、重新抛出异常等等。

  5. finally语句的作用是什么?
    finally语句块中的代码无论是否出现异常都会被执行,通常用于释放资源等操作。

1
2
3
4
5
6
try:
# 可能会出现异常的代码块
except ExceptionType:
# 处理异常的代码块
finally:
# 最终执行的代码块
  • try:表示要执行的代码块,这里是可能会出现异常的代码块
  • except:表示当try语句块中出现指定类型的异常时,执行该语句块;
  • ExceptionType:指定要处理的异常类型;
  • finally:不管try语句块中是否有异常都会执行的代码块

除了使用except语句来处理异常,还可以使用else语句块,当没有异常出现时,执行else语句块中的代码

1
2
3
4
5
6
7
8
try:
# 可能会出现异常的代码块
except ExceptionType:
# 处理异常的代码块
else:
# 没有异常时执行的代码块
finally:
# 最终执行的代码块

38. Python中的标准异常类

异常类 描述
Exception 所有标准异常类的基类
AssertionError 断言语句失败时引发
AttributeError 对象没有这个属性
EOFError 没有输入,输入流结束
FileNotFoundError 请求的文件或目录未找到
ImportError 导入模块或包失败
IndexError 序列中没有此索引(index)
KeyError 映射中没有这个键
KeyboardInterrupt 用户中断执行(通常是输入^C)
MemoryError 操作耗尽内存
NameError 未声明/初始化对象(没有属性)
NotImplementedError 尚未实现的方法或函数
OSError 操作系统错误
OverflowError 数字运算超出最大限制
RecursionError 递归调用超出最大限制
RuntimeError 一般的运行时错误
StopIteration 迭代器没有更多的值
SyntaxError Python 语法错误
IndentationError 缩进错误
TabError Tab和空格混用
SystemError 一般的解释器系统错误
SystemExit Python 解释器请求退出
TypeError 对类型无效的操作
UnboundLocalError 访问未初始化的本地变量
UnicodeError Unicode 相关的错误
ValueError 传递给函数的参数类型不正确,或者值不合法
ZeroDivisionError 除数为零

本文链接:
https://huajun-chen.github.io/2023/03/01/Python面试题/