Python 小技巧:路径检查函数

研究目的

在对输入路径进行核查时,需要检查其是否存在、是否可访问,并记录错误信息。

1.0

  • 编写两个辅助函数
    • check_directory_path(path: Path) -> dict:返回一个字典,包含是否存在、是否可读、错误信息等。
    • check_file_path(path: Path) -> dict:同样返回一个字典,包含文件是否存在、是否可读、错误信息等。
from datetime import datetime
import logging
from pathlib import Path
from typing import Optional, Union, Tuple, Dict, Any
import os
import stat
import sys

def check_directory_path(path: Path) -> Tuple[bool, Dict[str, Any]]:
    """
    检查目录路径的可访问性
    
    参数:
        path: 要检查的Path对象
        
    返回:
        (是否有效, 错误信息字典)
    """
    result = {
        "exists": False,
        "readable": False,
        "writable": False,
        "error_messages": []
    }
    
    try:
        # 检查路径是否存在
        if not path.exists():
            result["error_messages"].append("目录不存在")
            return False, result
        
        result["exists"] = True
        
        # 检查是否是目录
        if not path.is_dir():
            result["error_messages"].append("路径不是目录")
            return False, result
        
        # 检查读权限
        try:
            # 尝试列出目录内容
            next(path.iterdir())
            result["readable"] = True
        except PermissionError:
            result["error_messages"].append("没有读取目录的权限")
        except StopIteration:
            # 空目录,但有读取权限
            result["readable"] = True
        except Exception as e:
            result["error_messages"].append(f"读取目录时发生错误: {e}")
        
        # 检查写权限
        try:
            # 尝试创建一个临时文件来测试写权限
            test_file = path / ".write_test"
            test_file.touch()
            test_file.unlink()  # 删除测试文件
            result["writable"] = True
        except PermissionError:
            result["error_messages"].append("没有写入目录的权限")
        except Exception as e:
            result["error_messages"].append(f"测试目录写入权限时发生错误: {e}")
        
        # 如果没有错误消息,则目录可访问
        is_valid = len(result["error_messages"]) == 0
        return is_valid, result
        
    except Exception as e:
        result["error_messages"].append(f"检查目录时发生意外错误: {e}")
        return False, result

def check_file_path(path: Path) -> Tuple[bool, Dict[str, Any]]:
    """
    检查文件路径的可访问性
    
    参数:
        path: 要检查的Path对象
        
    返回:
        (是否有效, 错误信息字典)
    """
    result = {
        "exists": False,
        "readable": False,
        "writable": False,
        "error_messages": []
    }
    
    try:
        # 检查路径是否存在
        if not path.exists():
            result["error_messages"].append("文件不存在")
            return False, result
        
        result["exists"] = True
        
        # 检查是否是文件
        if not path.is_file():
            result["error_messages"].append("路径不是文件")
            return False, result
        
        # 检查读权限
        try:
            with open(path, 'rb'):
                pass
            result["readable"] = True
        except PermissionError:
            result["error_messages"].append("没有读取文件的权限")
        except Exception as e:
            result["error_messages"].append(f"读取文件时发生错误: {e}")
        
        # 检查写权限
        try:
            # 尝试以追加模式打开文件来测试写权限
            with open(path, 'ab'):
                pass
            result["writable"] = True
        except PermissionError:
            result["error_messages"].append("没有写入文件的权限")
        except Exception as e:
            result["error_messages"].append(f"测试文件写入权限时发生错误: {e}")
        
        # 如果没有错误消息,则文件可访问
        is_valid = len(result["error_messages"]) == 0
        return is_valid, result
        
    except Exception as e:
        result["error_messages"].append(f"检查文件时发生意外错误: {e}")
        return False, result

def check_path(path: Path) -> Tuple[bool, Dict[str, Any]]:
    """
    检查路径的可访问性,根据路径类型调用相应的检查函数
    
    参数:
        path: 要检查的Path对象
        
    返回:
        (是否有效, 错误信息字典)
    """
    logging.info(f"开始检查路径: {path}")
    
    if path.is_file():
        logging.info("路径是文件,进行文件检查")
        return check_file_path(path)
    elif path.is_dir():
        logging.info("路径是目录,进行目录检查")
        return check_directory_path(path)
    else:
        # 路径不存在或其他情况
        result = {
            "exists": False,
            "readable": False,
            "writable": False,
            "error_messages": ["路径不存在或无法确定类型"]
        }
        logging.warning("路径不存在或无法确定类型")
        return False, result

