【AI Study】第一天,多线程、文件操作

发布于:2025-05-24 ⋅ 阅读:(10) ⋅ 点赞:(0)

2025-05-21

多线程、文件操作(multi-thread.py)

  • with关键字:
    • 代码更清晰简洁,避免手动写try...finally
    • 简化资源管理的操作,确保资源在使用后能够被正常释放(即便是代码块发生异常,资源也会被释放),
    • 可应用的场景比较多,比如:多线程、文件操作、数据库操作、锁操作等等;
    • 工作原理:依赖对象的__enter__()__exit__()方法:
      • 在进入with代码块时,enter方法被调用,返回资源对象;
      • 在离开with代码块时,exit方法被调用,释放资源对象;
    • 举例:
     # 写入文件
    with open(filename, 'wb') as file:
      for chunk in response.iter_content(chunk_size=8192):
          file.write(chunk)
    
    # 创建线程池,最大线程数为5
    with ThreadPoolExecutor(max_workers=5) as executor:
      # 提交所有下载任务
      futures = [executor.submit(download_image, url) for url in IMAGE_URLS]
          
      # 获取所有任务的结果
      results = [future.result() for future in futures]
    

列表推导式(List Comprehension)

  • 说明
    # expression:对每个元素执行的操作,如item * 2
    # item:迭代变量,表示可迭代对象中的每个元素
    # iterable:可迭代对象,如列表、元组、字符串等
    [expression for item in iterable]
    
  • 示例
    # 传统写法
    square = []
    for x in [1,2,3,4]
      squares.append(x**2)
    print(squares) # 输出:[1,4,9,16]
    
    # 列表推导式
    squares = [x**2 for x in [1,2,3,4]]
    print(squares) # 输出:[1,4,9,16]
    

f-string,格式化字符串字面量(Formatted String Literal)

  • 说明
    Python 3.6及以上版本引入的语法糖,支持嵌入表达式,包括变量、函数、数学运算、格式化、对齐填充等;
  • 示例
    name = "Zhangsan"
    age = 30
    height = 187.237
    print(f"Name:{name},Age:{30+1},Height:{height:.2f}")
    

流式下载文件,并存储到本地

  • 代码
    def download_image(url):
      """下载单个图片的函数"""
      try:
          # 获取图片文件名
          filename = f'downloads/image_{url.split("=")[-1]}.jpg'
          
          # 发送HTTP请求
          response = requests.get(url, stream=True)
          response.raise_for_status()  # 检查请求是否成功
          
          # 写入文件
          with open(filename, 'wb') as file:
              for chunk in response.iter_content(chunk_size=8192):
                  file.write(chunk)
                  
          print(f"下载完成: {filename}")
          return filename
          
      except Exception as e:
          print(f"下载失败 {url}: {e}")
          return None
    
  • 解析
    • url.split("=")[-1]:根据字符串=分割成列表,取最后一个元素
    • request.get(url, stream=True):代表流式获取文件,降低内存消耗,尤其是读取大文件时
    • response.raise_for_status:安全检查机制,检查http响应的状态码,如果是200~299,则会继续执行,否则会抛出异常
    • for chunk in response.iter_content(chunk_size=8192):在流式读取文件时,以8192字节(8k)的数据库大小进行读取下载

多线程

  • 代码
      # 创建线程池,最大线程数为5
      with ThreadPoolExecutor(max_workers=5) as executor:
        # 提交所有下载任务
        futures = [executor.submit(download_image, url) for url in IMAGE_URLS]
            
        # 获取所有任务的结果
        results = [future.result() for future in futures]
    
  • 解释
    • with关键字:确保线程池在使用完后被正确关闭,避免代码块发生异常时线程池未正确关闭;
    • ThreadPoolExecutor(max_workers=5) as executor:创建最大线程数为5的线程池
    • futures = [executor.submit(download_image, url) for url in IMAGE_URLS]:列表推导式,生成5个Future对象
    • future.result():线程的执行是executor.submit()方法触发的,future.result()方法只是阻塞在这里等待结果返回

线程安全

  • 代码
def thread_safety_demo():
    """线程安全问题演示"""
    print("\n线程安全问题演示...")
    
    class Counter:
        def __init__(self):
            self.value = 0
            self.lock = threading.Lock()  # 创建锁对象
        
        def increment_unsafe(self):
            """非线程安全的自增方法"""
            self.value += 1
        
        def increment_safe(self):
            """线程安全的自增方法"""
            with self.lock:  # 使用锁保护共享资源
                self.value += 1
    
    def worker(counter, method):
        for _ in range(1000000):
            method()
    
    # 测试非线程安全的计数器
    unsafe_counter = Counter()
    threads = [
        threading.Thread(target=worker, args=(unsafe_counter, unsafe_counter.increment_unsafe))
        for _ in range(10)
    ]
    
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    
    print(f"非线程安全计数器预期结果: 100000,实际结果: {unsafe_counter.value}")
    
    # 测试线程安全的计数器
    safe_counter = Counter()
    threads = [
        threading.Thread(target=worker, args=(safe_counter, safe_counter.increment_safe))
        for _ in range(10)
    ]
    
    for t in threads:
        t.start()
    for t in threads:
        t.join()
    
    print(f"线程安全计数器预期结果: 100000,实际结果: {safe_counter.value}")
    
if __name__ == "__main__":
	thread_safety_demo()

运行结果:

线程安全问题演示...
非线程安全计数器预期结果: 100000,实际结果: 5833995
线程安全计数器预期结果: 100000,实际结果: 10000000
  • 分析
    • 定义一个方法thread_safety_demo(),用来演示线程安全问题
    • 在方法内创建一个对象: Counter:该对象内包含
    • 构造函数:__init__(self),包含两个实例变量:valuelock
      • value:用来计数的变量
      • lock:创建锁对象
    • 线程不安全的方法:increment_unsafe(self)
    • 线程安全的方法:increment_safe(self):
    • 线程内需要执行的任务:worker(counter,method)
      • counter:代表一个对象,这里要传入的是Counter的一个实例,因为我们要用不同的实例去运行相同的循环+1的方法,看结果值的不同来演示线程安全问题
      • method:代表真正要执行的任务
    • 分别用10个线程来执行线程不安全的任务: increment_unsafe和线程安全的任务: increment_safe
      • 注意这里用的不是线程池,而是使用的普通创建线程的方式【不推荐】,主要区别在于:无法控制最大线程数量,无法线程复用
    • 此外,上述demo循环了100万次,如果是1万次的时候,效果不明显,有可能结果相同。

文档字符串(docstring)

  • 代码
def calculate_average(numbers):
    """计算列表中所有数字的平均值。
    
    参数:
        numbers (list): 包含数字的列表
    
    返回:
        float: 平均值
    """
    if not numbers:
        return 0
    return sum(numbers) / len(numbers)
  • 说明
    • 方便阅读:用于说明其功能、参数以及返回值等信息。
    • 字符串与注释的差异:三个双引号创建的是字符串对象,而注释是用 # 或者 ‘’'(三个单引号)来实现的(在非 docstring 场景下)。
    • 格式化方面:在多行字符串里,换行符、缩进等格式都会被保留,所以要留意代码的排版。
    • 原始字符串的使用:如果需要创建原始字符串(忽略转义字符),可以在引号前加上 r,例如 r"““C:\path\to\file””"。