【python基础面经—秋招实习小白必看常见考点和问题】https://www.bilibili.com/video/BV1nXetznEqh?vd_source=7c2b5de7032bf3907543a7675013ce3a

1.GIL全局解释锁

python是不是线程安全的?

不是

(1)因为python不是线程安全的,GIL确保任意时刻只有一个线程可以执行python代码。

(2)工作原理:每个线程在执行python字节码之前先获取GIL,当一个线程获取GIL之后,其他线程必须等待该线程释放GIL才能继续执行。

(3)GIL存在的主要目的是简化内存管理,但在多核CPU上限制了多线程的并行执行。

(4)对于I/O密集型任务(文件读取、网络请求),GIL影响较小,因为即使一个线程被阻塞,其他线程仍然可以运行。

!(5)如何避免GIL:将python代码转换为C语言代码;使用multiprocessing多线程模块;使用C扩展模块,在C扩展模块中释放GIL,允许其他线程执行python代码;在pytorch时使用numpy矢量化计算。

2.面向对象编程

python是面向对象编程还是面向过程编程,有什么区别?

面向对象编程

面向对象编程的核心是类和对象。类定义了对象的属性和方法,对象是类的实例和具体实现。

四大特性:封装、继承、多态、抽象。

是否了解抽象是什么含义?

抽象:隐藏复杂的实现细节,暴露简单的接口提供给用户使用。

封装:将数据和具体操作代码放在某个对象里面,外部无法直接访问,必须调用类的方法。

继承:在现有类的基础上,创建新类。子类继承父类的属性,可以重用父类的代码,并可以在子类中对父类代码进行修改或拓展。

多态:当子类和父类有相同方法时,子类的方法会覆盖父类的方法。

抽象:隐藏复杂的实现细节,暴露简单的接口提供给用户使用。

3.*args & **kwargs

是否了解*args & **kwargs的含义?

*args:接收任意数量的非关键字参数并打包成元组

# *args - 接收任意数量的位置参数
def total(*args):
    result = 0
    for num in args:
        result += num
    return result

# 调用方式很灵活
print(total(1, 2, 3))      # 输出: 6
print(total(10, 20))       # 输出: 30
print(total())             # 输出: 0

def show_args(*args):
    print(f"args = {args}")
    print(f"类型: {type(args)}")

show_args(1, 2, 3)
# 输出:
# args = (1, 2, 3)
# 类型: <class 'tuple'>

**kwargs:接收任意数量的关键字参数并打包成字典

# **kwargs - 接收任意数量的关键字参数
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

# 调用
print_info(name="小明", age=18, city="北京")
# 输出:
# name: 小明
# age: 18
# city: 北京

def show_kwargs(**kwargs):
    print(f"kwargs = {kwargs}")
    print(f"类型: {type(kwargs)}")

show_kwargs(a=1, b=2)
# 输出:
# kwargs = {'a': 1, 'b': 2}
# 类型: <class 'dict'>

函数接收实参,按顺序分配给形参,但遇到带 * 的形参时,就把未分配出去的一起以元组或字典形式打包分配给带星号的形参。

4.python数据结构(列表,元组,集合,字典)

!基础类型:

类型是否有序是否可变是否允许重复典型用途
list列表有序可变允许重复存储有序数据序列
tuple元组有序不可变允许重复

存储不可变数据,如函数返回多值

不可变数据有哪些?

字符串、int

set集合无序可变不允许重复去重、集合运算
dict字典有序可变

key不允许重复

value允许重复

键值映射、快速查找

数据结构的增删改查:

操作list列表tuple元组
新增

append(x)末尾加

insert(i,x)指定位置

extend([...])扩展

不可变
删除

remove(x)按值删除

pop(i)按索引删除

clear()全删除

不可变
修改lst[i]=x直接修改不可变
查找

lst[i]索引

in判断是否存在

index(x)找位置

t[i]索引

in判断存在

