python面试问题
本文最后更新于404 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

python 深拷贝与浅拷贝

浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。
深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。

import copy
a = [1,2,[1,2]]
b = a.copy()
c = copy.deepcopy(a)
print(a,b,c)
b[2].append(3)
print(a,b,c)
#输出
[1, 2, [1, 2]] [1, 2, [1, 2]] [1, 2, [1, 2]]
[1, 2, [1, 2, 3]] [1, 2, [1, 2, 3]] [1, 2, [1, 2]]

python 多线程能用多个 cpu 么?

python 的多线程不能利用多核 CPU。原因是 python 的解释器使用了GIL(Global Interpreter Lock),在任意时刻中只允许单个python线程运行。无论系统有多少个 CPU 核心,python 程序都只能在一个 CPU 上运行。
GIL 中文译为全局解释器锁,其本质上类似操作系统的 Mutex。GIL 的功能是:在 CPython 解释器中执行的每一个 Python 线程,都会先锁住自己,以阻止别的线程执行。
因为 python 的多线程本身不能利用多核 CPU,但 Python 中还有其他的多进程编程模块,例如 multiprocessing,它可以在多个 CPU 上并行执行任务。使用多进程可以充分利用多个 CPU 运行不同的进程,从而提高并行计算的性能。在多进程编程中,每个进程都有自己独立的 Python 解释器和内存空间,因此不存在 GIL 的限制。

垃圾回收

引用计数:垃圾回收的第一步

Python的垃圾回收机制主要依赖于引用计数。每个对象在内部都有一个引用计数器,用于记录引用该对象的其他对象的数量。当引用计数为0时,表示没有其他对象引用它,这意味着该对象不再被使用,可以被回收。

循环引用:垃圾回收的挑战

然而,引用计数机制并不能解决所有的垃圾回收问题。让我们来看一个例子:

class Person:    
	def __init__(self):        
		self.pet = None
		person1 = Person()
		person2 = Person()
		person1.pet = person2
		person2.pet = person1

在这个例子中,我们创建了两个Person对象,然后相互引用对方,形成了一个循环引用。这意味着即使没有其他对象引用这两个Person对象,它们的引用计数也永远不会变为0,从而导致内存泄漏。

标记-清除算法:解决循环引用的难题

为了解决循环引用的问题,Python引入了一种称为"标记-清除"的垃圾回收算法。这个算法包含两个主要阶段。
首先,垃圾回收器从根对象开始,递归地遍历所有可访问的对象,并标记它们为"可访问"。根对象可以是全局变量、活动函数以及其他被程序直接引用的对象。
接下来,在标记阶段完成后,垃圾回收器将清除所有未标记的对象。这些未标记的对象被认为是不可访问的垃圾,它们所占用的内存将被释放。

分代回收:优化垃圾回收的利器

除了标记-清除算法,Python还引入了"分代回收"的概念,以进一步优化垃圾回收的效率。分代回收将内存中的对象分为不同的代,每个代代表对象的存活时间。
通常情况下,新创建的对象被分配到第一代,被称为"年轻代"。如果对象在年轻代中存活时间较长,它将被提升到下一代,即"中年代"。同样地,如果对象在中年代中存活时间较长,它将被进一步提升到更高的代,即"老年代"。
分代回收的思想是,大部分对象的生命周期都很短暂,只有少数对象会存活更久。因此,垃圾回收器更频繁地回收年轻代,而对于老年代的回收发生得相对较少,从而提高了垃圾回收的效率。

python 里的生成器是什么

在 Python 中,生成器(Generator)是一种特殊的迭代器,它是一种用于创建迭代器的函数。生成器函数可以通过 yield 语句来产生一个值,并且在产生值之后可以暂停执行,保存当前的状态。当再次请求下一个值时,生成器会从上次暂停的位置继续执行,生成下一个值。这种方式可以有效地节省内存空间,因为它不需要一次性生成所有的值,而是按需生成。
生成器的工作方式与普通函数不同。普通函数在调用时会执行完所有的代码并返回结果,而生成器函数在每次调用时只会执行到 yield 语句处,并返回一个值。生成器函数的执行过程可以被暂停和恢复,从而实现了按需生成数据的效果。

def countdown(n):
    while n > 0:
        yield n
        n -= 1
# 使用生成器函数创建生成器对象
generator = countdown(5)
print(next(generator)) #使用next获取值5
# 通过迭代生成器对象获取值
for value in generator:
    print(value) #4,3,2,1

迭代器和生成器的区别

