目录
- 专栏导读
- 前言
- 1. 字典的内存管理机制是什么?
- 2. 列表的内存管理机制是什么?
- 3. 元组和列表的区别
- 4. 字符串插值的方法
- 5. 闭包、装饰器的原理
- 闭包(Closure)
- 装饰器(Decorator)
- 6. map、filter的区别
- 7. range()函数的用法
- 8. __new__方法和__init__方法
- 9. 比较:is vs ==
- 10. any和all
- 11. with语句(上下文管理器)
- 12. 迭代器、生成器的区别
- 迭代器(Iterator)
- 生成器(Generator)
- 13. 打乱一个列表的元素
- 14. 将元组变为字典
- 15. JSON序列化
- 16. Python中的传参形式
- 17. 如何修改全局变量
- 18. Python中的递归
- 19. 深拷贝和浅拷贝
- 20. *args和**kwargs的含义
- 21. 什么是单例模式
- 22. 什么是多态
- 23. 解释一下模块和包
- 模块(Module)
- 包(Package)
- 24. 什么是asyncio?它有什么用途?
- 25. 什么是协程?
- 26. 协程和线程的区别
- 27. Future和Task的区别
- 28. Python中的元类(Metaclass)
- 29. Python中的描述符(Descriptor)
- 30. Python中的上下文变量(Context Variables)
- 总结
- 重点掌握的概念:
- 面试建议:
- 结尾
专栏导读
🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手
🏳️🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注
👍 该系列文章专栏:请点击——>Python办公自动化专栏求订阅
🕷 此外还有爬虫专栏:请点击——>Python爬虫基础专栏求订阅
📕 此外还有python基础专栏:请点击——>Python基础学习专栏求订阅
文章作者技术和水平有限,如果文中出现错误,希望大家能指正🙏
❤️ 欢迎各位佬关注! ❤️
前言
本文汇总了Python面试中最常见的基础知识问题,涵盖了数据结构、内存管理、面向对象、异步编程等多个方面。这些问题在Python面试中出现频率极高,掌握这些知识点对于通过面试至关重要。
1. 字典的内存管理机制是什么?
核心概念:
-
Python字典使用哈希表(Hash Table)实现
-
采用开放寻址法解决哈希冲突
-
动态扩容机制
import sys# 字典的内存分配示例
my_dict = {}
for i in range(10):my_dict[i] = i * 2print(f"元素数量: {len(my_dict)}, 内存大小: {sys.getsizeof(my_dict)}")
关键特点:
-
负载因子控制在2/3左右时触发扩容
-
Python 3.7+保证插入顺序
-
键必须是不可变对象
2. 列表的内存管理机制是什么?
核心概念:
-
基于动态数组实现
-
预分配额外空间以减少频繁扩容
-
扩容策略:通常按1.5倍或2倍增长
import sys# 列表扩容机制演示
my_list = []
for i in range(20):my_list.append(i)print(f"长度: {len(my_list)}, 容量估算: {sys.getsizeof(my_list)}")
内存特点:
-
连续内存存储,支持随机访问
-
末尾操作O(1),中间操作O(n)
-
删除元素时不会立即缩容
3. 元组和列表的区别
特性 | 列表(List) | 元组(Tuple) |
---|---|---|
可变性 | 可变 | 不可变 |
语法 | [1, 2, 3] | (1, 2, 3) |
性能 | 较慢 | 较快 |
内存占用 | 较大 | 较小 |
用途 | 动态数据 | 固定数据 |
# 性能对比
import timeitlist_time = timeit.timeit('x = [1, 2, 3, 4, 5]', number=1000000)
tuple_time = timeit.timeit('x = (1, 2, 3, 4, 5)', number=1000000)print(f"列表创建时间: {list_time}")
print(f"元组创建时间: {tuple_time}")
4. 字符串插值的方法
name = "Alice"
age = 25
score = 95.5# 方法1:% 格式化(旧式)
result1 = "姓名: %s, 年龄: %d, 分数: %.1f" % (name, age, score)# 方法2:str.format()方法
result2 = "姓名: {}, 年龄: {}, 分数: {:.1f}".format(name, age, score)
result3 = "姓名: {name}, 年龄: {age}, 分数: {score:.1f}".format(name=name, age=age, score=score)# 方法3:f-string(推荐,Python 3.6+)
result4 = f"姓名: {name}, 年龄: {age}, 分数: {score:.1f}"# 方法4:Template字符串
from string import Template
template = Template("姓名: $name, 年龄: $age")
result5 = template.substitute(name=name, age=age)
5. 闭包、装饰器的原理
闭包(Closure)
def outer_function(x):def inner_function(y):return x + y # 访问外部函数的变量return inner_function# 创建闭包
add_10 = outer_function(10)
print(add_10(5)) # 输出: 15# 检查闭包
print(add_10.__closure__) # 显示闭包变量
装饰器(Decorator)
import functools
import time# 简单装饰器
def timer(func):@functools.wraps(func)def wrapper(*args, **kwargs):start = time.time()result = func(*args, **kwargs)end = time.time()print(f"{func.__name__} 执行时间: {end - start:.4f}秒")return resultreturn wrapper# 带参数的装饰器
def repeat(times):def decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):for _ in range(times):result = func(*args, **kwargs)return resultreturn wrapperreturn decorator@timer
@repeat(3)
def greet(name):print(f"Hello, {name}!")
6. map、filter的区别
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]# map: 对每个元素应用函数,返回新的迭代器
squares = list(map(lambda x: x**2, numbers))
print(f"平方: {squares}") # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]# filter: 过滤满足条件的元素
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(f"偶数: {even_numbers}") # [2, 4, 6, 8, 10]# 等价的列表推导式
squares_comp = [x**2 for x in numbers]
even_comp = [x for x in numbers if x % 2 == 0]# 性能对比
import timeit
map_time = timeit.timeit(lambda: list(map(lambda x: x**2, range(1000))), number=1000)
comp_time = timeit.timeit(lambda: [x**2 for x in range(1000)], number=1000)
7. range()函数的用法
# 基本用法
print(list(range(5))) # [0, 1, 2, 3, 4]
print(list(range(2, 8))) # [2, 3, 4, 5, 6, 7]
print(list(range(0, 10, 2))) # [0, 2, 4, 6, 8]
print(list(range(10, 0, -1))) # [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]# range对象特性
r = range(1000000)
print(type(r)) # <class 'range'>
print(sys.getsizeof(r)) # 内存占用很小,惰性求值
print(500000 in r) # O(1) 时间复杂度的成员检测# 实际应用
for i in range(3):print(f"第 {i+1} 次循环")# 与enumerate结合
fruits = ['apple', 'banana', 'orange']
for index, fruit in enumerate(fruits):print(f"{index}: {fruit}")
8. __new__方法和__init__方法
class Person:def __new__(cls, name, age):print(f"__new__ 被调用,创建实例")instance = super().__new__(cls)return instancedef __init__(self, name, age):print(f"__init__ 被调用,初始化实例")self.name = nameself.age = age# 单例模式示例
class Singleton:_instance = Nonedef __new__(cls):if cls._instance is None:cls._instance = super().__new__(cls)return cls._instancedef __init__(self):if not hasattr(self, 'initialized'):self.initialized = Trueprint("Singleton 初始化")# 测试
person = Person("Alice", 25)
s1 = Singleton()
s2 = Singleton()
print(s1 is s2) # True
关键区别:
__new__
:负责创建实例,返回实例对象__init__
:负责初始化实例,无返回值__new__
先于__init__
执行
9. 比较:is vs ==
# == 比较值是否相等
# is 比较身份(内存地址)是否相同a = [1, 2, 3]
b = [1, 2, 3]
c = aprint(a == b) # True,值相等
print(a is b) # False,不是同一个对象
print(a is c) # True,是同一个对象# 小整数缓存
x = 256
y = 256
print(x is y) # True,小整数被缓存m = 257
n = 257
print(m is n) # False(在某些情况下可能是True)# 字符串驻留
str1 = "hello"
str2 = "hello"
print(str1 is str2) # True,字符串驻留# None的比较
value = None
print(value is None) # 推荐
print(value == None) # 不推荐
10. any和all
# any(): 任意一个为True则返回True
print(any([True, False, False])) # True
print(any([False, False, False])) # False
print(any([])) # False(空序列)# all(): 所有元素为True才返回True
print(all([True, True, True])) # True
print(all([True, False, True])) # False
print(all([])) # True(空序列)# 实际应用
numbers = [2, 4, 6, 8, 10]# 检查是否所有数字都是偶数
all_even = all(num % 2 == 0 for num in numbers)
print(f"所有数字都是偶数: {all_even}")# 检查是否有负数
has_negative = any(num < 0 for num in numbers)
print(f"包含负数: {has_negative}")# 验证用户输入
def validate_user_data(data):required_fields = ['name', 'email', 'age']return all(field in data and data[field] for field in required_fields)user_data = {'name': 'Alice', 'email': 'alice@example.com', 'age': 25}
print(validate_user_data(user_data)) # True
11. with语句(上下文管理器)
# 文件操作
with open('example.txt', 'w') as f:f.write('Hello, World!')
# 文件自动关闭,即使发生异常# 自定义上下文管理器
class Timer:def __enter__(self):self.start = time.time()print("计时开始")return selfdef __exit__(self, exc_type, exc_val, exc_tb):self.end = time.time()print(f"计时结束,耗时: {self.end - self.start:.4f}秒")return False # 不抑制异常with Timer():time.sleep(1)print("执行一些操作")# 使用contextlib
from contextlib import contextmanager@contextmanager
def database_transaction():print("开始事务")try:yield "数据库连接"except Exception as e:print(f"回滚事务: {e}")raiseelse:print("提交事务")finally:print("关闭连接")with database_transaction() as db:print(f"使用 {db} 执行操作")
12. 迭代器、生成器的区别
迭代器(Iterator)
class NumberIterator:def __init__(self, max_num):self.max_num = max_numself.current = 0def __iter__(self):return selfdef __next__(self):if self.current < self.max_num:self.current += 1return self.currentraise StopIteration# 使用迭代器
for num in NumberIterator(5):print(num) # 1, 2, 3, 4, 5
生成器(Generator)
# 生成器函数
def number_generator(max_num):current = 0while current < max_num:current += 1yield current# 生成器表达式
squares_gen = (x**2 for x in range(10))# 内存效率对比
import syslist_comp = [x**2 for x in range(1000)]
gen_exp = (x**2 for x in range(1000))print(f"列表推导式内存: {sys.getsizeof(list_comp)} bytes")
print(f"生成器表达式内存: {sys.getsizeof(gen_exp)} bytes")# 斐波那契数列生成器
def fibonacci():a, b = 0, 1while True:yield aa, b = b, a + bfib = fibonacci()
for _ in range(10):print(next(fib), end=' ') # 0 1 1 2 3 5 8 13 21 34
主要区别:
- 迭代器:实现
__iter__
和__next__
方法的对象 - 生成器:使用
yield
关键字的函数,自动实现迭代器协议 - 生成器更简洁,内存效率更高
13. 打乱一个列表的元素
import random# 方法1:random.shuffle()(原地打乱)
original_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
random.shuffle(original_list)
print(f"打乱后: {original_list}")# 方法2:random.sample()(返回新列表)
original_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shuffled_list = random.sample(original_list, len(original_list))
print(f"原列表: {original_list}")
print(f"新列表: {shuffled_list}")# 方法3:手动实现Fisher-Yates算法
def manual_shuffle(lst):result = lst.copy()for i in range(len(result) - 1, 0, -1):j = random.randint(0, i)result[i], result[j] = result[j], result[i]return resultoriginal_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
shuffled = manual_shuffle(original_list)
print(f"手动打乱: {shuffled}")# 方法4:使用numpy(如果可用)
try:import numpy as nparr = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])np.random.shuffle(arr)print(f"numpy打乱: {arr.tolist()}")
except ImportError:print("numpy未安装")
14. 将元组变为字典
# 方法1:元组列表转字典
tuples_list = [('a', 1), ('b', 2), ('c', 3)]
dict1 = dict(tuples_list)
print(dict1) # {'a': 1, 'b': 2, 'c': 3}# 方法2:两个元组转字典
keys = ('name', 'age', 'city')
values = ('Alice', 25, 'Beijing')
dict2 = dict(zip(keys, values))
print(dict2) # {'name': 'Alice', 'age': 25, 'city': 'Beijing'}# 方法3:嵌套元组转字典
nested_tuple = (('x', 10), ('y', 20), ('z', 30))
dict3 = {k: v for k, v in nested_tuple}
print(dict3) # {'x': 10, 'y': 20, 'z': 30}# 方法4:单个元组转字典(索引作为键)
single_tuple = ('apple', 'banana', 'cherry')
dict4 = {i: v for i, v in enumerate(single_tuple)}
print(dict4) # {0: 'apple', 1: 'banana', 2: 'cherry'}# 方法5:复杂转换
data_tuple = (('user1', 'Alice', 25), ('user2', 'Bob', 30))
dict5 = {item[0]: {'name': item[1], 'age': item[2]} for item in data_tuple}
print(dict5) # {'user1': {'name': 'Alice', 'age': 25}, 'user2': {'name': 'Bob', 'age': 30}}
15. JSON序列化
import json
from datetime import datetime
import decimal# 基本序列化和反序列化
data = {'name': 'Alice','age': 25,'hobbies': ['reading', 'swimming'],'is_student': False
}# 序列化为JSON字符串
json_string = json.dumps(data, ensure_ascii=False, indent=2)
print(json_string)# 反序列化
parsed_data = json.loads(json_string)
print(parsed_data)# 文件操作
with open('data.json', 'w', encoding='utf-8') as f:json.dump(data, f, ensure_ascii=False, indent=2)with open('data.json', 'r', encoding='utf-8') as f:loaded_data = json.load(f)# 自定义序列化
class DateTimeEncoder(json.JSONEncoder):def default(self, obj):if isinstance(obj, datetime):return obj.isoformat()elif isinstance(obj, decimal.Decimal):return float(obj)return super().default(obj)complex_data = {'timestamp': datetime.now(),'price': decimal.Decimal('99.99')
}json_with_custom = json.dumps(complex_data, cls=DateTimeEncoder)
print(json_with_custom)# 处理不可序列化的对象
class Person:def __init__(self, name, age):self.name = nameself.age = agedef to_dict(self):return {'name': self.name, 'age': self.age}person = Person('Bob', 30)
person_json = json.dumps(person.to_dict())
print(person_json)
16. Python中的传参形式
# 位置参数
def greet(name, age):print(f"Hello, {name}! You are {age} years old.")greet("Alice", 25)# 关键字参数
greet(age=30, name="Bob")# 默认参数
def greet_with_default(name, age=18, city="Unknown"):print(f"{name}, {age} years old, from {city}")greet_with_default("Charlie")
greet_with_default("David", city="Shanghai")# 可变位置参数 *args
def sum_all(*args):return sum(args)print(sum_all(1, 2, 3, 4, 5)) # 15# 可变关键字参数 **kwargs
def print_info(**kwargs):for key, value in kwargs.items():print(f"{key}: {value}")print_info(name="Eve", age=28, city="Beijing")# 混合使用
def complex_function(required, default="default", *args, **kwargs):print(f"Required: {required}")print(f"Default: {default}")print(f"Args: {args}")print(f"Kwargs: {kwargs}")complex_function("必需参数", "自定义默认值", 1, 2, 3, name="Alice", age=25)# 强制关键字参数(Python 3+)
def force_keyword(a, b, *, c, d):return a + b + c + d# force_keyword(1, 2, 3, 4) # 错误
result = force_keyword(1, 2, c=3, d=4) # 正确# 仅位置参数(Python 3.8+)
def position_only(a, b, /, c, d):return a + b + c + dresult = position_only(1, 2, c=3, d=4) # 正确
# result = position_only(a=1, b=2, c=3, d=4) # 错误
17. 如何修改全局变量
# 全局变量
global_var = 10
global_list = [1, 2, 3]def modify_global():global global_var # 声明要修改全局变量global_var = 20print(f"函数内 global_var: {global_var}")def modify_global_list():# 可变对象可以直接修改内容global_list.append(4)print(f"函数内 global_list: {global_list}")def reassign_global_list():global global_list # 重新赋值需要global声明global_list = [10, 20, 30]print(f"重新赋值后 global_list: {global_list}")print(f"修改前 global_var: {global_var}")
modify_global()
print(f"修改后 global_var: {global_var}")print(f"修改前 global_list: {global_list}")
modify_global_list()
print(f"修改后 global_list: {global_list}")reassign_global_list()
print(f"最终 global_list: {global_list}")# 嵌套函数中的nonlocal
def outer_function():outer_var = 100def inner_function():nonlocal outer_var # 声明要修改外层函数的变量outer_var = 200print(f"内层函数中 outer_var: {outer_var}")print(f"修改前 outer_var: {outer_var}")inner_function()print(f"修改后 outer_var: {outer_var}")outer_function()# 使用globals()和locals()
def show_scopes():local_var = "local"print(f"Local variables: {locals()}")print(f"Global variables: {list(globals().keys())[-5:]}")show_scopes()
18. Python中的递归
# 经典递归:阶乘
def factorial(n):if n <= 1:return 1return n * factorial(n - 1)print(f"5! = {factorial(5)}") # 120# 斐波那契数列(低效递归)
def fibonacci_recursive(n):if n <= 1:return nreturn fibonacci_recursive(n-1) + fibonacci_recursive(n-2)# 优化:记忆化递归
def fibonacci_memo(n, memo={}):if n in memo:return memo[n]if n <= 1:return nmemo[n] = fibonacci_memo(n-1, memo) + fibonacci_memo(n-2, memo)return memo[n]# 使用functools.lru_cache装饰器
from functools import lru_cache@lru_cache(maxsize=None)
def fibonacci_cached(n):if n <= 1:return nreturn fibonacci_cached(n-1) + fibonacci_cached(n-2)# 递归遍历目录
import osdef list_files_recursive(directory, level=0):items = []try:for item in os.listdir(directory):item_path = os.path.join(directory, item)indent = " " * levelif os.path.isdir(item_path):items.append(f"{indent}{item}/")items.extend(list_files_recursive(item_path, level + 1))else:items.append(f"{indent}{item}")except PermissionError:items.append(f"{indent}[Permission Denied]")return items# 递归深度限制
import sys
print(f"默认递归深度限制: {sys.getrecursionlimit()}")# 尾递归优化(Python不支持,但可以手动转换为迭代)
def factorial_iterative(n):result = 1for i in range(1, n + 1):result *= ireturn result
19. 深拷贝和浅拷贝
import copy# 原始数据
original = {'name': 'Alice','scores': [85, 90, 78],'info': {'age': 25,'city': 'Beijing'}
}# 浅拷贝
shallow_copy = copy.copy(original)
# 或者
shallow_copy2 = original.copy()
shallow_copy3 = dict(original)# 深拷贝
deep_copy = copy.deepcopy(original)# 修改测试
print("=== 修改前 ===")
print(f"Original: {original}")
print(f"Shallow: {shallow_copy}")
print(f"Deep: {deep_copy}")# 修改嵌套对象
original['scores'].append(95)
original['info']['age'] = 26print("\n=== 修改后 ===")
print(f"Original: {original}")
print(f"Shallow: {shallow_copy}") # 嵌套对象被影响
print(f"Deep: {deep_copy}") # 嵌套对象不受影响# 列表的拷贝
original_list = [[1, 2, 3], [4, 5, 6]]# 浅拷贝方法
shallow1 = original_list.copy()
shallow2 = original_list[:]
shallow3 = list(original_list)# 深拷贝
deep_list = copy.deepcopy(original_list)# 修改测试
original_list[0].append(4)
print(f"\nOriginal list: {original_list}")
print(f"Shallow copy: {shallow1}") # 受影响
print(f"Deep copy: {deep_list}") # 不受影响# 自定义拷贝行为
class CustomClass:def __init__(self, value):self.value = valueself.data = [1, 2, 3]def __copy__(self):print("执行浅拷贝")new_obj = CustomClass(self.value)new_obj.data = self.data # 共享引用return new_objdef __deepcopy__(self, memo):print("执行深拷贝")new_obj = CustomClass(self.value)new_obj.data = copy.deepcopy(self.data, memo)return new_objoriginal_obj = CustomClass("test")
shallow_obj = copy.copy(original_obj)
deep_obj = copy.deepcopy(original_obj)
20. *args和**kwargs的含义
# *args:可变位置参数
def function_with_args(required_arg, *args):print(f"Required argument: {required_arg}")print(f"Additional arguments: {args}")print(f"Type of args: {type(args)}")for i, arg in enumerate(args):print(f" args[{i}]: {arg}")function_with_args("必需参数", "额外1", "额外2", "额外3")# **kwargs:可变关键字参数
def function_with_kwargs(required_arg, **kwargs):print(f"Required argument: {required_arg}")print(f"Keyword arguments: {kwargs}")print(f"Type of kwargs: {type(kwargs)}")for key, value in kwargs.items():print(f" {key}: {value}")function_with_kwargs("必需参数", name="Alice", age=25, city="Beijing")# 同时使用*args和**kwargs
def flexible_function(*args, **kwargs):print(f"Positional arguments: {args}")print(f"Keyword arguments: {kwargs}")flexible_function(1, 2, 3, name="Bob", age=30)# 参数顺序:位置参数 -> *args -> 关键字参数 -> **kwargs
def complete_function(pos1, pos2, *args, kw1="default", **kwargs):print(f"pos1: {pos1}, pos2: {pos2}")print(f"args: {args}")print(f"kw1: {kw1}")print(f"kwargs: {kwargs}")complete_function("a", "b", "c", "d", kw1="custom", extra="value")# 解包参数
def add_three_numbers(a, b, c):return a + b + cnumbers = [1, 2, 3]
result = add_three_numbers(*numbers) # 解包列表
print(f"Sum: {result}")data = {'a': 10, 'b': 20, 'c': 30}
result = add_three_numbers(**data) # 解包字典
print(f"Sum: {result}")# 实际应用:装饰器
def log_function_call(func):def wrapper(*args, **kwargs):print(f"调用函数 {func.__name__}")print(f"位置参数: {args}")print(f"关键字参数: {kwargs}")result = func(*args, **kwargs)print(f"返回值: {result}")return resultreturn wrapper@log_function_call
def calculate(x, y, operation="add"):if operation == "add":return x + yelif operation == "multiply":return x * yreturn 0calculate(5, 3, operation="multiply")
21. 什么是单例模式
# 方法1:使用__new__方法
class Singleton:_instance = None_initialized = Falsedef __new__(cls):if cls._instance is None:cls._instance = super().__new__(cls)return cls._instancedef __init__(self):if not self._initialized:self.value = 0self._initialized = True# 方法2:使用装饰器
def singleton(cls):instances = {}def get_instance(*args, **kwargs):if cls not in instances:instances[cls] = cls(*args, **kwargs)return instances[cls]return get_instance@singleton
class DatabaseConnection:def __init__(self):self.connection = "Connected to database"print("创建数据库连接")# 方法3:使用元类
class SingletonMeta(type):_instances = {}def __call__(cls, *args, **kwargs):if cls not in cls._instances:cls._instances[cls] = super().__call__(*args, **kwargs)return cls._instances[cls]class Logger(metaclass=SingletonMeta):def __init__(self):self.logs = []print("创建日志记录器")def log(self, message):self.logs.append(message)print(f"Log: {message}")# 方法4:模块级单例(推荐)
# config.py
class Config:def __init__(self):self.debug = Trueself.database_url = "sqlite:///app.db"# 创建单例实例
config = Config()# 测试单例模式
print("=== 测试Singleton类 ===")
s1 = Singleton()
s2 = Singleton()
print(f"s1 is s2: {s1 is s2}") # Trueprint("\n=== 测试DatabaseConnection ===")
db1 = DatabaseConnection()
db2 = DatabaseConnection()
print(f"db1 is db2: {db1 is db2}") # Trueprint("\n=== 测试Logger ===")
logger1 = Logger()
logger2 = Logger()
print(f"logger1 is logger2: {logger1 is logger2}") # True
logger1.log("第一条消息")
logger2.log("第二条消息")
print(f"logger1.logs: {logger1.logs}")
print(f"logger2.logs: {logger2.logs}")# 线程安全的单例
import threadingclass ThreadSafeSingleton:_instance = None_lock = threading.Lock()def __new__(cls):if cls._instance is None:with cls._lock:if cls._instance is None:cls._instance = super().__new__(cls)return cls._instance
22. 什么是多态
# 多态的基本概念:同一接口,不同实现
from abc import ABC, abstractmethod# 抽象基类
class Animal(ABC):@abstractmethoddef make_sound(self):pass@abstractmethoddef move(self):pass# 具体实现类
class Dog(Animal):def make_sound(self):return "汪汪"def move(self):return "跑步"class Cat(Animal):def make_sound(self):return "喵喵"def move(self):return "悄悄走"class Bird(Animal):def make_sound(self):return "叽叽喳喳"def move(self):return "飞翔"# 多态的使用
def animal_behavior(animal: Animal):print(f"动物发出声音: {animal.make_sound()}")print(f"动物移动方式: {animal.move()}")# 创建不同的动物实例
animals = [Dog(), Cat(), Bird()]# 多态调用
for animal in animals:animal_behavior(animal)print("-" * 20)# 鸭子类型(Duck Typing)
class Duck:def make_sound(self):return "嘎嘎"def move(self):return "游泳"class Robot:def make_sound(self):return "哔哔"def move(self):return "机械移动"# 即使没有继承Animal,只要有相同的方法,就可以使用
duck = Duck()
robot = Robot()animal_behavior(duck) # 正常工作
animal_behavior(robot) # 也能正常工作# 运算符重载实现多态
class Vector:def __init__(self, x, y):self.x = xself.y = ydef __add__(self, other):if isinstance(other, Vector):return Vector(self.x + other.x, self.y + other.y)elif isinstance(other, (int, float)):return Vector(self.x + other, self.y + other)else:raise TypeError(f"不支持的类型: {type(other)}")def __str__(self):return f"Vector({self.x}, {self.y})"# 多态的运算符重载
v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2 # Vector + Vector
v4 = v1 + 5 # Vector + intprint(f"v1 + v2 = {v3}")
print(f"v1 + 5 = {v4}")# 策略模式体现多态
class PaymentStrategy(ABC):@abstractmethoddef pay(self, amount):passclass CreditCardPayment(PaymentStrategy):def pay(self, amount):return f"使用信用卡支付 {amount} 元"class AlipayPayment(PaymentStrategy):def pay(self, amount):return f"使用支付宝支付 {amount} 元"class WechatPayment(PaymentStrategy):def pay(self, amount):return f"使用微信支付 {amount} 元"class PaymentProcessor:def __init__(self, strategy: PaymentStrategy):self.strategy = strategydef process_payment(self, amount):return self.strategy.pay(amount)# 使用不同的支付策略
payments = [PaymentProcessor(CreditCardPayment()),PaymentProcessor(AlipayPayment()),PaymentProcessor(WechatPayment())
]for payment in payments:print(payment.process_payment(100))
23. 解释一下模块和包
模块(Module)
# math_utils.py - 自定义模块
def add(a, b):"""加法函数"""return a + bdef multiply(a, b):"""乘法函数"""return a * bPI = 3.14159class Calculator:def __init__(self):self.history = []def calculate(self, operation, a, b):if operation == 'add':result = add(a, b)elif operation == 'multiply':result = multiply(a, b)else:result = Noneself.history.append((operation, a, b, result))return result# 模块的使用
# 方法1:导入整个模块
import math_utils
result = math_utils.add(5, 3)
print(f"5 + 3 = {result}")# 方法2:导入特定函数
from math_utils import add, PI
result = add(10, 20)
print(f"PI = {PI}")# 方法3:导入并重命名
import math_utils as mu
from math_utils import Calculator as Calccalc = Calc()
result = calc.calculate('add', 1, 2)# 方法4:导入所有(不推荐)
# from math_utils import *# 查看模块信息
print(f"模块名称: {math_utils.__name__}")
print(f"模块文件: {math_utils.__file__}")
print(f"模块文档: {math_utils.__doc__}")
print(f"模块属性: {dir(math_utils)}")
包(Package)
# 包的结构示例
"""
mypackage/__init__.pymodule1.pymodule2.pysubpackage/__init__.pysubmodule.py
"""# mypackage/__init__.py
"""这是一个示例包"""__version__ = "1.0.0"
__author__ = "Your Name"# 控制 from mypackage import * 的行为
__all__ = ['module1', 'important_function']from .module1 import important_function
from .module2 import AnotherClass# 包级别的初始化代码
print(f"正在初始化包 {__name__}")# mypackage/module1.py
def important_function():return "这是一个重要的函数"def helper_function():return "这是一个辅助函数"# mypackage/module2.py
class AnotherClass:def __init__(self):self.value = "Another Class"# mypackage/subpackage/__init__.py
from .submodule import SubFunction# mypackage/subpackage/submodule.py
def SubFunction():return "子包中的函数"# 使用包
import mypackage
from mypackage import module1
from mypackage.subpackage import SubFunction# 相对导入(在包内部使用)
# from . import module1 # 同级导入
# from ..module1 import func # 上级导入
# from .subpackage import sub # 子包导入# 动态导入
import importlibmodule_name = "math"
math_module = importlib.import_module(module_name)
print(f"π = {math_module.pi}")# 检查模块是否存在
try:import some_optional_module
except ImportError:print("可选模块不存在")some_optional_module = None# 模块搜索路径
import sys
print("Python模块搜索路径:")
for path in sys.path:print(f" {path}")# 添加自定义路径
sys.path.append('/path/to/custom/modules')# 重新加载模块(开发时有用)
importlib.reload(math_utils)
24. 什么是asyncio?它有什么用途?
import asyncio
import aiohttp
import time
from concurrent.futures import ThreadPoolExecutor# 基本的异步函数
async def hello_async():print("Hello")await asyncio.sleep(1) # 异步等待print("World")# 运行异步函数
# asyncio.run(hello_async())# 并发执行多个异步任务
async def fetch_data(url, session):async with session.get(url) as response:return await response.text()async def main():urls = ['https://httpbin.org/delay/1','https://httpbin.org/delay/2','https://httpbin.org/delay/1']async with aiohttp.ClientSession() as session:# 并发执行tasks = [fetch_data(url, session) for url in urls]results = await asyncio.gather(*tasks)return results# 异步生成器
async def async_generator():for i in range(5):await asyncio.sleep(0.1)yield iasync def consume_async_generator():async for value in async_generator():print(f"Generated: {value}")# 异步上下文管理器
class AsyncContextManager:async def __aenter__(self):print("进入异步上下文")await asyncio.sleep(0.1)return selfasync def __aexit__(self, exc_type, exc_val, exc_tb):print("退出异步上下文")await asyncio.sleep(0.1)async def use_async_context():async with AsyncContextManager() as cm:print("在异步上下文中")await asyncio.sleep(0.5)# 异步队列
async def producer(queue):for i in range(5):await asyncio.sleep(0.1)await queue.put(f"item-{i}")print(f"生产: item-{i}")await queue.put(None) # 结束信号async def consumer(queue):while True:item = await queue.get()if item is None:breakprint(f"消费: {item}")await asyncio.sleep(0.2)queue.task_done()async def producer_consumer_example():queue = asyncio.Queue(maxsize=3)# 创建生产者和消费者任务producer_task = asyncio.create_task(producer(queue))consumer_task = asyncio.create_task(consumer(queue))# 等待生产者完成await producer_task# 等待队列中的所有任务完成await queue.join()# 取消消费者任务consumer_task.cancel()# 异步锁
shared_resource = 0
lock = asyncio.Lock()async def modify_shared_resource(name):global shared_resourceasync with lock:print(f"{name} 获得锁")temp = shared_resourceawait asyncio.sleep(0.1) # 模拟处理时间shared_resource = temp + 1print(f"{name} 释放锁,资源值: {shared_resource}")async def lock_example():tasks = [modify_shared_resource("Task-1"),modify_shared_resource("Task-2"),modify_shared_resource("Task-3")]await asyncio.gather(*tasks)# 超时处理
async def slow_operation():await asyncio.sleep(3)return "操作完成"async def timeout_example():try:result = await asyncio.wait_for(slow_operation(), timeout=2)print(result)except asyncio.TimeoutError:print("操作超时")# 事件循环控制
async def event_loop_example():loop = asyncio.get_event_loop()# 在线程池中运行阻塞操作def blocking_operation():time.sleep(1)return "阻塞操作完成"result = await loop.run_in_executor(None, blocking_operation)print(result)# 调度回调def callback():print("回调函数执行")loop.call_later(1, callback)await asyncio.sleep(1.1)# 性能对比:同步 vs 异步
def sync_fetch(url):# 模拟网络请求time.sleep(1)return f"Data from {url}"async def async_fetch(url):# 模拟异步网络请求await asyncio.sleep(1)return f"Data from {url}"def compare_performance():urls = [f"url-{i}" for i in range(5)]# 同步方式start = time.time()sync_results = [sync_fetch(url) for url in urls]sync_time = time.time() - start# 异步方式async def async_main():tasks = [async_fetch(url) for url in urls]return await asyncio.gather(*tasks)start = time.time()async_results = asyncio.run(async_main())async_time = time.time() - startprint(f"同步执行时间: {sync_time:.2f}秒")print(f"异步执行时间: {async_time:.2f}秒")print(f"性能提升: {sync_time/async_time:.2f}倍")# 运行示例
if __name__ == "__main__":# asyncio.run(hello_async())# asyncio.run(consume_async_generator())# asyncio.run(use_async_context())# asyncio.run(producer_consumer_example())# asyncio.run(lock_example())# asyncio.run(timeout_example())# asyncio.run(event_loop_example())compare_performance()
25. 什么是协程?
import asyncio
import inspect
from types import GeneratorType, CoroutineType# 生成器函数(早期协程的基础)
def generator_function():print("生成器开始")yield 1print("生成器继续")yield 2print("生成器结束")return "完成"# 基于生成器的协程(Python 3.4风格)
@asyncio.coroutine
def old_style_coroutine():print("旧式协程开始")yield from asyncio.sleep(1)print("旧式协程结束")return "旧式协程完成"# 原生协程(Python 3.5+推荐)
async def native_coroutine():print("原生协程开始")await asyncio.sleep(1)print("原生协程结束")return "原生协程完成"# 协程的状态
async def coroutine_states():print("协程状态演示")# 创建协程对象coro = native_coroutine()print(f"协程类型: {type(coro)}")print(f"是否为协程: {inspect.iscoroutine(coro)}")# 运行协程result = await coroprint(f"协程结果: {result}")# 协程的生命周期
class CoroutineLifecycle:def __init__(self, name):self.name = nameasync def __aenter__(self):print(f"{self.name}: 进入协程上下文")return selfasync def __aexit__(self, exc_type, exc_val, exc_tb):print(f"{self.name}: 退出协程上下文")async def work(self):print(f"{self.name}: 开始工作")await asyncio.sleep(0.5)print(f"{self.name}: 工作完成")async def lifecycle_demo():async with CoroutineLifecycle("Worker-1") as worker:await worker.work()# 协程的异常处理
async def error_coroutine():await asyncio.sleep(0.1)raise ValueError("协程中的错误")async def handle_coroutine_error():try:await error_coroutine()except ValueError as e:print(f"捕获到协程异常: {e}")# 协程的取消
async def cancellable_coroutine():try:print("可取消协程开始")await asyncio.sleep(5) # 长时间运行print("可取消协程完成")except asyncio.CancelledError:print("协程被取消")raise # 重新抛出取消异常async def cancel_demo():task = asyncio.create_task(cancellable_coroutine())# 等待1秒后取消await asyncio.sleep(1)task.cancel()try:await taskexcept asyncio.CancelledError:print("任务已被取消")# 协程的组合
async def step1():print("执行步骤1")await asyncio.sleep(0.5)return "步骤1完成"async def step2(result1):print(f"执行步骤2,接收: {result1}")await asyncio.sleep(0.5)return "步骤2完成"async def step3(result2):print(f"执行步骤3,接收: {result2}")await asyncio.sleep(0.5)return "步骤3完成"async def sequential_coroutines():"""顺序执行协程"""result1 = await step1()result2 = await step2(result1)result3 = await step3(result2)return result3async def parallel_coroutines():"""并行执行协程"""results = await asyncio.gather(step1(),step1(), # 可以同时执行多个相同的协程step1())return results# 协程与线程的对比
import threading
import timedef thread_function(name, duration):print(f"线程 {name} 开始")time.sleep(duration)print(f"线程 {name} 结束")async def coroutine_function(name, duration):print(f"协程 {name} 开始")await asyncio.sleep(duration)print(f"协程 {name} 结束")def compare_thread_vs_coroutine():# 线程方式start = time.time()threads = []for i in range(3):t = threading.Thread(target=thread_function, args=(f"Thread-{i}", 1))threads.append(t)t.start()for t in threads:t.join()thread_time = time.time() - start# 协程方式async def coroutine_main():tasks = [coroutine_function(f"Coroutine-{i}", 1) for i in range(3)]await asyncio.gather(*tasks)start = time.time()asyncio.run(coroutine_main())coroutine_time = time.time() - startprint(f"线程执行时间: {thread_time:.2f}秒")print(f"协程执行时间: {coroutine_time:.2f}秒")# 运行示例
if __name__ == "__main__":# asyncio.run(hello_async())# asyncio.run(consume_async_generator())# asyncio.run(use_async_context())# asyncio.run(producer_consumer_example())# asyncio.run(lock_example())# asyncio.run(timeout_example())# asyncio.run(event_loop_example())compare_performance()
26. 协程和线程的区别
特性 | 协程(Coroutine) | 线程(Thread) |
---|---|---|
调度方式 | 协作式调度 | 抢占式调度 |
切换开销 | 极低 | 较高 |
内存占用 | 很小(KB级别) | 较大(MB级别) |
并发数量 | 可达数万个 | 通常数百个 |
同步机制 | 无需锁 | 需要锁机制 |
CPU密集型 | 不适合 | 适合 |
I/O密集型 | 非常适合 | 适合 |
import asyncio
import threading
import time
import concurrent.futures# 协程示例
async def async_io_task(task_id):print(f"异步任务 {task_id} 开始")await asyncio.sleep(1) # 模拟I/O操作print(f"异步任务 {task_id} 完成")return f"结果-{task_id}"# 线程示例
def thread_io_task(task_id):print(f"线程任务 {task_id} 开始")time.sleep(1) # 模拟I/O操作print(f"线程任务 {task_id} 完成")return f"结果-{task_id}"# 性能对比
def performance_comparison():num_tasks = 10# 协程性能测试async def async_test():tasks = [async_io_task(i) for i in range(num_tasks)]return await asyncio.gather(*tasks)start = time.time()async_results = asyncio.run(async_test())async_time = time.time() - start# 线程性能测试start = time.time()with concurrent.futures.ThreadPoolExecutor(max_workers=10) as executor:thread_results = list(executor.map(thread_io_task, range(num_tasks)))thread_time = time.time() - startprint(f"协程执行 {num_tasks} 个任务耗时: {async_time:.2f}秒")print(f"线程执行 {num_tasks} 个任务耗时: {thread_time:.2f}秒")# 内存使用对比import psutilimport osprocess = psutil.Process(os.getpid())print(f"当前进程内存使用: {process.memory_info().rss / 1024 / 1024:.2f} MB")# 协程的适用场景
async def web_scraping_example():"""网络爬虫场景"""import aiohttpurls = ['https://httpbin.org/delay/1','https://httpbin.org/delay/1','https://httpbin.org/delay/1']async def fetch(session, url):async with session.get(url) as response:return await response.text()async with aiohttp.ClientSession() as session:tasks = [fetch(session, url) for url in urls]results = await asyncio.gather(*tasks)return results# 线程的适用场景
def cpu_intensive_task(n):"""CPU密集型任务"""result = 0for i in range(n):result += i * ireturn resultdef cpu_intensive_comparison():num_tasks = 4task_size = 1000000# 单线程start = time.time()single_results = [cpu_intensive_task(task_size) for _ in range(num_tasks)]single_time = time.time() - start# 多线程start = time.time()with concurrent.futures.ThreadPoolExecutor() as executor:multi_results = list(executor.map(cpu_intensive_task, [task_size] * num_tasks))multi_time = time.time() - startprint(f"单线程CPU密集型任务耗时: {single_time:.2f}秒")print(f"多线程CPU密集型任务耗时: {multi_time:.2f}秒")print(f"性能提升: {single_time/multi_time:.2f}倍")
27. Future和Task的区别
import asyncio
from concurrent.futures import ThreadPoolExecutor, Future as ThreadFuture# asyncio.Future
async def future_example():loop = asyncio.get_event_loop()# 创建Future对象future = loop.create_future()# 在另一个协程中设置结果async def set_result():await asyncio.sleep(1)future.set_result("Future的结果")# 启动设置结果的协程asyncio.create_task(set_result())# 等待Future完成result = await futureprint(f"Future结果: {result}")# asyncio.Task
async def task_example():async def background_task():await asyncio.sleep(2)return "Task的结果"# 创建Tasktask = asyncio.create_task(background_task())# Task的状态print(f"Task创建后状态: {task.done()}")# 等待Task完成result = await taskprint(f"Task结果: {result}")print(f"Task完成后状态: {task.done()}")# Future vs Task 对比
async def future_vs_task():loop = asyncio.get_event_loop()# Future: 低级别的可等待对象future = loop.create_future()# Task: 对协程的封装async def coro():return "协程结果"task = asyncio.create_task(coro())# 手动设置Future的结果future.set_result("手动设置的结果")# 等待两者完成future_result = await futuretask_result = await taskprint(f"Future结果: {future_result}")print(f"Task结果: {task_result}")# Task的生命周期管理
async def task_lifecycle():async def long_running_task():try:for i in range(10):print(f"任务进度: {i+1}/10")await asyncio.sleep(0.5)return "任务完成"except asyncio.CancelledError:print("任务被取消")raise# 创建并启动任务task = asyncio.create_task(long_running_task())# 等待一段时间后取消await asyncio.sleep(2)task.cancel()try:result = await taskexcept asyncio.CancelledError:print("确认任务已取消")# 并发控制
async def concurrent_control():async def worker(name, duration):print(f"工作者 {name} 开始")await asyncio.sleep(duration)print(f"工作者 {name} 完成")return f"结果-{name}"# 创建多个任务tasks = [asyncio.create_task(worker(f"Worker-{i}", i+1))for i in range(3)]# 等待第一个完成done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)print(f"第一个完成的任务: {done}")# 取消剩余任务for task in pending:task.cancel()# 等待所有任务结束await asyncio.gather(*pending, return_exceptions=True)
28. Python中的元类(Metaclass)
# 元类的基本概念
class MyMetaclass(type):def __new__(mcs, name, bases, attrs):print(f"创建类 {name}")# 自动为所有方法添加日志for key, value in attrs.items():if callable(value) and not key.startswith('__'):attrs[key] = MyMetaclass.add_logging(value)return super().__new__(mcs, name, bases, attrs)@staticmethoddef add_logging(func):def wrapper(*args, **kwargs):print(f"调用方法: {func.__name__}")return func(*args, **kwargs)return wrapperclass MyClass(metaclass=MyMetaclass):def method1(self):return "方法1"def method2(self):return "方法2"# 单例元类
class SingletonMeta(type):_instances = {}def __call__(cls, *args, **kwargs):if cls not in cls._instances:cls._instances[cls] = super().__call__(*args, **kwargs)return cls._instances[cls]class Database(metaclass=SingletonMeta):def __init__(self):self.connection = "数据库连接"# 属性验证元类
class ValidatedMeta(type):def __new__(mcs, name, bases, attrs):# 为所有属性添加类型检查for key, value in attrs.items():if isinstance(value, type):attrs[f'_{key}'] = Noneattrs[key] = mcs.create_property(key, value)return super().__new__(mcs, name, bases, attrs)@staticmethoddef create_property(name, expected_type):def getter(self):return getattr(self, f'_{name}')def setter(self, value):if not isinstance(value, expected_type):raise TypeError(f"{name} 必须是 {expected_type.__name__} 类型")setattr(self, f'_{name}', value)return property(getter, setter)class Person(metaclass=ValidatedMeta):name = strage = intdef __init__(self, name, age):self.name = nameself.age = age
29. Python中的描述符(Descriptor)
# 描述符协议
class LoggedAttribute:def __init__(self, name):self.name = nameself.value = Nonedef __get__(self, obj, objtype=None):print(f"获取属性 {self.name}: {self.value}")return self.valuedef __set__(self, obj, value):print(f"设置属性 {self.name}: {value}")self.value = valuedef __delete__(self, obj):print(f"删除属性 {self.name}")self.value = None# 类型检查描述符
class TypedAttribute:def __init__(self, expected_type):self.expected_type = expected_typeself.value = Nonedef __set_name__(self, owner, name):self.name = namedef __get__(self, obj, objtype=None):if obj is None:return selfreturn getattr(obj, f'_{self.name}', None)def __set__(self, obj, value):if not isinstance(value, self.expected_type):raise TypeError(f"{self.name} 必须是 {self.expected_type.__name__} 类型")setattr(obj, f'_{self.name}', value)# 范围检查描述符
class RangeAttribute:def __init__(self, min_value, max_value):self.min_value = min_valueself.max_value = max_valuedef __set_name__(self, owner, name):self.name = namedef __get__(self, obj, objtype=None):if obj is None:return selfreturn getattr(obj, f'_{self.name}', None)def __set__(self, obj, value):if not (self.min_value <= value <= self.max_value):raise ValueError(f"{self.name} 必须在 {self.min_value} 到 {self.max_value} 之间")setattr(obj, f'_{self.name}', value)class Student:name = TypedAttribute(str)age = RangeAttribute(0, 150)score = RangeAttribute(0, 100)def __init__(self, name, age, score):self.name = nameself.age = ageself.score = score
30. Python中的上下文变量(Context Variables)
import contextvars
import asyncio# 创建上下文变量
user_id = contextvars.ContextVar('user_id')
request_id = contextvars.ContextVar('request_id', default='unknown')# 上下文变量的使用
def get_current_user():return user_id.get()def get_request_id():return request_id.get()async def process_request(uid, rid):# 设置上下文变量user_id.set(uid)request_id.set(rid)print(f"处理请求 - 用户: {get_current_user()}, 请求ID: {get_request_id()}")# 调用其他函数,它们可以访问上下文变量await business_logic()await log_operation()async def business_logic():print(f"业务逻辑 - 当前用户: {get_current_user()}")await asyncio.sleep(0.1)async def log_operation():print(f"记录日志 - 用户: {get_current_user()}, 请求: {get_request_id()}")# 上下文变量的隔离
async def context_isolation_demo():# 并发处理多个请求await asyncio.gather(process_request("user1", "req1"),process_request("user2", "req2"),process_request("user3", "req3"))# 手动管理上下文
def manual_context_management():# 复制当前上下文ctx = contextvars.copy_context()# 在新上下文中运行def run_in_context():user_id.set("context_user")print(f"上下文中的用户: {get_current_user()}")ctx.run(run_in_context)# 原上下文不受影响try:print(f"原上下文用户: {get_current_user()}")except LookupError:print("原上下文中没有设置用户")
总结
Python面试中的基础知识涵盖了语言的核心特性和高级概念。掌握这些知识点不仅有助于通过面试,更重要的是能够写出更高质量、更Pythonic的代码。
重点掌握的概念:
- 数据结构:列表、字典、元组的内存管理和性能特点
- 函数式编程:闭包、装饰器、高阶函数的应用
- 面向对象:继承、多态、元类、描述符的深入理解
- 异步编程:协程、事件循环、并发控制的实践
- 内存管理:深浅拷贝、垃圾回收、引用计数的机制
- 高级特性:上下文管理器、生成器、迭代器的使用
面试建议:
- 理论与实践结合:不仅要知道概念,还要能写出代码示例
- 性能意识:了解不同实现方式的时间和空间复杂度
- 最佳实践:遵循PEP 8规范,写出Pythonic的代码
- 实际应用:能够将这些知识点应用到实际项目中
希望这份汇总能够帮助大家在Python面试中取得好成绩!
相关文章推荐:
-
Python面试之基础知识常见面试题1-基础篇:点我跳转
-
Python面试之基础知识常见面试题2-列表篇:点我跳转
结尾
-
希望对初学者有帮助;致力于办公自动化的小小程序员一枚
-
希望能得到大家的【❤️一个免费关注❤️】感谢!
-
求个 🤞 关注 🤞 +❤️ 喜欢 ❤️ +👍 收藏 👍
-
此外还有办公自动化专栏,欢迎大家订阅:Python办公自动化专栏
-
此外还有爬虫专栏,欢迎大家订阅:Python爬虫基础专栏
-
此外还有Python基础专栏,欢迎大家订阅:Python基础学习专栏