遍历for x in lstfor x in t
去重需转set需转set
操作set集合dict字典
新增

add(x)加元素

update({...})批量加

d[key]=value新增键值
删除

remove(x)按值删除

discard(x)按值删除

pop()随机删除

pop(key)按键删除

del d[key] clear()全删除

修改只能先删除后加d[key]=new_value
查找

in判断存在

d[key]

d.get(key)

遍历for x in lstfor k,v in d.items()
去重自动去重key自动唯一

列表和元组有什么区别?

列表是可变的,元组是不可变的;列表元素可以修改、添加、删除,但是元组一旦创建就不可以修改。

 字典如何按照key排序?

因为key不允许重复,用sorted函数实现。

d = {'b':2,'a':3,'c':1}
sorted_items = sorted(d.items()) # 按 key 升序
print(sorted_items)
# 输出:[('a',3),('b',2),('c',1)]

5.深拷贝、浅拷贝

!!!

浅拷贝:复制对象的最外层结构,内部引用共享。

copy.copy()

import copy

# 创建一个嵌套列表(列表里有列表)
original = [1, 2, [3, 4]]

# 浅拷贝
shallow = copy.copy(original)

print(f"原列表: {original}")
print(f"浅拷贝: {shallow}")
print(f"是同一个对象? {original is shallow}")          # False(外层不同)
print(f"内层列表相同? {original[2] is shallow[2]}")    # True(内层共享!)

# 修改内层列表
shallow[2].append(5)

print(f"\n修改后:")
print(f"原列表: {original}")   # [1, 2, [3, 4, 5]]  ← 原列表也被改了!
print(f"浅拷贝: {shallow}")   # [1, 2, [3, 4, 5]]

深拷贝:递归复制内部的所有元素及其可变对象。

copy.deepcopy()

import copy

original = [1, 2, [3, 4]]

# 深拷贝
deep = copy.deepcopy(original)

print(f"原列表: {original}")
print(f"深拷贝: {deep}")
print(f"是同一个对象? {original is deep}")          # False
print(f"内层列表相同? {original[2] is deep[2]}")    # False(内层也独立!)

# 修改内层列表
deep[2].append(5)

print(f"\n修改后:")
print(f"原列表: {original}")   # [1, 2, [3, 4]]  ← 原列表不受影响!
print(f"深拷贝: {deep}")       # [1, 2, [3, 4, 5]]

比如创建一个列表嵌套列表,分别使用两种拷贝方法,然后在嵌套列表里面增加一个元素,print结果,会发现,浅拷贝里面的嵌套列表也增加了这个元素,但是深拷贝没有。

浅拷贝:

对象内部元素是不可变对象,或不关心内部共享。

如:复制配置模版、树结构的节点引用

深拷贝:

当对象内部含有可变对象,并且需要完全独立修改,避免改动相互影响

如:复制嵌套列表、复杂 JSON 配置。

6.is和==的区别

== 是比较两个元素的值是否相等

is 是比较两个元素内存地址是否相同

7.函数的默认参数是可变对象,会有什么影响?

所有的调用会共享一个对象,导致后续调用受到前一次修改的影响。

原因:因为默认参数在函数定义时只初始化一次,后续调用都用同一个对象。

解决方法:默认参数用None,然后在函数内部初始化。

def add_item(x, lst=[]): # 默认参数是一个可变list
    lst.append(x)
    return lst

print(add_item(1)) # [1]
print(add_item(2)) # [1, 2]
print(add_item(3)) # [1, 2, 3]

8.python导入库的顺序

标准库:os,sys

第三方库:numpy,pytorch,pandas

本地库:自己写的代码模块和包

9.实例方法、静态方法、类方法?

以下哪种方法调用是错的?

实例方法:类方法中默认都是实例方法,特点就是包含一个self参数,调用时需要先实例化

静态方法:需要使用 @staticmethod 修饰,不包含self参数,可以直接用类名或者类对象调用。