迭代器是一种对象,它实现了迭代协议,即具有 __iter__ 和 __next__ 方法。迭代器可以用于遍历可迭代对象(如列表、元组、字典等),每次调用 __next__ 方法都返回可迭代对象中的下一个元素,当没有更多元素可返回时,会引发 StopIteration 异常。
生成器是一种特殊的迭代器,它是通过生成器函数或生成器表达式创建的。生成器函数是使用 def 关键字定义的函数,其中包含 yield 语句。生成器表达式则类似于列表推导式,但使用圆括号括起来。生成器函数可以通过 yield 语句来产生一个值,并且在产生值之后可以暂停执行,保存当前的状态。当再次请求下一个值时,生成器会从上次暂停的位置继续执行,生成下一个值。避免一次性生成所有数据,从而节省内存空间。
区别

  • 定义方式:迭代器可以通过实现迭代协议来创建,而生成器可以通过生成器函数或生成器表达式来创建。
  • 返回值:迭代器的 __next__ 方法返回下一个元素,而生成器函数中的 yield 语句用于产生值,并且在生成值后可以暂停执行,保存当前状态。生成器表达式直接返回生成器对象。
  • 状态保存:迭代器在迭代过程中不会保存状态,每次调用 __next__ 方法都是从头开始计算下一个值。而生成器函数和生成器表达式可以保存状态,在下次调用时从上次暂停的位置继续执行。
  • 内存占用:迭代器需要保存完整的迭代对象,占用内存较大。生成器采用按需生成的方式,只在需要时生成数据,节省内存空间。
  • 使用方式:迭代器可以通过 iter() 函数进行转换,并使用 next() 函数获取下一个元素。生成器可以直接使用,也可以通过迭代来获取值。

装饰器

装饰器(Decorator)是 Python 中一种特殊的语法构造,用于修改或扩展函数(或类)的功能。装饰器本质上是一个函数,它接受一个函数作为输入,并返回一个新的函数作为输出,这个新函数通常会在原函数的基础上添加一些额外的功能或行为。
装饰器的作用是实现代码的重用和功能的动态扩展,它可以在不修改原函数代码的情况下,对函数进行功能增强、日志记录、性能统计等操作。通过装饰器,我们可以将通用的功能逻辑与具体函数的实现分离,提高代码的可读性和可维护性。

import time

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        execution_time = end_time - start_time
        print(f"函数 {func.__name__} 执行时间:{execution_time} 秒")
        return result
    return wrapper

@timer
def my_function():
    time.sleep(2)
    print("执行完成")

my_function()
#输出
执行完成
函数 my_function 执行时间:2.0017595291137695 秒

python 有哪些数据类型

  • 数字类型(Numeric Types):整数(int)、浮点数(float)、复数(complex)等。
  • 字符串类型(String Type):由字符组成的序列,用于表示文本信息。
  • 列表类型(List Type):有序可变的集合,可以包含任意类型的元素,使用方括号 [] 表示。
  • 元组类型(Tuple Type):有序不可变的集合,可以包含任意类型的元素,使用圆括号 () 表示。
  • 集合类型(Set Type):无序的可变集合,不允许重复元素,使用花括号 {} 表示。
  • 字典类型(Dictionary Type):无序的键值对集合,用于存储具有唯一键的值,使用花括号 {} 表示。
  • 布尔类型(Boolean Type):表示真或假的值,包括 True 和 False 两个取值。
  • 空值类型(None Type):表示空对象或缺失值的特殊类型,只有一个取值 None。

Python 中列表( List )中的 del,remove,和 pop 等的用法和区别

1.del:del 是一个关键字,用于删除列表中指定位置的元素或删除整个列表。它有以下两种用法:

  • 删除指定位置的元素:使用 del 关键字加上列表名和索引,例如:del my_list[index],将删除列表 my_list 中索引为 index 的元素。
  • 删除整个列表:使用 del 关键字加上列表名,例如:del my_list,将删除整个列表 my_list。
    2.remove:remove 是列表的方法,用于删除列表中第一个匹配的指定元素。它的用法是:
  • 使用列表名后跟点操作符(.)和 remove 方法,加上要删除的元素作为参数,例如:my_list.remove(element),将删除列表my_list中第一个匹配元素 element。
    3.pop:pop 是列表的方法,用于删除并返回列表中指定位置的元素。它的用法是:
  • 使用列表名后跟点操作符(.)和pop方法,加上要删除的元素的索引作为参数,例如:my_list.pop(index),将删除列表 my_list 中索引为 index 的元素,并返回该元素的值。
  • 如果不指定索引,默认删除并返回列表中最后一个元素。返回删除的元素值。
a = [1,2,3,4,5]
del a[3]
print(a)
a.remove(1)
print(a)
a.pop(1)
print(a)
#输出
[1, 2, 3, 5]
[2, 3, 5]
[2, 5]

python set 底层实现

在 Python 中,set 是一种无序且不重复的集合数据类型。set 的底层实现是基于哈希表(Hash Table),具体是通过哈希函数将元素映射到哈希表的槽位上。
当我们向 set 中添加元素时,Python 会通过哈希函数计算元素的哈希值,并将其放入相应的槽位上。如果多个元素计算的哈希值相同,即发生了哈希冲突,Python 会使用开放定址法或链表解决冲突,将冲突的元素存储在槽位上的一个链表或其他数据结构中。
由于基于哈希表的实现,set 具有以下特点:

  • 元素的添加、删除和查找操作的平均时间复杂度是 O(1)。
  • set中的元素是无序的,每次遍历的顺序可能不同。
  • set中的元素不重复,重复添加的元素只会保留一个。
  • set不支持索引操作,因为元素的存储位置不是固定的。

