引言

有些面试官往往喜欢考察一些相对冷门的编程语言知识点,以此来推断求职者的编程功底。

因此,本文整理了一些python语言中较容易忽视的一些内容,作为知识拓展。

本文将回答以下问题:

  • 1.*arg**kwarg 作用是什么?
  • 2.迭代器和生成器是什么,有什么区别?
  • 3.return 和 yield 有什么区别?
  • 4.CPython 是什么?
  • 5.CPython 的垃圾回收是怎么做的?
  • 6.装饰器是什么,本质有什么作用?
  • 7.懒加载是什么?
  • 8.单例模式和工厂模式有什么区别?
  • 9.反射是什么?
  • 10.Thread有哪些基本操作?
  • 11.await 和 async 有什么作用?
  • 12.列表、字典、集合的底层类型是什么?

1. *arg**kwarg 作用是什么?

*args**kwargs 是 Python 里函数定义时常见的“可变参数”写法,用来让函数传入不定数量的实参,具体作用是:

语法作用
*args接收任意个 位置参数,打包成元组
**kwargs接收任意个 关键字参数,打包成字典

示例:

def demo(*args, **kwargs):
    print("args:", args)      # 元组
    print("kwargs:", kwargs)  # 字典

demo(1, 2, 3, name="Alice", age=25)
# 输出:
# args: (1, 2, 3)
# kwargs: {'name': 'Alice', 'age': 25}

2. 迭代器和生成器是什么?

迭代器是实现了迭代器协议的对象,用__iter____next__魔术方法表示协议[1]。

  • __iter__(): 返回自身
  • __next__(): 返回下一个值,没值时抛 StopIteration
class Fib(object):
    """迭代器"""
    
    def __init__(self, num):
        self.num = num
        self.a, self.b = 0, 1
        self.idx = 0
   
    def __iter__(self):
        return self

    def __next__(self):
        if self.idx < self.num:
            self.a, self.b = self.b, self.a + self.b
            self.idx += 1
            return self.a
        raise StopIteration()

生成器是语法简化版的迭代器。

def fib(num):
    """生成器"""
    a, b = 0, 1
    for _ in range(num):
        a, b = b, a + b
        yield a

3. return 和 yield 有什么区别?

return比较常见,结束整个函数并返回一个值。

yield则是暂停函数,把值“产出”出去,下次再从此行后继续,使函数变成生成器。

示例对比:

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

print(add(2, 3))  # 5
print(add(2, 3))  # 再次调用重新运行整个函数

一旦执行到 return,函数彻底结束。

def count_up(n):
    i = 0
    while i < n:
        yield i   # 暂停并返回 i
        i += 1

gen = count_up(3)   # 返回生成器对象
print(next(gen))    # 0
print(next(gen))    # 1
print(next(gen))    # 2
# 再 next 会 StopIteration

每次next()执行到yield就暂停,保留局部变量和指令位置。

4. CPython 是什么?

CPython 是用 C 语言写成的 Python 解释器,也是 Python 官方的解释器实现。

CPython 能够把 .py 源码编译成字节码(.pyc),然后用解释器执行。

除此之外,还有其它 Python 解释器版本,对比如下表所示:

实现语言特性
CPythonC官方、最广泛使用
PyPyRPythonJIT 加速,通常运行更快
JythonJava运行在 JVM,可直接调用 Java 库
IronPythonC#运行在 .NET 上
MicroPythonC面向嵌入式设备

5. CPython 的垃圾回收是怎么做的?

在 CPython 里,“垃圾回收”主要靠引用计数两套机制配合**:

1. 引用计数(Reference Counting)

  • 每个对象里有个 ob_refcnt 计数器,记录当前有多少地方在引用它。
  • 任何赋值、传参、容器保存都会让计数 +1;引用关系解除时 -1。
  • 计数降到 0 说明没人再用,解释器立即调用析构、释放内存。
import sys

x = []                       # 创建空列表
print(sys.getrefcount(x))    # 引用计数(额外+1因为getrefcount也传参了)

y = x                        # 再次引用
del x                        # 计数 -1
del y                        # 计数归 0,立即释放

缺点:循环引用无法清理(两个对象互相指着对方,计数永远 >0)。

2. 分代垃圾回收(Generational GC)

为解决循环引用问题,CPython 在 gc 模块里实现了 分代垃圾回收

  • 对象分代:0 代(新生)、1 代、2 代。存活越久,代数越高。
  • 触发条件:当某代分配-释放次数差值超阈值时触发扫描。
  • 扫描规则:标记仍有外部可达性的对象,剩下的就是“仅互相引用”的循环,直接释放。