类方法:需要使用 @classmethod 修饰,至少包含一个cls参数,使用类名直接调用。

10.装饰器、生成器、迭代器、列表推导式、上文管理器?

!装饰器:用来修改或扩展函数方法的高阶函数,接受一个函数作为参数并且返回另一个函数,而不需要修改源代码。例如代码重用、日志记录、性能计时。

@cache

!生成器:通过 yield 语句生成一个值。它不会一次返回所有的值,而是每次请求一个值时才生成一个。这种惰性求值可以节省内存。

迭代器:实现了 iter 和 next 方法,它允许我们遍历一个集合而不需要暴露集合的内部结构。(例如生成器本身它就是一个迭代器)。

列表推导式:一般是由 一个表达式+可选条件+for 语句构成,用一种简洁的方式来创建基于某种逻辑的列表。

squares = [x**2 for x in range(5)]

上下文管理器:通过 with 语句来实现,主要用来自动管理资源,保证用完后能正确释放,比如文件读写和数据库连接等。

11.python如何处理异常?

try,except,else,finally语句

try:尝试可能引发异常的操作

except:捕获并处理特定异常类型

else:在try模块没有引发异常时执行

finally:无论是否发生异常都会执行,一般用于清理操作、释放资源

12.lambda函数?

匿名函数,结构就是 lambda 参数:表达式,通过简洁的表达式来定义一个函数的功能

# 普通函数
def add(x, y):
    return x + y

print(add(2, 3))  # 5

# lambda
add = lambda x, y: x + y
print(add(2, 3))  # 5

13.python中的断言assert?

用于调试的工具,检查代码中某个条件是否为真

assert 条件表达式,“错误信息(可选)”。成立正常运行,不成立就抛出异常。

常用于训练模型代码中抛出一个shape是否符合要求。

14.__slot__是什么?如何使用?

它是一个类变量,用于限制类的实例可以拥有的属性。

使用slots可以节省内存,因为它阻止了实例字典的创建,防手误添加错属性。

class Person:
    __slots__ = ('name', 'age')

    def __init__(self, name, age):
        self.name = name
        self.age = age

p = Person("Alice", 30)
# print(p.__dict__) # 会报错:AttributeError: 'Person' object has no attribute '__dict__'

15.map、filter、reduce函数?

map:对每个元素应用函数,生成新序列。

def square(x):
    return x * x

nums = [1, 2, 3, 4]
squares = list(map(square, nums)) # 使用 square 函数
print(squares) # [1, 4, 9, 16]

filter:过滤,保留所有返回 True 的元素。

def is_even(x):
    return x % 2 == 0 

nums = [1, 2, 3, 4, 5, 6]
evens = list(filter(is_even, nums)) # 使用 is_even 函数
print(evens) # [2, 4, 6]

reduce:对元素进行累积处理,返回一个单一值。

from functools import reduce

def multiply(x, y):
    return x * y

nums = [1, 2, 3, 4]
product = reduce(multiply, nums) # 使用multiply函数
print(product) # 24

16.常见的数据结构(非常重要!!!)

(1)数据和链表的区别?

数组和链表是两种常见的数据结构,它们的主要区别在于内存存储方式、访问效率和插入删除的性能。

数组:使用连续内存,支持通过索引进行快速随机访问,时间复杂度是O(1)

链表:使用非连续内存,每个节点通过指针连接,只能顺序访问O(n)

在插入和删除方面:

数组:如果要在中间插入或删除元素,通畅需要移动后续元素,效率是O(n)

链表:插入和删除更高效,只需要修改指针即可,效率为O(1),前提是已知前驱节点

如果应用场景是频繁访问元素,适合用数组;如果是频繁插入删除,链表更合适。

(2)栈是一种什么数据结构?插入或者删除栈顶元素,指针是否有变化?

栈是一种后进先出的数据结构,只能在栈顶进行插入(push)和删除(pop)操作。

