勇哥注:
这个系列贴子,勇哥站在C#开发角度来学习python。
我以C#常用知识点来类比python,共4篇。
你看完后,就明白,所谓python简单易学只是表面。这门语言的语法体量很大不输给C#,它其实是很好的把复杂性隐藏了,给人入门简单的印象,其实想精通它困难是比较大的。
C#开发者快速学习python(1):入口函数,运算符,模块与包,py文件 ,基础数据类型,列表
C#开发者快速学习python(2):枚举,装饰器,self,类的三种类型,特殊方法,oop,代码格式,方法 ,方法返回类型,识别函数返回类型
C#开发者快速学习python(3):有无数组?字符串方法,读写文件,序列化,配置文件,异常处理,循环和选择,模块与包,调试手段
C#开发者快速学习python(4):与C#不同点,鸭子类型,多线程,多进程编程,访问控制,事件委托实现,lambda表达式,常用内置库,为啥说python简单易用?
(九)python的语法体系很大(和C#相比,该有的都有了),凭什么说其“简单易用”?
你说得非常对!Python虽然常被贴上"简单易学"的标签,但它的语法体系和功能实际上相当丰富和复杂。
这种表面简洁与内在丰富的结合,正是Python的魅力所在。下面从几个角度展开说明:
1. Python的"简单"是一种设计哲学
Python的"简单"更多体现在它的语法风格和设计理念上:
- 可读性优先 :强制缩进、简洁的关键字、清晰的代码结构
- 一种明确的做事方式 :"There should be one—and preferably only one—obvious way to do it"
- 语法糖 :通过简洁的语法表达复杂逻辑(如列表推导式、上下文管理器)
但这种"简单"并不意味着功能有限,相反,Python通过这些设计哲学,
让复杂的功能变得易于表达和理解。
2. Python语法体系的复杂性体现在
2.1 多范式支持
Python支持面向对象、函数式、过程式等多种编程范式,每种范式都有对应的语法特性:
- 面向对象 :类、继承、多态、属性装饰器、元类
- 函数式 :高阶函数、lambda表达式、闭包、装饰器
- 过程式 :直观的控制流语句 2.2 动态类型系统的深度
Python的动态类型系统提供了灵活性,但也带来了复杂性:
- 动态类型检查与运行时错误处理
- 类型提示(Type Hints)系统的不断完善
- 鸭子类型的灵活应用 2.3 高级特性的丰富性
Python包含许多强大的高级特性:
- 装饰器 :函数和类装饰器,可以在不修改原函数的情况下扩展功能
- 上下文管理器 :通过 with 语句简化资源管理
- 生成器与协程 :支持惰性计算和异步编程
- 元编程 :通过元类、反射等机制在运行时修改代码行为
- 描述符协议 :自定义属性访问行为 2.4 内置数据结构的强大功能
Python的内置数据结构(列表、字典、集合等)不仅使用简单,而且提供了丰富的方法和操作:
- 列表推导式、字典推导式、集合推导式
- 切片操作的灵活应用
- 字典的高级用法(默认字典、有序字典等)
3. 与C#的语法体系对比
虽然Python和C#在功能上有很多对应之处,但它们的实现方式和设计理念有显著差异:
- 类型系统 :Python是动态弱类型,C#是静态强类型
- 访问控制 :Python依赖命名约定,C#使用严格的访问修饰符
- 错误处理 :Python倾向于"请求原谅比许可容易"(EAFP),C#倾向于"先看后跳"(LBYL)
- 扩展性 :Python通过装饰器和元编程扩展,C#通过接口和特性扩展
4. Python的学习曲线
Python的学习曲线特点是"入门容易,精通难":
- 初学者可以在几小时内掌握基础语法并写出简单程序
- 但要真正掌握Python的高级特性、最佳实践和性能优化,需要相当长的时间和实践
- 随着Python版本的更新,新特性不断增加(如f-string、海象运算符、模式匹配等)
5. 为什么Python依然受欢迎
尽管语法体系复杂,但Python依然保持着极高的受欢迎度,原因在于:
- 表达力强 :用更少的代码表达更复杂的逻辑
- 生态丰富 :拥有庞大的第三方库和活跃的社区
- 跨平台 :可以在各种操作系统上运行
- 应用广泛 :从Web开发、数据分析到人工智能、科学计算
Python就像一把瑞士军刀,虽然结构不复杂,但功能全面且强大。
它的"简单"是一种精心设计的复杂性,让新手容易上手,同时也能满足专业开发者的高级需求。
这种平衡正是Python成功的关键所在。
(八)内置库到底有哪些
Python的内置库(也称为标准库)是随Python安装一起提供的模块和包的集合,功能非常丰富。
这些库覆盖了从基础数据处理到高级系统交互的广泛领域,无需额外安装即可使用。
下面按功能类别介绍主要的内置库:
1. 基础功能模块
1.1 系统交互
- os :提供与操作系统交互的功能(文件操作、目录操作、环境变量等)
import os
os.mkdir('new_folder') # 创建目录
print(os.getcwd()) # 获取当前工作目录
- sys :提供对Python解释器相关的操作和变量(命令行参数、退出程序等)
import sys
print(sys.version) # 查看
Python版本
sys.exit() # 退出程序
- platform :提供获取平台(操作系统)信息的功能
1.2 文件与目录处理
- pathlib :提供面向对象的文件系统路径操作
from pathlib import Path
path = Path('example.txt')
if path.exists():
print(path.read_text())
- shutil :提供高级文件操作(复制、移动、删除等)
2. 数据类型与数据结构
2.1 基础数据结构扩展
- collections :提供额外的数据类型(OrderedDict、defaultdict、Counter等)
from collections import
defaultdict, Counter
d = defaultdict(list) # 默认值为
列表的字典
c = Counter([1, 2, 2, 3, 3, 3])
# 计数
- heapq :提供堆队列算法(优先队列)
- bisect :提供二分查找算法 2.2 数据序列化
- json :处理JSON数据的编码与解码
import json
data = {'name': 'Alice', 'age':
30}
json_str = json.dumps(data) # 转为JSON字符串
parsed_data = json.loads
(json_str) # 解析JSON字符串
- pickle :Python对象的序列化与反序列化
- csv :处理CSV文件的读写
3. 文本处理
3.1 字符串操作
- string :提供字符串常量和辅助函数
- re :正则表达式操作(匹配、替换、分割等)
import re
pattern = r'\d+'
result = re.findall(pattern,
'There are 123 apples and 456
oranges')
print(result) # 输出: ['123', '456']
3.2 文本编码
- codecs :提供编解码器接口
- unicodedata :Unicode字符数据库
4. 日期与时间
- datetime :处理日期和时间
from datetime import datetime,
timedelta
now = datetime.now()
tomorrow = now + timedelta
(days=1)
print(now.strftime('%Y-%m-%d
%H:%M:%S')) # 格式化输出
- time :提供时间相关的函数
- calendar :日历相关功能
5. 网络与通信
5.1 网络协议
- socket :底层网络接口
- http.client :HTTP客户端协议实现
- urllib :URL处理模块(请求、解析等)
from urllib.request import
urlopen
response = urlopen('https://www.python.org')
data = response.read()
- smtplib :SMTP协议实现(发送邮件)
- email :处理电子邮件消息 5.2 网络服务
- http.server :简单的HTTP服务器
- socketserver :网络服务器框架
6. 并发与并行
- threading :多线程编程
import threading
def worker():
print('Worker thread')
t = threading.Thread
(target=worker)
t.start()
t.join()
- multiprocessing :多进程编程
- concurrent.futures :高级异步执行接口
- asyncio :异步I/O编程
7. 数学计算
- math :数学函数(三角函数、对数、指数等)
import math
print(math.sqrt(16)) # 平方根
print(math.pi) # 圆周率
- random :生成随机数
- statistics :统计函数
- decimal :十进制浮点运算
8. 加密与安全
- hashlib :安全哈希算法(MD5、SHA1等)
import hashlib
hash_obj = hashlib.sha256
(b'Hello World')
print(hash_obj.hexdigest())
- secrets :生成安全的随机数(用于密码、令牌等)
- crypt :Unix密码加密
9. 测试与调试
- unittest :单元测试框架
import unittest
class TestStringMethods(unittest.
TestCase):
def test_upper(self):
self.assertEqual('hello'.
upper(), 'HELLO')
- doctest :文档测试
- pdb :Python调试器
- logging :日志记录
10. 其他常用模块
- argparse :命令行参数解析
- enum :枚举类型支持
- typing :类型提示支持
- itertools :迭代器函数
import itertools
# 生成笛卡尔积
for combo in itertools.product
([1, 2], ['a', 'b']):
print(combo) # 输出: (1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')
- functools :函数工具(如lru_cache、partial等)
- contextlib :上下文管理器工具
11. Python标准库的版本变化
Python的标准库会随着版本更新而变化,一些模块可能被添加、改进或废弃。例如:
- Python 3.5 添加了 typing 模块
- Python 3.7 添加了 dataclasses 模块
- Python 3.10 添加了模式匹配(match-case)语法
12. 如何学习和使用内置库
- 官方文档 :Python官方文档是学习标准库的最佳资源,包含详细的API说明和示例
- help()函数 :在Python交互式解释器中使用 help(module_name) 可以查看模块的帮助信息
- 实践探索 :通过实际项目练习使用不同的模块
Python的内置库就像是一个"瑞士军刀",提供了丰富的功能支持各种编程需求。
掌握这些内置库,可以大大提高编程效率,避免重复造轮子。
(七)lambda表达式
Lambda表达式是Python中定义匿名函数的一种简洁方式,也被称为"lambda函数"或"lambda表达式"。下面详细介绍lambda表达式的相关内容:
1. Lambda表达式的基本语法
Lambda表达式的语法格式为: lambda 参数列表: 表达式
- lambda 是Python的关键字
- 参数列表与普通函数类似,可以有多个参数,也可以没有参数
- 冒号 : 用于分隔参数列表和表达式
- 表达式是函数的返回值,不需要使用 return 语句
示例:
# 定义一个lambda函数,接受两个参数并返回它们的和 add = lambda x, y: x + y # 调用lambda函数 result = add(3, 5) # 输出: 8
2. Lambda表达式的特点
- 匿名性 :lambda函数没有名称,它返回一个函数对象
- 简洁性 :通常用于编写简单的单行函数
- 表达式单一 :只能包含一个表达式,不能包含复杂的逻辑(如循环、条件语句等)
- 闭包特性 :可以访问定义它的作用域中的变量
3. Lambda表达式的常见用法
3.1 作为函数参数
lambda表达式常用于需要传递简单函数作为参数的场景:
# 结合内置函数使用 data = [1, 3, 5, 7, 9] # 使用lambda作为sorted的key参数 result = sorted(data, key=lambda x: -x) # 降序排序 print(result) # 输出: [9, 7, 5, 3, 1] # 使用lambda作为filter的筛选条件 even_numbers = list(filter(lambda x: x % 2 == 0, range(10))) # 筛选偶数 print(even_numbers) # 输出: [0, 2, 4, 6, 8] # 使用lambda作为map的映射函数 squared = list(map(lambda x: x * x, [1, 2, 3, 4])) # 计算平方 print(squared) # 输出: [1, 4, 9, 16]
3.2 作为闭包
lambda表达式可以访问其定义环境中的变量:
def make_multiplier(n): # 返回一个lambda函数,该函数捕获了变量n return lambda x: x * n # 创建两个乘法函数 double = make_multiplier(2) # 乘以2的函数 triple = make_multiplier(3) # 乘以3的函数 print(double(5)) # 输出: 10 print(triple(5)) # 输出: 15
3.3 在列表推导式或字典推导式中
# 列表推导式中使用lambda funcs = [lambda x: x * i for i in range(5)] for f in funcs: print(f(2)) # 注意:这里会输出5个8,因为lambda捕获的是i的引用,最终i=4 # 改进方法 - 使用默认参数立即绑定值 funcs = [lambda x, i=i: x * i for i in range(5)] for f in funcs: print(f(2)) # 正确输出: 0, 2, 4, 6, 8
3.4 作为回调函数
在GUI编程或异步编程中,lambda常作为简单的回调函数:
# 简化版的GUI按钮回调示例 class Button: def __init__(self, callback=None): self.callback = callback def click(self): if self.callback: self.callback() # 创建按钮并设置lambda作为回调 button = Button(lambda: print("按钮被点击了!")) button.click() # 输出: 按钮被点击了!
4. Lambda表达式与普通函数的对比
- 代码长度 :lambda通常更简洁,适合单行逻辑
- 可读性 :对于复杂逻辑,普通函数更易读,尤其是有文档字符串的情况下
- 使用场景 :lambda适合简单的操作,普通函数适合复杂的、可重用的逻辑
5. Lambda表达式的限制
- 只能包含一个表达式,不能包含多条语句或复杂控制流
- 不能包含return语句(表达式的值自动作为返回值)
- 不能包含文档字符串
- 不能用作类方法的替代
6. 实际应用建议
- 对于简单的、一次性的函数,使用lambda表达式可以使代码更简洁
- 当函数逻辑变得复杂时,应考虑将lambda重构为普通函数以提高可读性
- 在使用lambda作为闭包时,注意变量作用域和延迟绑定的问题
Lambda表达式是Python函数式编程的重要组成部分,它与map()、filter()、sorted()等内置函数结合使用,
可以使代码更加简洁、优雅。
(六)C#的事件、委托,在python中有无相似的概念
C#中的事件(Event)和委托(Delegate)是实现回调机制和观察者模式的重要机制。
Python中虽然没有直接对应的关键字或类型,但可以通过其灵活的函数特性和面向对象设计实现类似功能。
下面详细介绍Python中的相关实现方式:
1. C#委托的Python对应概念
C#中的委托是一种类型安全的函数指针,可以指向一个或多个方法。在Python中,函数是一等公民(first-class citizens),
可以直接作为参数传递、赋值给变量、存储在数据结构中,这提供了类似委托的功能。
基本示例:
# 函数作为参数传递(类似委托) def add(a, b): return a + b def multiply(a, b): return a * b def calculate(operation, x, y): # operation 类似于委托,可以接受不同的函数 return operation(x, y) # 使用 result1 = calculate(add, 3, 4) # 输出: 7 result2 = calculate(multiply, 3, 4) # 输出: 12
更接近委托的实现 - 使用functools模块:
import functools # 创建类似委托的函数 add_five = functools.partial(lambda x, y: x + y, 5) # 固定第一个参数为5 # 调用 result = add_five(10) # 输出: 15
2. C#事件的Python对应概念
C#中的事件是基于委托的通知机制,允许对象在特定状态改变时通知其他对象。Python中可以通过实现观察者模式来模拟事件系统。
自定义事件系统实现:
class Event: def __init__(self): self._handlers = [] def add_handler(self, handler): # 注册事件处理程序(类似C#的 += 运算符) if handler not in self._handlers: self._handlers.append(handler) def remove_handler(self, handler): # 移除事件处理程序(类似C#的 -= 运算符) if handler in self._handlers: self._handlers.remove(handler) def fire(self, *args, **kwargs): # 触发事件(调用所有注册的处理程序) for handler in self._handlers: handler(*args, **kwargs) # 使用示例 class Button: def __init__(self): self.click = Event() # 定义点击事件 def press(self): # 按钮被按下时触发点击事件 print("按钮被按下") self.click.fire() # 创建按钮实例 button = Button() # 定义事件处理程序 def on_button_click(): print("点击事件处理程序被调用") def on_button_click2(): print("第二个点击事件处理程序被调用") # 注册事件处理程序 button.click.add_handler(on_button_click) button.click.add_handler(on_button_click2) # 触发事件 button.press() # 输出: # 按钮被按下 # 点击事件处理程序被调用 # 第二个点击事件处理程序被调用 # 移除一个处理程序 button.click.remove_handler(on_button_click2) button.press() # 输出: # 按钮被按下 # 点击事件处理程序被调用
3. 使用第三方库
Python中有一些第三方库专门提供了事件系统的实现,如 pydispatch 或 blinker :
使用blinker库实现事件:
from blinker import signal # 创建一个信号(类似事件) button_clicked = signal('button_clicked') # 定义事件处理函数 @button_clicked.connect def on_button_clicked(sender): print(f"按钮 {sender} 被点击") # 发送信号(触发事件) button_clicked.send('登录按钮') # 输出: 按钮 登录按钮 被点击
4. Python实现与C#的主要区别
- 类型安全 :C#的委托是类型安全的,而Python的函数传递是动态类型的
- 语法简洁性 :Python没有专门的委托和事件关键字,但实现方式更加灵活
- 多播委托 :C#的委托支持多播(一个委托可以引用多个方法),Python需要手动管理多个处理函数
- 线程安全 :C#的事件在多线程环境下是线程安全的,Python的自定义事件系统需要自己处理线程安全问题
5. 实际应用场景
Python中的这些机制常用于:
- GUI编程中的事件处理(如Tkinter、PyQt中的事件系统)
- 异步编程中的回调函数
- 插件系统的实现
- 观察者模式的应用
Python虽然没有C#那样的事件和委托关键字,但通过其灵活的函数特性和面向对象设计,可以实现类似甚至更强大的功能。
(五)访问控制
Python的访问控制与其他语言(如C#、Java)有很大不同,它主要依赖于命名约定而非严格的访问修饰符。
下面详细解释Python中与下划线前缀相关的访问控制机制:
1. 单下划线前缀(_var)
单下划线前缀是一种约定,表示该属性或方法被视为"内部使用",不应该被外部直接访问。
这只是一种程序员之间的约定,Python解释器不会强制执行。
特点:
- 表示"私有"的建议,无语法限制
- 导入模块时,使用 from module import * 不会导入以单下划线开头的名称
- 不会触发名称修饰(name mangling)
示例:
class MyClass: def __init__(self): self.public_var = "可以自由访问" self._private_var = "建议内部使用" def _internal_method(self): return "这是一个内部方法" # 使用 obj = MyClass() print(obj.public_var) # 正常访问 print(obj._private_var) # 技术上可以访问,但不建议 obj._internal_method() # 技术上可以调用,但不建议
注意:上面例子中,外部访问是不加划线,内部访问的是加_。
public和private这两上拼写并不能决定这个。
如果public_var改为_public_var那它就是内部访问了。
这体现了Python"约定优于配置"的设计哲学。
2. 双下划线前缀(__var)
双下划线前缀会触发Python的名称修饰机制(name mangling),这是Python中最接近"私有"的概念。
特点:
- 会被Python解释器自动修改名称为 _ClassName__var
- 主要目的是避免子类意外覆盖父类的方法或属性
- 提供了一定程度的访问保护,但仍可通过修改后的名称访问
示例:
class MyClass: def __init__(self): self.__private_var = "这是双下划线私有变量" def __private_method(self): return "这是双下划线私有方法" def access_private(self): # 类内部可以正常访问 return self.__private_var + " " + self.__private_method() # 使用 obj = MyClass() # print(obj.__private_var) # 报错: AttributeError # obj.__private_method() # 报错: AttributeError # 通过修改后的名称访问 print(obj._MyClass__private_var) # 输出: 这是双下划线私有变量 print(obj.access_private()) # 通过公共方法访问私有成员
3. 双下划线前后缀(__var__)
这种格式用于Python的特殊方法(魔术方法),如 __init__ 、 __str__ 等,通常有特殊用途。
特点:
- 由Python保留用于特殊用途
- 自定义名称应避免使用这种格式
4. 名称修饰(Name Mangling)原理
当类中定义以双下划线开头的属性或方法时,Python解释器会自动将其名称修改为 _ClassName__var ,
这可以防止子类意外覆盖父类的方法或属性。
示例:
class Parent: def __method(self): return "父类方法" def call_method(self): return self.__method() class Child(Parent): def __method(self): # 不会覆盖父类的__method return "子类方法" parent = Parent() child = Child() print(parent.call_method()) # 输出: 父类方法 print(child.call_method()) # 输出: 父类方法(而不是子类方法)
5. 实际应用建议
- 对于不希望被外部直接访问的成员,使用单下划线前缀
- 当需要防止子类覆盖父类方法或属性时,使用双下划线前缀
- 遵循"我们都是成年人"的原则,Python更多依赖程序员的自律
- 在API设计中,明确区分公共接口(无下划线)和内部实现(单下划线)
Python的访问控制机制体现了其"约定优于配置"的设计哲学,它给予开发者很大的灵活性,
同时也要求开发者遵守这些约定以保持代码的可维护性。
(四)多进程编程
Python完全支持多进程编程,这是Python中实现并行计算的重要方式,特别适合处理CPU密集型任务。
下面详细介绍Python多进程编程的相关内容:
1. 多进程的实现模块
Python主要通过 multiprocessing 模块实现多进程编程,该模块提供了类似于 threading 的API,
但创建的是独立的进程而不是线程。
2. 基本使用示例
import multiprocessing import time def worker(num): """进程工作函数""" print(f'进程 {num} 开始工作') time.sleep(2) print(f'进程 {num} 完成工作') if __name__ == '__main__': # 创建并启动3个进程 processes = [] for i in range(3): p = multiprocessing.Process(target=worker, args=(i,)) processes.append(p) p.start() # 等待所有进程结束 for p in processes: p.join() print('所有进程执行完毕')
3. 多进程的主要特点
- 真正的并行执行 :每个进程有独立的Python解释器和内存空间,不受GIL(全局解释器锁)的限制,可以充分利用多核CPU
- 进程间通信 :由于进程间内存不共享,需要使用特定的通信机制,如队列(Queue)、管道(Pipe)、共享内存(Value/Array)等
- 资源消耗较大 :创建和管理进程比线程开销大,每个进程都有独立的内存空间
- 稳定性高 :一个进程崩溃不会影响其他进程
4. 进程池的使用
对于大量短期任务,可以使用进程池来避免频繁创建和销毁进程的开销:
import multiprocessing def square(x): return x * x if __name__ == '__main__': # 创建包含4个进程的进程池 with multiprocessing.Pool(processes=4) as pool: # 映射任务到进程池 results = pool.map(square, [1, 2, 3, 4, 5]) print(f'计算结果: {results}') # 输出: [1, 4, 9, 16, 25]
5. 进程间通信方式
- 队列(Queue) :线程安全的多生产者、多消费者队列
- 管道(Pipe) :用于两个进程间的通信
- 共享内存(Value/Array) :在进程间共享变量
- 管理器(Manager) :创建可在进程间共享的更复杂的数据结构(列表、字典等)
6. 多进程与多线程的选择
- 多进程 :适合CPU密集型任务,如数据计算、图像处理等
- 多线程 :适合I/O密集型任务,如网络请求、文件读写等
7. 其他相关模块
- concurrent.futures.ProcessPoolExecutor :提供高级异步执行接口
- subprocess :用于创建新进程并与之交互
- os.fork() :在Unix/Linux系统上创建进程(Windows不支持)
Python的多进程编程为CPU密集型任务提供了真正的并行计算能力,
有效弥补了多线程受GIL限制的不足,是Python并行编程的重要组成部分。
(三)python的多线程
Python的多线程是一种并发编程方式,但由于GIL(全局解释器锁)的存在,
其特性与其他语言有所不同。下面详细介绍Python多线程的相关内容:
1. 基本概念与使用
Python的多线程通过内置的 threading 模块实现。多线程允许程序同时执行多个任务,
但需要注意的是,由于GIL的限制,Python的多线程在CPU密集型任务中无法真正实现并行。
基本使用示例 :
import threading import time # 定义线程函数 def print_numbers(): for i in range(5): print(f"数字: {i}") time.sleep(1) def print_letters(): for letter in 'ABCDE': print(f"字母: {letter}") time.sleep(1) # 创建并启动线程 thread1 = threading.Thread(target=print_numbers) thread2 = threading.Thread(target=print_letters) thread1.start() thread2.start() # 等待线程结束 thread1.join() thread2.join() print("所有线程执行完毕")
2. GIL的影响
**GIL(全局解释器锁)**是Python解释器中的一个机制,它确保在任何时刻只有一个线程在执行Python字节码。
这意味着,即使在多核CPU上,Python的多线程也无法同时执行Python代码。
GIL的影响 :
- 对于CPU密集型任务(如大量计算),多线程并不能提高性能,甚至可能因为线程切换而降低性能
- 对于I/O密集型任务(如网络请求、文件读写),多线程可以提高效率,因为线程在等待I/O时会释放GIL,允许其他线程执行
3. 线程同步
由于多线程共享同一进程的内存空间,当多个线程访问共享资源时,需要进行同步以避免数据竞争。
Python提供了多种线程同步机制:
锁(Lock) :
import threading # 创建锁对象 lock = threading.Lock() shared_counter = 0 def increment_counter(): global shared_counter for _ in range(1000): with lock: # 使用with语句自动获取和释放锁 shared_counter += 1 # 创建多个线程 threads = [] for _ in range(10): thread = threading.Thread(target=increment_counter) threads.append(thread) thread.start() # 等待所有线程完成 for thread in threads: thread.join() print(f"最终计数器值: {shared_counter}") # 应该是10000
其他同步原语 :
- RLock :可重入锁,允许同一线程多次获取锁
- Condition :条件变量,用于线程间的通知机制
- Semaphore :信号量,控制对资源的访问数量
- Event :事件,用于线程间的简单通知
- Barrier :栅栏,等待指定数量的线程到达后再继续
4. 守护线程
守护线程(Daemon Thread)是一种在后台运行的线程,当所有非守护线程结束时,守护线程会被自动终止。
import threading import time def background_task(): while True: print("守护线程运行中...") time.sleep(1) # 创建守护线程 daemon_thread = threading.Thread(target=background_task) daemon_thread.daemon = True # 设置为守护线程 daemon_thread.start() # 主线程执行一段时间后结束 print("主线程开始执行") time.sleep(3) print("主线程结束,守护线程也会被终止")
5. 线程池
对于需要创建大量线程的场景,使用线程池可以更高效地管理线程资源。
Python的 concurrent.futures 模块提供了 ThreadPoolExecutor 类来实现线程池。
import concurrent.futures import time def process_task(task_id): print(f"开始处理任务 {task_id}") time.sleep(2) # 模拟耗时操作 return f"任务 {task_id} 完成" # 创建线程池 with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor: # 提交任务到线程池 future_to_task = {executor.submit(process_task, i): i for i in range(5)} # 获取任务结果 for future in concurrent.futures.as_completed(future_to_task): task_id = future_to_task[future] try: result = future.result() print(result) except Exception as e: print(f"任务 {task_id} 发生异常: {e}")
6. Python多线程的适用场景
由于GIL的存在,Python多线程主要适用于以下场景:
- I/O密集型任务(网络请求、文件读写、数据库操作等)
- 有大量等待时间的任务
- 需要响应式UI的应用程序
对于CPU密集型任务,Python提供了 multiprocessing 模块来实现真正的并行计算,它通过创建多个进程来绕过GIL的限制。
7. 与多进程的对比
8. 并发编程的其他选择
除了多线程和多进程,Python还提供了异步编程的方式来处理并发:
异步编程(async/await) :
import asyncio async def say_after(delay, what): await asyncio.sleep(delay) print(what) async def main(): # 并发执行两个协程 task1 = asyncio.create_task(say_after(1, "你好")) task2 = asyncio.create_task(say_after(2, "世界")) print("开始") # 等待两个任务完成 await task1 await task2 print("结束") # 运行主协程 asyncio.run(main())
异步编程适用于I/O密集型任务,它使用单线程事件循环来管理多个任务,避免了线程切换的开销。
总结
Python的多线程是一种并发编程方式,由于GIL的存在,它在CPU密集型任务中无法实现真正的并行,
但在I/O密集型任务中可以提高效率。
在实际开发中,应根据任务类型选择合适的并发方式:
I/O密集型任务可以使用多线程或异步编程,而CPU密集型任务则应使用多进程。
(二)动态语言的重要概念:鸭子类型
鸭子类型(Duck Typing)是Python等动态类型语言中的一个重要概念,
它强调对象的行为(方法和属性)比对象的具体类型更重要。
这个概念源自一句谚语:"如果它走路像鸭子,叫声像鸭子,那么它就是鸭子"。
核心思想
在鸭子类型系统中,我们不关心一个对象是什么类型,只关心它是否具有我们需要的方法或属性。
只要对象实现了特定的方法或具有特定的属性,我们就可以用它来完成相应的任务,
而不需要它属于某个特定的类或实现某个特定的接口。
简单示例
假设我们有一个函数,它需要一个可以"叫"的对象:
def make_sound(animal): animal.speak()
现在,我们有两个完全不同的类:
class Dog: def speak(self): print("汪汪汪!") class Cat: def speak(self): print("喵喵喵!")
尽管 Dog 和 Cat 是两个完全不同的类,它们之间没有继承关系,但它们都有一个 speak() 方法。
因此,我们可以将它们的实例都传递给 make_sound() 函数:
dog = Dog() cat = Cat() make_sound(dog) # 输出: 汪汪汪! make_sound(cat) # 输出: 喵喵喵!
这里, make_sound() 函数并不关心传入的对象是什么类型,只要它有一个 speak() 方法即可,这就是鸭子类型的核心思想。
更实际的例子
在Python中,很多内置函数和操作符都依赖于鸭子类型。
例如, len() 函数不关心对象是什么类型,只要它有 __len__() 方法即可:
class Book: def __init__(self, title, pages): self.title = title self.pages = pages def __len__(self): return self.pages class Library: def __init__(self, books): self.books = books def __len__(self): return len(self.books) # 创建对象 book = Book("Python编程", 300) library = Library([book, Book("数据分析", 400)]) # 使用len()函数 print(len(book)) # 输出: 300 print(len(library)) # 输出: 2
同样,迭代器协议也是基于鸭子类型的。
一个对象只要实现了 __iter__() 和 __next__() 方法,就可以被用于 for 循环中。
与接口的区别
在静态类型语言如C#中,我们通常需要定义接口并让类显式实现这些接口。例如:
// C#中的接口定义 interface ISpeaker { void Speak(); } // 显式实现接口 class Dog : ISpeaker { public void Speak() { Console.WriteLine("汪汪汪!"); } } // 函数接受接口类型 void MakeSound(ISpeaker speaker) { speaker.Speak(); }
而在Python的鸭子类型系统中,我们不需要显式定义接口或让类显式实现接口,只要对象具有所需的方法即可。
这种方式更加灵活,但也可能在运行时才发现错误(例如,如果对象没有所需的方法)。
鸭子类型的优缺点
优点 :
- 代码更加灵活,可以轻松扩展
- 不需要复杂的继承层次结构
- 促进了组合优于继承的设计原则
缺点 :
- 错误可能在运行时才被发现
- 代码的可读性可能降低,因为类型信息不明确
- IDE的自动完成和类型检查功能可能不够强大
为了弥补这些缺点,Python 3引入了类型提示(Type Hints),允许开发者在代码中添加类型信息,
虽然这些信息在运行时会被忽略,但可以提高代码的可读性和可维护性,也有助于IDE提供更好的支持。
总之,鸭子类型是Python灵活性的重要体现,它允许我们编写更加通用和可复用的代码,
而不必严格遵守特定的类型层次结构。
(一)python与C#的不同点
Python和C#是两种流行但设计理念差异较大的编程语言,以下是它们之间的主要不同点:
1. 语法特点
Python :
- 使用缩进(空格或制表符)来表示代码块,没有大括号
- 语句结尾不需要分号
- 动态类型,变量声明不需要指定类型
- 简洁的语法,代码行数通常更少
- 支持多行字符串和文档字符串
C# :
- 使用大括号 {} 表示代码块
- 语句结尾需要分号
- 静态类型,变量声明必须指定类型(C# 10后支持var类型推断)
- 更严格的语法规则
- 支持XML文档注释
示例对比 :
# Python定义函数
def greet(name):
if name:
return f"Hello, {name}!"
else:
return "Hello, World!"
// C#定义函数
public string Greet(string name)
{
if (name != null)
{
return $"Hello, {name}!";
}
else
{
return "Hello, World!";
}
}
2. 类型系统
Python :
- 动态类型语言,变量类型在运行时确定
- 弱类型系统,可以进行隐式类型转换
- 变量不需要声明类型
- 支持鸭子类型("如果它走路像鸭子,叫声像鸭子,那么它就是鸭子")
C# :
- 静态类型语言,变量类型在编译时确定
- 强类型系统,类型转换需要显式进行
- 变量必须声明类型
- 主要基于名义类型系统
3. 执行方式
Python :
- 解释型语言,代码逐行解释执行
- 不需要编译成中间代码(尽管CPython有字节码)
- 执行速度相对较慢
- 跨平台通过解释器实现
C# :
- 编译型语言,代码先编译为中间语言(IL)
- 运行时通过.NET CLR(公共语言运行时)的JIT编译器转换为机器码
- 执行速度较快
- 跨平台通过.NET Core/.NET 5+实现
4. 内存管理
Python :
- 使用引用计数作为主要的垃圾回收机制
- 辅以循环引用检测器解决引用计数无法处理的情况
- 自动内存管理,无需手动释放内存
- 有全局解释器锁(GIL)限制多线程性能
C# :
- 使用标记-清除算法的垃圾回收器
- 有分代垃圾回收机制
- 自动内存管理,支持析构函数和using语句处理资源
- 无GIL,多线程可以充分利用多核
5. 编程范式
Python :
- 支持多种编程范式:面向对象、函数式、命令式
- 对函数式编程支持较好(高阶函数、lambda表达式等)
- 设计哲学是"一种明确的做事方式"(虽然实际中可能有多种方式)
- 强调代码可读性("可读性计数")
C# :
- 主要是面向对象编程语言
- 近年来增加了对函数式编程的支持(LINQ、lambda表达式等)
- 设计哲学更强调类型安全和性能
- 支持更多的OOP特性(如访问修饰符、接口、抽象类等)
6. 生态系统
Python :
- 强大的科学计算和数据分析库(NumPy、Pandas、SciPy)
- 优秀的机器学习框架(TensorFlow、PyTorch、scikit-learn)
- 丰富的Web框架(Django、Flask、FastAPI)
- 广泛应用于数据科学、人工智能、自动化脚本等领域
C# :
- 强大的企业应用开发框架(ASP.NET、Entity Framework)
- 优秀的桌面应用开发工具(WPF、WinForms、MAUI)
- 游戏开发支持(Unity游戏引擎使用C#)
- 广泛应用于企业软件、Web应用、游戏开发等领域
7. 异常处理
Python :
- 使用try-except-else-finally结构
- 异常是类,所有异常继承自BaseException
- 支持else块(当没有异常时执行)
- 采用EAFP(Easier to Ask Forgiveness than Permission)风格
C# :
- 使用try-catch-finally结构
- 异常是类,所有异常继承自Exception
- 没有else块
- 支持异常过滤器
- 更倾向于LBYL(Look Before You Leap)风格
8. 并发与并行
Python :
- 由于GIL的存在,多线程在CPU密集型任务中无法真正并行
- 对于CPU密集型任务,通常使用多进程(multiprocessing模块)
- 对异步编程支持良好(async/await语法)
C# :
- 没有GIL限制,多线程可以真正并行执行
- 提供丰富的并发编程API(Task、Parallel、async/await等)
- 对异步编程支持完善
9. 访问控制
Python :
- 没有严格的访问控制机制
- 使用约定来表示私有成员(单下划线和双下划线前缀)
- 双下划线前缀会触发名称修饰,但仍可访问
C# :
- 有严格的访问控制机制
- 提供public、private、protected、internal等访问修饰符
- 严格限制对私有成员的访问
10. 扩展性
Python :
- 可以轻松集成C/C++代码以提高性能
- 使用ctypes、Cython等工具与C/C++交互
- 本身是解释型语言,扩展模块通常编译为共享库
C# :
- 可以使用P/Invoke调用本机代码
- 支持通过COM互操作与其他语言交互
- 主要是编译型语言,扩展通常通过类库实现