import gc

print(gc.get_threshold())  # (700, 10, 10) 默认 700 次分配触发 0 代收集
gc.collect()               # 手动触发完整收集

3. 整体流程

  1. 创建对象 → ob_refcnt = 1
  2. 多处引用、释放 → 实时增减计数
  3. 计数归零 → 立即销毁
  4. 周期性 → 分代 GC 扫描,发现循环 → 强制回收

6. 装饰器是什么?

装饰器本质是一个函数(或类)接收器,它把目标函数作为参数并返回一个新函数,从而在不改源代码的前提下,为目标函数动态增加功能。常见场景有日志、权限控制、缓存等。

以下是一个日志装饰器的示例用法:

def logger(func):
    def wrapper(*args, **kwargs):
        print(f"[LOG] 调用 {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@logger          # 等价于 say_hello = logger(say_hello)
def say_hello():
    print("Hello")

say_hello()

除了自定义装饰器外,python还内置了以下三种比较常用的装饰器。

装饰器用途
@staticmethod定义静态方法,不需要 self
@classmethod定义类方法,首参是 cls
@property把方法变成只读属性

@staticmethod能够通过不用创建示例就调用函数。

class Tool:
    @staticmethod
    def add(a, b):
        return a + b

print(Tool.add(3, 5))  # 8

@classmethod装饰的函数能够通过传入cls直接对类变量进行操作。

class User:
    count = 0  # 类变量,所有实例共享

    @classmethod
    def inc(cls):
        cls.count += 1  # 通过cls访问类变量

User.inc()         # 直接对类操作,不需要实例
print(User.count)  # 1

@property能够让实例像访问属性一样去调用方法。

class Person:
    def __init__(self, age):
        self._age = age

    @property
    def age(self):
        return self._age

p = Person(20)
print(p.age)  # 20,像访问属性一样

7. 懒加载是什么?

“懒加载”(Lazy Loading)是一个设计思想,意思是:需要用到某个资源/对象时再去创建或加载,而不是一开始就占用资源。

下面是一个代理懒加载的示例,初始化对象时先用代理ProxyImage,调用实际方法时,再加载真实类。

class RealImage:
    def __init__(self, filename):
        self.filename = filename
        print(f"加载图片 {filename}")

    def display(self):
        print(f"显示 {self.filename}")

class ProxyImage:
    def __init__(self, filename):
        self.filename = filename
        self.real_image = None

    def display(self):
        if self.real_image is None:
            self.real_image = RealImage(self.filename)
        self.real_image.display()

img = ProxyImage("big_picture.jpg")
img.display()  # 第一次调用才加载图片

8. 单例模式和工厂模式有什么区别?

单例模式保证一个类全局只有一个实例;

工厂模式提供一个统一接口创建对象,让客户端不依赖具体类。

单例模式示例,使用__new__创建唯一对象。

class Singleton:
    _instance = None
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

a = Singleton()
b = Singleton()
print(a is b)  # True,全局唯一

应用场景:配置管理、日志对象、数据库连接池。

工厂模式示例:

class Circle:
    def draw(self): print("画圆")

class Square:
    def draw(self): print("画方")

class ShapeFactory:
    @staticmethod
    def create_shape(type_):
        if type_ == "circle":
            return Circle()
        elif type_ == "square":
            return Square()

shape = ShapeFactory.create_shape("circle")
shape.draw()  # 画圆

应用场景:系统需要动态创建不同类型对象,客户端不想依赖具体类。

9. 反射是什么?

反射(Reflection)是指程序在运行时检查和操作对象信息的能力。

反射能实现以下功能:

  • 获取对象类型、属性、方法
  • 动态调用方法 / 访问变量
  • 动态修改、增加属性
  • 按名字加载模块、类

常用的反射相关函数:

函数 / 关键字作用
hasattr(obj, "name")判断对象是否有某属性 / 方法
getattr(obj, "name", default)按名字获取属性 / 方法
setattr(obj, "name", value)动态设置属性
delattr(obj, "name")删除属性
type(obj)获取类型
dir(obj)列出对象可用成员
globals() / locals()获取全局 / 局部命名空间
__import__("module")按字符串导入模块

使用示例:

class User:
    def __init__(self, name):
        self.name = name
    def greet(self):
        print(f"Hello {self.name}")

u = User("Alice")

# 运行时动态调用 greet
if hasattr(u, "greet"):
    method = getattr(u, "greet")  # 获取方法对象
    method()  # 执行 -> Hello Alice

# 动态添加属性
setattr(u, "age", 25)
print(u.age)  # 25

10. Thread有哪些基本操作?

Thread类提供了以下函数用来处理多线程操作。

函数关键点典型场景
Lock一次只让一个线程进保护共享资源
RLock同一线程可重复获取递归、嵌套调用
Condition等条件满足再唤醒指定线程生产者/消费者
Event广播式开关,全体等待线程同时解除一次性信号、任务启动
Semaphore限制最大并发数连接池、下载并发
Barrier一批线程到齐再统一放行阶段同步、分布式并行场景

1. Lock(互斥锁)

  • 作用:一次只允许一个线程进入“临界区”
  • 特点:最简单,只有 acquire() / release() 两个动作
import threading

lock = threading.Lock()
count = 0

def task():
    global count
    with lock:           # 等价于 acquire+release
        tmp = count
        count = tmp + 1  # 这里同一时刻只有一个线程能改

2. RLock(可重入锁)

  • 作用:允许同一线程多次 acquire,内部有个计数器,acquire 多少次,就要 release 多少次才能真正释放
rlock = threading.RLock()

rlock.acquire()
rlock.acquire()    # OK,同一线程允许重入
print("拿了两次锁")
rlock.release()
rlock.release()    # 计数归零,锁真正释放

3. Condition(条件变量)

  • 作用:比 Lock 多了一个“等条件成立再通知”的机制
  • 场景:生产者-消费者模型
cond = threading.Condition()
items = []

def producer():
    with cond:
        items.append("data")
        cond.notify()       # 通知等待的消费者

def consumer():
    with cond:
        while not items:
            cond.wait()     # 等待生产者唤醒
        print(items.pop())

4. Event

  • 作用一个线程发信号,其它等待的线程全都被唤醒
  • 特点:只有两种状态:set()(开)/clear()(关)
evt = threading.Event()

def waiter():
    print("wait for signal…")
    evt.wait()            # 阻塞直到set
    print("got signal")

def sender():
    evt.set()             # 发信号,全体wait()解除阻塞

5. Semaphore(信号量)

  • 作用:允许最多 N 个线程同时进入临界区
  • 场景:限制并发量
sem = threading.Semaphore(3)  # 最多允许3个线程并行

def worker():
    with sem:
        # 同时最多3个worker在干活
        pass

6. Barrier(栅栏/屏障)

  • 作用:让一批线程“集合完毕再一起出发”
  • 特点:所有线程都 wait(),等数量到齐才放行
bar = threading.Barrier(3)

def job():
    print("ready")
    bar.wait()        # 阻塞直到3个线程都到了
    print("go!")

11. awaitasync有什么作用?

asyncawait 是协程和异步编程的核心关键字:

async 用来定义协程函数,await 用来挂起协程等待耗时操作完成,从而不阻塞事件循环。

协程属于单线程并发,能够提高 IO 效率,节省线程开销。

协程函数和普通函数的区别如下表所示:

特性普通函数协程函数 (async def)
调用返回函数返回值协程对象(需 await 才执行)
是否可挂起✅(用 await 等异步操作)
用途CPU 密集计算IO 密集操作(网络、文件、数据库)

使用协程读写文件的示例:

import asyncio
import aiofiles

async def write_file(filename, content):
    async with aiofiles.open(filename, 'w') as f:
        await f.write(content)  # 异步写入文件

async def read_file(filename):
    async with aiofiles.open(filename, 'r') as f:
        data = await f.read()   # 异步读取文件
        print(data)

def main():
    loop = asyncio.get_event_loop()  # 获取事件循环
    tasks = [
        write_file("test.txt", "Hello, asyncio!"),
        read_file("test.txt")
    ]
    loop.run_until_complete(asyncio.gather(*tasks))  # 并发执行协程
    loop.close()

if __name__ == "__main__":
    main()

12. 列表、字典、集合的底层类型是什么?

列表(list)、字典(dict)、集合(set)的底层类型和操作复杂度如下表所示:

数据结构底层类型随机访问插入/删除查找内存特点
list动态数组O(1)O(n)O(n)内存连续,扩容慢
dict哈希表N/AO(1)O(1)内存分散,哈希冲突解决
set哈希表(key)N/AO(1)O(1)内存分散,哈希冲突解决

参考

[1] Python-100-Days:https://github.com/jackfrued/Python-100-Days/blob/master/Day31-35/31.Python%E8%AF%AD%E8%A8%80%E8%BF%9B%E9%98%B6.md

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