python 字典和 set() 的区别

在Python中,字典(Dictionary)和set是两种不同的数据类型,它们之间有以下区别:

  • 存储结构:字典是键值对的集合,每个键与一个值关联;而 set 是元素的集合,没有键值对的概念。
  • 唯一性:字典中的键是唯一的,每个键对应一个值,但不同的键可以有相同的值;set 中的元素是唯一的,不允许重复出现。
  • 访问方式:通过键可以直接访问字典中的值,例如 my_dict[key];而在 set 中只能通过遍历或使用in运算符来判断一个元素是否存在于集合中。
  • 可变性:字典是可变的数据类型,可以进行增加、删除、修改操作;set 也是可变的,可以添加、删除元素。
  • 有序性:字典中的元素是无序的,存储顺序和添加顺序可能不同;set 中的元素也是无序的。

__init__ 和 __new__ 和 __call__ 的区别

__new__ 方法用于对象的创建,__init__ 方法用于对象的初始化。 __new__ 方法在对象创建前被调用,__init__ 方法在对象创建后被调用。 __call__ 方法用于将对象作为函数进行调用,可以赋予对象函数的行为。

python 在内存上做了哪些优化?

Python在内存上的一些优化技术:

  • 引用计数:Python使用引用计数来追踪对象的引用数量。这种机制能够快速确定对象是否不再被引用,并及时回收内存。引用计数是一种轻量级的内存管理技术,可以快速处理对象的引用计数变化。
  • 垃圾回收:除了引用计数,Python还实现了垃圾回收机制来处理循环引用等特殊情况。Python使用标记-清除算法(Mark and Sweep)和分代回收算法(Generational Garbage Collection)来回收不再使用的对象,释放内存空间。
  • 内存池:Python通过内存池管理器(Memory Pool Manager)来分配内存。内存池是一种预先分配一定大小的内存块的技术,在对象创建和销毁时可以更高效地管理内存。内存池可以减少内存碎片和减轻内存分配的开销。
  • 内存共享:对于一些不可变对象(如小整数、字符串等),Python会进行内存共享。多个变量引用相同的不可变对象时,它们可以共享相同的内存空间,从而节约内存。
  • 迭代器和生成器:Python中的迭代器和生成器可以节省大量内存,特别是处理大型数据集时。它们按需生成数据,而不是一次性将所有数据加载到内存中。
  • 字符串驻留机制:对于一些短字符串,Python会将其驻留(intern)在内存中,即多个变量引用相同的字符串对象。这种机制可以减少相同字符串的内存使用。

python传值还是传址

传值:在C++中,传值就是把一个参数的值给这个函数,其中的更改不会影响原来的值。
传址:即传引用,直接把这个参数的内存地址传递进去,直接去这个内存地址上进行修改。
但是这些在Python中都没有,Python个只有可变对象和不可变对象。
Python中的传参都是传递对象的引用。
不可变对象相当于传值,不会对原来的对象进行修改。

什么是猴子补丁?

猴子补丁(Monkey Patching)是指在运行时动态修改已有的代码,通常是在不修改原始代码的情况下添加、替换或修改现有的功能。
在Python中,由于Python的动态特性,我们可以在运行时修改类、模块或对象的行为,以满足特定的需求或修复问题。这种修改的方式就被称为猴子补丁。

Python 中的 is 和 == 有什么区别?

is 比较的是两个对象的 id 值是否相等,也就是比较两个对象是否为同一个实例对象,是否指向同一个内存地址
== 比较的是两个对象的内容是否相等,默认会调用对象的 __eq__() 方法。

gbk 和 utf8 的区别

GBK是在国家标准GB2312基础上扩容后兼容GB2312的标准。GBK编码专门用来解决中文编码的,是双字节的。不论中英文都是双字节的。
UTF-8 编码是用以解决国际上字符的一种多字节编码,它对英文使用8位(即一个字节),中文使用24位(三个字节)来编码。对于英文字符较多的论坛则用UTF-8 节省空间。另外,如果是外国人访问你的GBK网页,需要下载中文语言包支持。访问UTF-8编码的网页则不出现这问题。可以直接访问。
GBK包含全部中文字符;UTF-8则包含全世界所有国家需要用到的字符。

__init__.py 文件的作用以及意义

这个文件定义了包的属性和方法,它可以什么也不定义;可以只是一个空文件,但是必须存在。 如果 __init__.py 不存在,这个目录就仅仅是一个目录,而不是一个包,它就不能被导入或者包含其它的模块和嵌套包。
或者可以这样理解。这样,当我们导入这个包的时候,__init__.py 文件自动运行。帮我们导入了这么多个模块,我们就不需要将所有的 import 语句写在一个文件里了,也可以减少代码量。

参考资料

23. Python – 牛客网
python中函数参数引用之传值/传址和copy/deepcopy-CSDN博客
【python】__init__.py文件到底是什么? – 知乎

来自山东
文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