插入时,新元素放进栈顶,栈顶指针向上移动;删除时,栈顶元素被移除,指针向下移动。

(3)栈和队列有什么区别?栈和堆有什么区别?

栈是后进先出结构,只能在栈顶插入和删除;队列是先进先出结构,从队尾插入、从队头删除

堆通常指优先队列,是一种特殊的完全二叉树,用于高效地获取最大/最小值。

常见数据结构的应用场景:

场景选择
随机访问块数组
插删频繁链表
后进先出
先进先出队列
快速查找映射关系哈希表
需要排序/范围查询树(BST/红黑树)
动态获取最大/最小值

常见数据结构相关基础知识:

数据结构逻辑结构是否连续存储访问方式插入/删除查找效率典型应用

数组

Array

线性连续存储随机访问O(1)代价大O(n)按索引块O(1)

固定大小

随机访问

链表

Linked List

线性分散节点顺序访问O(n)只修改指针O(1)慢O(n)频繁删插

Stack

线性可基于数组/链表LIFO只操作栈顶O(1)不支持随机查找

函数调用

括号匹配

队列

Queue

线性可基于数组/链表FIFO只操作首尾O(n)不支持随机查找

消息队列

任务调度

哈希表

Hash Table

关系映射数组+数列

通过key均摊

O(1)均摊O(1)均摊

字典

缓存

Tree

层次结构节点+指针需遍历O(logn)插删需维护平衡查找O(nlogn)

组织层级数据

Heap

完全二叉树数据存储只能访问堆顶插删O(nlogn)不支持随机查找优先队列

17.python小练习

(1)如何从一个列表中删除重复元素?/如何判断列表有重复元素?

先把列表转化为集合,再转回列表,因为集合不允许有重复元素

lst = [1, 2, 3, 4, 2]
has_duplicates = len(lst) != len(set(lst))
print(has_duplicates) # 输出:True,说明有重复 

(2)如何将多个列表组合成一个元组?

使用zip函数,list(zip(list1,list2,list3))

a = [1, 2, 3]
b = ['a', 'b', 'c']
c = [True, False, True] 

z = zip(a, b, c) 
print(list(z))
# [(1, 'a', True), (2, 'b', Flase), (3, 'c', True)] 

(3)如何删除字符串中所有空白?

“”.join(s.split())

(4)如何删除文件

os.remove()

18.python的参数传递是值传递,还是引用传递?

既不是纯粹的值传递,也不是纯粹的引用传递,而是对象引用传递。

对于不可变对象,函数内部对参数的修改不会影响到函数外部的变量;

对于可变对象,函数内部对参数的修改会影响到函数外部的变量。

19.python中__init__和__new__有什么区别?

__init__:是实例初始化,用于初始化对象的属性。

__new__:是实例创建方法,用于创建并返回一个新的实例。

20.如何在一亿个数据里面找到top100?

最小堆来实现。维护一个大小为100的堆,每来一个新数,如果堆长度小于100,就加入;如果比堆顶大,就替换堆顶。

import heapq
import random

def find_top_k(data, k=100):
    heap = []
    for x in data:
        if len(heap) < k:
            heapq.heappush(heap, x)
        else:
            if x > heap[0]:
                heapq.heappushpop(heap, x)
    return sorted(heap, reverse=True)

21.面向过程和面向对象编程有什么区别?

面向过程(Procedural Programming,POP)

核心:把问题分解为一个个函数/步骤,一步一步解决

关注点:怎么做(流程)

程序结构:数据和操作

面向对象(Object-Oriented Programming,OOP)

核心:把问题抽象为对象,对象封装了属性(数据)方法(行为)

关注点:谁来做(对象)

程序结构:数据和操作封装在对象内部

22.python为什么 async 和 await 可以一起用?

async定义协程函数,返回协成对象。

await挂起当前协程等待另一个协程完成。

两者结合才能实现时间循环中的异步调度。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