def get_user_input() -> Path:
    """
    获取用户输入的路径并进行验证
    
    返回:
        标准化后的Path对象
    """
    while True:
        user_input = input("请输入路径: ").strip()
        
        # 第一步:检查输入是否为空
        if not user_input:
            print("输入不能为空,请重新输入。")
            logging.warning("用户输入了空路径")
            continue
        
        # 第二步:标准化路径
        try:
            normalized_path = normalize_path(user_input)
            logging.info(f"标准化后的路径: {normalized_path}")
        except Exception as e:
            print(f"路径标准化失败: {e}")
            logging.error(f"路径标准化失败: {user_input} - {e}")
            continue
        
        # 第三步:检查路径
        is_valid, check_result = check_path(normalized_path)
        
        # 记录检查结果
        if is_valid:
            logging.info("路径检查通过")
            print("路径有效且可访问。")
            return normalized_path
        else:
            logging.warning(f"路径检查未通过: {check_result['error_messages']}")
            print("路径存在问题:")
            for error in check_result['error_messages']:
                print(f"  - {error}")
            
            # 询问用户是否继续使用此路径
            choice = input("是否继续使用此路径? (y/n): ").strip().lower()
            if choice == 'y':
                logging.info("用户选择继续使用有问题的路径")
                return normalized_path
            else:
                logging.info("用户选择重新输入路径")
                print("请重新输入路径。")

1.5

使用 pathlib 模块优化后的代码,它更加符合 Python 3.0 的风格,并且利用了 pathlib 提供的面向对象方法来处理文件系统操作,更加简洁:

from datetime import datetime
import logging
from pathlib import Path
from typing import Optional, Union, List, Tuple
import sys

# 使用 pathlib 优化的检查函数
def check_directory_path(path: Path) -> Tuple[bool, List[str]]:
    """检查目录路径的可访问性"""
    errors = []
    
    if not path.exists():
        errors.append("目录不存在")
        return False, errors
    
    if not path.is_dir():
        errors.append("路径不是目录")
        return False, errors
    
    # 检查读权限 - 尝试列出目录内容
    try:
        # 使用 pathlib 的 iterdir() 方法检查读权限
        next(path.iterdir())
    except PermissionError:
        errors.append("没有读取目录的权限")
    except StopIteration:
        # 空目录,但有读取权限
        pass
    except Exception as e:
        errors.append(f"读取目录时发生错误: {e}")
    
    # 检查写权限 - 尝试创建和删除临时文件
    test_file = path / ".write_test.tmp"
    try:
        test_file.touch()
        test_file.unlink()  # 删除测试文件
    except PermissionError:
        errors.append("没有写入目录的权限")
    except Exception as e:
        errors.append(f"测试目录写入权限时发生错误: {e}")
    
    return len(errors) == 0, errors

def check_file_path(path: Path) -> Tuple[bool, List[str]]:
    """检查文件路径的可访问性"""
    errors = []
    
    if not path.exists():
        errors.append("文件不存在")
        return False, errors
    
    if not path.is_file():
        errors.append("路径不是文件")
        return False, errors
    
    # 检查读权限 - 尝试打开文件
    try:
        with path.open('rb') as f:
            # 尝试读取一个字节
            f.read(1)
    except PermissionError:
        errors.append("没有读取文件的权限")
    except Exception as e:
        errors.append(f"读取文件时发生错误: {e}")
    
    # 检查写权限 - 尝试以追加模式打开文件
    try:
        with path.open('ab'):
            pass
    except PermissionError:
        errors.append("没有写入文件的权限")
    except Exception as e:
        errors.append(f"测试文件写入权限时发生错误: {e}")
    
    return len(errors) == 0, errors

def check_path(path: Path) -> Tuple[bool, List[str]]:
    """检查路径可访问性,只在遇到问题时记录日志"""
    if path.is_file():
        return check_file_path(path)
    elif path.is_dir():
        return check_directory_path(path)
    else:
        # 路径不存在时记录日志
        logging.warning(f"路径不存在或类型未知: {path}")
        return False, ["路径不存在或无法确定类型"]

# 用户输入处理
def get_user_input() -> Path:
    """获取用户输入的路径并进行验证"""
    while True:
        user_input = input("请输入路径: ").strip()
        
        if not user_input:
            print("输入不能为空,请重新输入。")
            logging.warning("用户输入了空路径")
            continue
        
        try:
            normalized_path = normalize_path(user_input)
        except Exception as e:
            logging.error(f"路径标准化失败: {e}")
            print(f"路径标准化失败: {e}")
            continue
        
        is_valid, errors = check_path(normalized_path)
        
        if is_valid:
            return normalized_path
        
        print("路径存在问题:")
        for error in errors:
            print(f"  - {error}")
        
        choice = input("是否继续使用此路径? (y/n): ").strip().lower()
        if choice == 'y':
            logging.warning(f"用户选择继续使用有问题的路径: {normalized_path}")
            return normalized_path
        else:
            print("请重新输入路径。")

主要优化点:

  1. 完全使用 pathlib 方法

    • 使用 path.exists(), path.is_dir(), path.is_file() 等 pathlib 方法替代 os 模块的函数
    • 使用 path.iterdir() 检查目录读取权限
    • 使用 path.touch()path.unlink() 测试目录写入权限
    • 使用 path.open() 方法检查文件读写权限
  2. 遵循 EAFP (Easier to Ask for Forgiveness than Permission) 原则

    • 直接尝试执行操作并捕获异常,而不是先检查权限
    • 这提供了更准确的权限检查,因为权限可能在检查和实际操作之间发生变化
  3. Pythonic 的代码风格

    • 充分利用 pathlib 的面向对象特性
    • 代码更简洁、更易读

