《对线面试官》| 高频 Python 面试题 pt.1
1.聊聊 python 中的值传递和引用传递吧
- 值传递:
值传递意味着在函数调用时,将实际参数的值复制一份传递给函数的形式参数
在函数内部,形式参数将作为局部变量使用,对形式参数的修改不会影响原始变量的值
- 引用传递
引用传递意味着在函数调用时,将实际参数的引用(内存地址)传递给函数的形式参数
在函数内部,形式参数与原始变量指向同一个内存地址,因此对形式参数的修改也会影响原始变量的值
- 总结
需要注意的是,Python 中的参数传递方式实际上都是对象的引用传递
但是对于不可变对象,由于其值无法修改,所以看起来表现为值传递;而对于可变对象,由于其值可以修改,所以表现为引用传递
2.什么是 Python 自省
自省(introspection)指的是程序能够在运行时检查对象的类型、属性和方法等信息的能力
Python 是一门具有强大自省能力的语言,它可以在运行时动态地获取和操作对象的信息
Python 中的自省可以通过以下方式实现:
type()
函数:用于获取对象的类型。dir()
函数:用于列出对象的所有属性和方法。hasattr()
、getattr()
和setattr()
函数:用于检查、获取和设置对象的属性。isinstance()
函数:用于判断对象是否属于指定类型。callable()
函数:用于检查对象是否可调用(即是否是函数或方法)。- 函数的
.__code__
属性:用于获取函数的字节码对象,从而可以进一步分析函数的信息。 - 类的
.__dict__
属性:用于获取类的所有属性和方法。
3.python 中单下划线和双下划线的区别
- 单下划线
1)在变量或函数名前加上单下划线 _
表示这是一个私有的变量或函数,这意味着该变量或函数不应该在类外部直接访问(虽然不会强制限制访问,但这个算是一种约定)
class MyClass:
def __init__(self):
self._private_var = 42
def _private_method(self):
print("This is a private method.")
2)还有一种用途就是占位符,有时候在 for 循环中,我们只关心序列中的某个元素,而不需要使用序列中其他元素或者序列的索引
这时候可以用单下划线作为变量名,表示这个变量不会被使用
numbers = [1, 2, 3, 4, 5]
for _ in numbers:
print("Hello")
- 双下划线
1)在类中定义的双下划线变量,这种变量被称为类的私有变量
它们不会被继承,也不能在类外部直接访问。但是在类内部可以通过self.__变量名
进行访问
class Parent:
def __init__(self):
self.__private_var = 42
def __private_method(self):
print("This is a private method.")
2)在变量名前加上双下划线 __
会触发 Python 的名称改写(name mangling)机制,将变量名改写为_类名__变量名
的形式
即如果 Test 类里有一成员 __x
,那么 dir(Test)
时会看到 _Test__x
而非 __x
。这是为了避免该成员的名称与子类中的名称冲突。但要注意这要求该名称末尾没有下划线
3)双下划线开头双下划线结尾的是一些 Python 的“魔术”对象,如类成员的 __init__
、__del__
、__add__
、__getitem__
等,以及全局的 __file__
、__name__
等
官方建议不要将这样的命名方式应用于自己的变量或函数
4.迭代器和生成器的区别
Python 中的迭代器(Iterators)和生成器(Generators)都可用于处理可迭代对象
- 迭代器(Iterators)
迭代器是实现了迭代协议的对象,通过 __iter__()
和 __next__()
方法进行迭代
__iter__()
方法返回迭代器对象本身,而 __next__()
方法返回下一个元素,需要手动调用 next()
方法来获取下一个元素
迭代器对象可以用于遍历一个序列或者集合,每次调用 __next__()
方法时,会返回序列中的下一个元素,当没有元素可返回时,会引发 StopIteration
异常
numbers = [1, 2, 3, 4, 5]
iterator = iter(numbers)
print(next(iterator)) # 输出 1
print(next(iterator)) # 输出 2
print(next(iterator)) # 输出 3
Python 的 for
循环背后会自动处理可迭代对象,从中获取一个迭代器,并使用迭代器来逐个获取元素。
- 生成器(Generators)
生成器是一种特殊的迭代器,可以通过函数来创建,当函数中包含 yield
关键字时,该函数就成为生成器函数
成器函数执行到 yield
语句时,会将结果返回给调用者,但并不会终止函数的执行
下次调用生成器的 __next__()
方法时,函数会从上一次 yield
语句处继续执行,直到再次遇到 yield
或者函数结束
def number_generator():
yield 1
yield 2
yield 3
yield 4
yield 5
generator = number_generator()
print(next(generator)) # 输出 1
print(next(generator)) # 输出 2
print(next(generator)) # 输出 3
5.*args 和 **kwargs 区别
请看我这篇文章
6.什么是 GIL
GIL(全局解释器锁)是 Python 解释器为了保证多线程情况下解释器的稳定性而引入的一种机制
在 CPython 解释器中,由于解释器的内存管理并不是线程安全的,为了避免多线程情况下的数据竞争和死锁等问题,引入了 GIL
GIL 的作用是确保在同一时刻只有一个线程在解释器中执行字节码。这意味着在多线程程序中,Python 解释器的执行是单线程的
当一个线程获取了 GIL 后,其他线程必须等待该线程释放 GIL 才能继续执行。这样虽然能够保证解释器的稳定性,但也导致了在多核 CPU 上,Python 的多线程程序并不能充分利用多核资源
对于 CPU 密集型任务(例如计算密集型的循环操作),多线程并不能带来性能的提升
但是对于 I/O 密集型任务(例如网络请求、文件读写),多线程可以在等待 I/O 的时间内执行其他任务,提高了整体的效率
7.协程是什么
在 Python 中,协程是一种轻量级的并发编程技术,它允许程序在同一个线程内实现多个协程之间的切换,从而实现非阻塞的并发执行
协程与传统的多线程或多进程并发模型不同,它不依赖于操作系统的线程或进程来实现并发,而是完全由 Python 解释器内部的事件循环机制来控制协程的执行
也就是说可以由用户来管理自己内核态—用户态切换的时机
Python里最常见的 yield 就是协程的思想
一个简单的案例
import asyncio
# 定义一个协程函数
async def greet():
print("Hello")
await asyncio.sleep(1) # 模拟耗时操作,让出执行权
print("World")
# 获取事件循环对象
loop = asyncio.get_event_loop()
# 运行协程函数
loop.run_until_complete(greet())