2.0

方法1:使用 os.access()

# 检查写权限
if not os.access(path, os.W_OK):
    errors.append("没有写入目录的权限")
  • 优点

    • 非常快速,不涉及任何磁盘 I/O
    • 不会创建任何临时文件
  • 缺点

    • 可能存在 TOCTOU (Time of Check to Time of Use) 竞争条件
    • 在某些特殊情况下可能不准确(如磁盘已满、权限在检查后立即更改等)

方法2:使用 pathlib

# 如果目录不存在,尝试创建它来测试写权限
if not path.exists():
    try:
        path.mkdir(parents=True, exist_ok=True)
        # 创建成功,说明有写权限
        path.rmdir()  # 删除测试目录
    except PermissionError:
        errors.append("没有在当前目录创建子目录的权限")
    except Exception as e:
        errors.append(f"测试目录创建权限时发生错误: {e}")

结合两种方法

from datetime import datetime
import logging
from pathlib import Path
from typing import Optional, Union, List, Tuple, Dict
import os
import sys
import tempfile

def check_directory_path(path: Path) -> Tuple[bool, List[str]]:
    """检查目录路径的可访问性"""
    errors = []
    
    if not path.exists():
        errors.append("目录不存在")
        return False, errors
    
    if not path.is_dir():
        errors.append("路径不是目录")
        return False, errors
    
    # 检查读权限
    if not os.access(path, os.R_OK):
        errors.append("没有读取目录的权限")
    else:
        # 对通过快速检查的进行实际验证
        try:
            # 尝试列出目录内容(但限制数量)
            with os.scandir(path) as it:
                next(it)  # 尝试获取第一个条目
        except StopIteration:
            pass  # 空目录是正常的
        except PermissionError:
            errors.append("实际读取目录失败:权限不足")
        except Exception as e:
            errors.append(f"读取目录时发生错误: {e}")
    
    # 检查写权限 - 使用快速检查加验证的方式
    if not os.access(path, os.W_OK):
        errors.append("没有写入目录的权限")
    else:
        # 对通过快速检查的进行实际验证
        # 替换原来的写权限检查
        try:
            with tempfile.TemporaryDirectory(dir=path) as _:
                pass  # 成功创建即表示有写权限
        except PermissionError:
            errors.append("实际写入目录失败:权限不足")
        except Exception as e:
            errors.append(f"测试目录写入权限时发生错误: {e}")
    
    return len(errors) == 0, errors

def check_file_path(path: Path) -> Tuple[bool, List[str]]:
    """检查文件路径的可访问性"""
    errors = []
    
    if not path.exists():
        errors.append("文件不存在")
        return False, errors
    
    if not path.is_file():
        errors.append("路径不是文件")
        return False, errors
    
    # 检查读权限
    # 使用os.access进行快速初步检查
    if not os.access(path, os.R_OK):
        errors.append("没有读取文件的权限")
    else:
        # 对通过初步检查的进行实际读取验证
        try:
            with path.open('rb') as f:
                f.read(1) # 尝试读取一个字节
        except PermissionError:
            errors.append("没有读取文件的权限")
        except Exception as e:
            errors.append(f"读取文件时发生错误: {e}")
    
    # 检查写权限
    if not os.access(path, os.W_OK):
        errors.append("没有写入文件的权限")
    else:
        # 对通过初步检查的进行实际写入验证
        try:
            with path.open('ab'):
                pass  # 尝试以追加模式打开
        except PermissionError:
            errors.append("没有写入文件的权限")
        except Exception as e:
            errors.append(f"测试文件写入权限时发生错误: {e}")
    
    return len(errors) == 0, errors

def check_path(path: Path) -> Tuple[bool, List[str]]:
    """检查路径可访问性"""
    if path.is_file():
        return check_file_path(path)
    elif path.is_dir():
        return check_directory_path(path)
    else:
        logging.warning(f"路径不存在或类型未知: {path}")
        return False, ["路径不存在或无法确定类型"]

# 用户输入处理
def get_user_input() -> Path:
    """获取用户输入的路径并进行验证"""
    while True:
        user_input = input("请输入路径: ").strip()
        
        if not user_input:
            print("输入不能为空,请重新输入。")
            logging.warning("用户输入了空路径")
            continue
        
        try:
            normalized_path = normalize_path(user_input)
        except Exception as e:
            logging.error(f"路径标准化失败: {e}")
            print(f"路径标准化失败: {e}")
            continue
        
        is_valid, errors = check_path(normalized_path)
        
        if is_valid:
            return normalized_path
        
        print("路径存在问题:")
        for error in errors:
            print(f"  - {error}")
        
        choice = input("是否继续使用此路径? (y/n): ").strip().lower()
        if choice == 'y':
            logging.warning(f"用户选择继续使用有问题的路径: {normalized_path}")
            return normalized_path
        else:
            print("请重新输入路径。")
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值