files.py
ultralytics\utils\files.py
目录
files.py
1.所需的库和模块
2.class WorkingDirectory(contextlib.ContextDecorator):
3.def spaces_in_path(path):
4.def increment_path(path, exist_ok=False, sep="", mkdir=False):
5.def file_age(path=__file__):
6.def file_date(path=__file__):
7.def file_size(path):
8.def get_latest_run(search_dir="."):
9.def update_models(model_names=("yolo11n.pt",), source_dir=Path("."), update_names=False):
1.所需的库和模块
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
import contextlib
import glob
import os
import shutil
import tempfile
from contextlib import contextmanager
from datetime import datetime
from pathlib import Path
2.class WorkingDirectory(contextlib.ContextDecorator):
# 这段代码定义了一个名为 WorkingDirectory 的类,它是一个上下文管理器,用于临时更改当前工作目录。这个类继承自 contextlib.ContextDecorator ,这意味着它可以作为上下文管理器( with 语句)使用,也可以作为装饰器使用。
# 定义了一个名为 WorkingDirectory 的类,继承自 contextlib.ContextDecorator 。
# contextlib.ContextDecorator 是一个辅助类,允许上下文管理器同时用作装饰器。这意味着这个类既可以使用 with 语句,也可以用于装饰函数。
class WorkingDirectory(contextlib.ContextDecorator):
# 用于临时更改工作目录的上下文管理器和装饰器。
# 此类允许使用上下文管理器或装饰器临时更改工作目录。
# 它确保在上下文或装饰函数完成后恢复原始工作目录。
# 方法:
# __enter__:将当前目录更改为指定目录。
# __exit__:在退出上下文时恢复原始工作目录。
# 示例:
# 用作上下文管理器:
# >>> with WorkingDirectory('/path/to/new/dir'):
# >>> # 在新目录中执行操作
# >>> pass
# 用作装饰器:
# >>> @WorkingDirectory('/path/to/new/dir')
# >>> def some_function():
# >>> # 在新目录中执行操作
# >>> pass
"""
A context manager and decorator for temporarily changing the working directory.
This class allows for the temporary change of the working directory using a context manager or decorator.
It ensures that the original working directory is restored after the context or decorated function completes.
Attributes:
dir (Path): The new directory to switch to.
cwd (Path): The original current working directory before the switch.
Methods:
__enter__: Changes the current directory to the specified directory.
__exit__: Restores the original working directory on context exit.
Examples:
Using as a context manager:
>>> with WorkingDirectory('/path/to/new/dir'):
>>> # Perform operations in the new directory
>>> pass
Using as a decorator:
>>> @WorkingDirectory('/path/to/new/dir')
>>> def some_function():
>>> # Perform operations in the new directory
>>> pass
"""
# 定义了类的初始化方法 __init__ ,接受一个参数。
# 1.new_dir :要切换到的新目录。
def __init__(self, new_dir):
# 在实例化时将工作目录设置为‘new_dir’,以便与上下文管理器或装饰器一起使用。
"""Sets the working directory to 'new_dir' upon instantiation for use with context managers or decorators."""
# 将传入的 new_dir 赋值给实例变量 self.dir ,表示 目标目录 。
self.dir = new_dir # new dir
# 使用 Path.cwd() 获取当前工作目录,并通过 resolve() 方法解析为绝对路径。
# 将 当前工作目录 存储在实例变量 self.cwd 中,用于在上下文退出时恢复。
self.cwd = Path.cwd().resolve() # current dir
# 定义了 __enter__ 方法,这是上下文管理器进入上下文时调用的方法。
def __enter__(self):
# 进入上下文后将当前工作目录更改为指定目录。
"""Changes the current working directory to the specified directory upon entering the context."""
# os.chdir(path)
# os.chdir() 函数是 Python 的标准库 os 模块中的一个函数,用于更改当前工作目录。
# 参数 :
# path :要更改到的目标目录的路径。
# 功能描述 :
# os.chdir(path) 函数将当前工作目录更改为 path 指定的目录。如果 path 不存在或无法访问,将抛出一个异常。
# 异常处理 :
# 当尝试更改到一个不存在或无法访问的目录时, os.chdir() 会抛出 FileNotFoundError 或 PermissionError 异常。因此,在实际使用中,可能需要捕获这些异常来处理错误情况。
# 使用 os.chdir(self.dir) 切换到目标目录 self.dir 。
# 当进入上下文时,当前工作目录会被切换到指定的目录。
os.chdir(self.dir)
# 定义了 __exit__ 方法,这是上下文管理器退出上下文时调用的方法。 参数 :
# 1.exc_type :异常类型(如果有异常发生)。
# 2.exc_val :异常值(如果有异常发生)。
# 3.exc_tb :异常的回溯信息(如果有异常发生)。
# # noqa 是一个注释,通常用于告诉代码检查工具(如 flake8 )忽略这一行的某些检查。
def __exit__(self, exc_type, exc_val, exc_tb): # noqa
# 退出上下文时恢复原始工作目录。
"""Restores the original working directory when exiting the context."""
# 在退出上下文时,使用 os.chdir(self.cwd) 将工作目录恢复到进入上下文之前的目录。
os.chdir(self.cwd)
# WorkingDirectory 类是一个上下文管理器,用于临时更改当前工作目录。它的主要功能包括。临时切换目录:在进入上下文时切换到指定目录,在退出上下文时恢复到原来的目录。支持 with 语句和装饰器:由于继承自 contextlib.ContextDecorator ,这个类既可以作为上下文管理器使用,也可以作为装饰器使用。
# 使用示例 :
# 作为上下文管理器使用 :
# with WorkingDirectory("/path/to/new/directory"):
# # 在这个代码块中,当前工作目录被切换到 "/path/to/new/directory"
# print("Current directory:", Path.cwd())
# # 退出上下文后,工作目录恢复到原来的目录
# print("Current directory after with block:", Path.cwd())
# 作为装饰器使用 :
# @WorkingDirectory("/path/to/new/directory")
# def my_function():
# # 在这个函数中,当前工作目录被切换到 "/path/to/new/directory"
# print("Current directory in function:", Path.cwd())
# my_function()
# # 函数执行完成后,工作目录恢复到原来的目录
# print("Current directory after function call:", Path.cwd())
# 这种设计使得 WorkingDirectory 类非常灵活,可以在需要临时更改工作目录的场景中方便地使用。
3.def spaces_in_path(path):
# 这段代码定义了一个名为 spaces_in_path 的上下文管理器,用于处理路径中包含空格的情况。它通过临时替换路径中的空格为下划线,避免在某些工具或命令中因空格导致的问题。
# 使用 contextlib.contextmanager 装饰器,将 spaces_in_path 函数定义为一个上下文管理器。这种写法比直接定义类更简洁,适用于简单的上下文管理逻辑。
@contextmanager
# 定义了一个函数 spaces_in_path ,接受一个参数。
# 1.path :需要处理的文件或目录路径。
def spaces_in_path(path):
# 上下文管理器处理名称中带有空格的路径。如果路径包含空格,它会用下划线替换它们,将文件/目录复制到新路径,执行上下文代码块,然后将文件/目录复制回其原始位置。
# Yields:
# (Path):如果存在空格,则用下划线替换临时路径中的空格,否则为原始路径。
# 示例:
# 使用上下文管理器处理带有空格的路径:
# >>> from ultralytics.utils.files import space_in_path
# >>> with space_in_path('/path/with space') as new_path:
# >>> # 您的代码在这里
"""
Context manager to handle paths with spaces in their names. If a path contains spaces, it replaces them with
underscores, copies the file/directory to the new path, executes the context code block, then copies the
file/directory back to its original location.
Args:
path (str | Path): The original path that may contain spaces.
Yields:
(Path): Temporary path with spaces replaced by underscores if spaces were present, otherwise the original path.
Examples:
Use the context manager to handle paths with spaces:
>>> from ultralytics.utils.files import spaces_in_path
>>> with spaces_in_path('/path/with spaces') as new_path:
>>> # Your code here
"""
# If path has spaces, replace them with underscores 如果路径中有空格,则用下划线替换。
# 检查路径中是否包含空格。如果路径是 Path 对象或字符串,将其转换为字符串后检查是否包含空格。
if " " in str(path):
# 检查输入的 path 是否是字符串类型,并将结果存储在变量 string 中。这用于后续决定返回的路径类型(字符串或 Path 对象)。
string = isinstance(path, str) # input type
# 将输入的路径统一转换为 Path 对象,方便后续操作。
path = Path(path)
# Create a temporary directory and construct the new path 创建临时目录并构造新路径。
# tempfile.TemporaryDirectory()
# tempfile.TemporaryDirectory() 是 Python 标准库 tempfile 模块中的一个函数,用于创建一个临时目录。这个临时目录在创建时是空的,并且在使用完毕后可以自动删除。
# 函数定义 :
# with TemporaryDirectory() as tmp_dir:
# print(tmp_dir)
# 参数。TemporaryDirectory() 可以接受一些参数来定制临时目录的行为 :
# dir :指定一个特定的目录,在该目录下创建临时目录。如果没有指定,则使用系统默认的临时文件目录。
# prefix :指定临时目录的前缀。
# suffix :指定临时目录的后缀。
# ignore_cleanup_errors :一个布尔值,指定是否忽略清理时发生的错误,默认为 False 。
# cleanup :一个布尔值,指定是否在退出上下文管理器时清理临时目录,默认为 True 。
# mode :设置目录的权限模式,默认为 0o700 。
# 在示例中, TemporaryDirectory() 被用作上下文管理器,它创建了一个临时目录,并在 with 块中提供了这个目录的路径。当 with 块执行完毕后,临时目录及其内容将被自动删除。
# TemporaryDirectory() 是处理需要临时文件或目录的场合的有用工具,特别是在测试、临时文件处理或任何需要临时存储的场合。使用临时目录可以避免临时文件对主文件系统的污染,并确保资源在使用后被正确清理。
# 使用 tempfile.TemporaryDirectory() 创建一个临时目录,用于存放处理后的文件或目录。 tmp_dir 是 临时目录的路径 。
with tempfile.TemporaryDirectory() as tmp_dir:
# 构造临时路径 tmp_path ,将原始路径的文件名或目录名中的空格替换为下划线。
# 例如,如果原始路径是 /path/to/my folder ,则临时路径可能是 /tmp/tempdir/my_folder 。
tmp_path = Path(tmp_dir) / path.name.replace(" ", "_")
# Copy file/directory 复制文件/目录。
# 如果原始路径是一个目录。
if path.is_dir():
# tmp_path.mkdir(parents=True, exist_ok=True)
# shutil.copytree(src, dst, symlinks=False, ignore=None, dirs_exist_ok=False)
# shutil.copytree() 是 Python 标准库 shutil (shell utilities)模块中的一个函数,用于递归地复制一个目录到另一个位置。这个函数会复制目录中的所有内容,包括子目录和文件。
# 参数 :
# src :源目录的路径。
# dst :目标目录的路径。如果目标目录已经存在,并且 dirs_exist_ok 参数为 False ,则会抛出一个 FileExistsError 异常。
# symlinks :一个布尔值,指定是否复制符号链接。默认为 False ,即不复制符号链接。
# ignore :一个可选的回调函数,用于排除不需要复制的文件或目录。
# dirs_exist_ok :一个布尔值,指定如果目标目录已经存在,是否允许复制操作继续。默认为 False ,如果目标目录存在,则会抛出异常。
# shutil.copytree() 是一个强大的工具,用于复制整个目录树,常用于备份、同步文件或在测试中创建测试数据目录。
# 使用 shutil.copytree 将整个目录复制到临时路径。 dirs_exist_ok=True 参数允许目标目录已存在时继续复制。
shutil.copytree(path, tmp_path)
# 如果原始路径是一个文件。
elif path.is_file():
# 确保临时路径的父目录存在。
tmp_path.parent.mkdir(parents=True, exist_ok=True)
# shutil.copy2(src, dst, *, follow_symlinks=True)
# shutil.copy2() 是 Python 标准库 shutil (shell utilities)模块中的一个函数,用于复制文件,同时尝试保留原文件的元数据,如修改时间和权限等。
# 参数 :
# src :源文件的路径。
# dst :目标文件的路径。如果目标文件已经存在,将会被覆盖。
# follow_symlinks :一个布尔值,默认为 True ,表示是否跟随符号链接。如果设置为 False ,则会复制符号链接本身而不是链接指向的文件。
# 功能描述 :
# shutil.copy2() 函数将一个文件从 src 路径复制到 dst 路径,并尝试保留源文件的元数据。如果 follow_symlinks 参数为 True ,它将复制符号链接所指向的文件;如果为 False ,则复制符号链接本身。
# 异常处理 :
# shutil.copy2() 可能会抛出异常,如 FileNotFoundError (源文件不存在)、 PermissionError (没有权限写入目标文件)等。因此,在实际使用中,你可能需要捕获这些异常来处理错误情况:
# shutil.copy2() 是一个非常有用的函数,它在复制文件的同时保留了尽可能多的文件属性,这在需要保持文件完整性的场景中非常有用。
# shutil.copy2() 是一个方便的工具,用于在需要保留文件元数据的情况下复制文件。与 shutil.copy() 相比, shutil.copy2() 能够更完整地复制文件属性,因此在需要这些属性时应该优先使用 shutil.copy2() 。
# 使用 shutil.copy2 将文件复制到临时路径,保留元数据(如修改时间等)。
shutil.copy2(path, tmp_path)
# 使用 try 块确保在上下文管理器中执行的代码可以正常运行。
try:
# Yield the temporary path 输出临时路径。
# 根据输入路径的类型(字符串或 Path 对象),返回相应的路径。 如果输入是字符串,则返回 临时路径的字符串表示 。 如果输入是 Path 对象,则返回 Path 对象。
yield str(tmp_path) if string else tmp_path
# 使用 finally 块确保在退出上下文时执行清理操作,无论是否发生异常。
finally:
# Copy file/directory back 复制文件/目录。
# 如果临时路径是一个目录。
if tmp_path.is_dir():
# 使用 shutil.copytree 将临时目录的内容复制回原始路径,允许覆盖已存在的目录。
shutil.copytree(tmp_path, path, dirs_exist_ok=True)
# 如果临时路径是一个文件。
elif tmp_path.is_file():
# 使用 shutil.copy2 将临时文件复制回原始路径,保留元数据。
shutil.copy2(tmp_path, path) # Copy back the file 复制回文件。
# 如果路径中没有空格。
else:
# If there are no spaces, just yield the original path 如果没有空格,则直接保留原始路径。
# 直接返回原始路径(无需任何处理)。
yield path
# spaces_in_path 是一个上下文管理器,用于处理路径中包含空格的情况。它的主要功能包括。临时替换空格:将路径中的空格替换为下划线,避免在某些工具或命令中因空格导致的问题。文件或目录复制:将原始路径的内容复制到临时路径,并在退出上下文时将内容复制回原始路径。类型兼容:根据输入路径的类型(字符串或 Path 对象),返回相应类型的路径。自动清理:在退出上下文时,确保临时目录被清理,原始路径的内容被正确恢复。
# 使用示例 :
# with spaces_in_path("/path/to/my folder") as new_path:
# print("Temporary path:", new_path)
# # 在这个上下文内,可以安全地使用 new_path,它不包含空格
# # 例如,运行某些命令或工具
# # 退出上下文后,原始路径的内容被恢复
# print("Original path restored:", Path("/path/to/my folder").exists())
# 这种上下文管理器非常适用于需要处理路径中空格的场景,例如在调用某些不支持空格路径的命令行工具时。
4.def increment_path(path, exist_ok=False, sep="", mkdir=False):
# 这段代码定义了一个函数 increment_path ,用于处理文件或目录路径的增量命名,避免路径冲突,并且可以选择是否创建目录。
# 定义了一个函数 increment_path ,接受以下参数 :
# 1.path :目标路径(可以是文件或目录)。
# 2.exist_ok :布尔值,默认为 False 。如果为 False ,当路径已存在时,会尝试生成一个增量路径;如果为 True ,则直接返回原路径。
# 3.sep :字符串,默认为空。用于分隔增量编号和原路径。
# 4.mkdir :布尔值,默认为 False 。如果为 True ,会创建路径对应的目录(如果路径是文件,则创建其父目录)。
def increment_path(path, exist_ok=False, sep="", mkdir=False):
# 增加文件或目录路径,即 runs/exp --> runs/exp{sep}2、runs/exp{sep}3,... 等等。
# 如果路径存在且 `exist_ok` 不为 True,则通过在路径末尾附加数字和 `sep` 来增加路径。如果路径是文件,则将保留文件扩展名。如果路径是目录,则数字将直接附加到路径末尾。如果 `mkdir` 设置为 True,则如果路径尚不存在,则将创建为目录。
# 示例:
# 增加目录路径:
# >>> from pathlib import Path
# >>> path = Path("runs/exp")
# >>> new_path = increase_path(path)
# >>> print(new_path)
# runs/exp2
# 增加文件路径:
# >>> path = Path("runs/exp/results.txt")
# >>> new_path = increase_path(path)
# >>> print(new_path)
# runs/exp/results2.txt
"""
Increments a file or directory path, i.e., runs/exp --> runs/exp{sep}2, runs/exp{sep}3, ... etc.
If the path exists and `exist_ok` is not True, the path will be incremented by appending a number and `sep` to
the end of the path. If the path is a file, the file extension will be preserved. If the path is a directory, the
number will be appended directly to the end of the path. If `mkdir` is set to True, the path will be created as a
directory if it does not already exist.
Args:
path (str | pathlib.Path): Path to increment.
exist_ok (bool): If True, the path will not be incremented and returned as-is.
sep (str): Separator to use between the path and the incrementation number.
mkdir (bool): Create a directory if it does not exist.
Returns:
(pathlib.Path): Incremented path.
Examples:
Increment a directory path:
>>> from pathlib import Path
>>> path = Path("runs/exp")
>>> new_path = increment_path(path)
>>> print(new_path)
runs/exp2
Increment a file path:
>>> path = Path("runs/exp/results.txt")
>>> new_path = increment_path(path)
>>> print(new_path)
runs/exp/results2.txt
"""
# 将输入的 path 转换为 Path 对象,这是一个跨操作系统的路径处理方式,避免了直接使用字符串拼接路径时可能出现的兼容性问题。
path = Path(path) # os-agnostic
# 判断路径是否存在且 exist_ok 是否为 False 。如果路径已存在且不允许覆盖( exist_ok=False ),则进入增量路径生成逻辑。
if path.exists() and not exist_ok:
# 如果路径是一个文件,则将路径的扩展名(后缀)分离出来,以便在增量路径中保留扩展名。 path.with_suffix("") 去掉文件的扩展名, path.suffix 获取扩展名。如果路径是一个目录,则 suffix 设置为空字符串。
path, suffix = (path.with_suffix(""), path.suffix) if path.is_file() else (path, "")
# Method 1
# 从 2 开始循环,尝试生成增量路径,直到找到一个不存在的路径为止。循环范围是 2 到 9999,表示最多尝试生成 9998 个增量路径。
for n in range(2, 9999):
# 使用格式化字符串生成 增量路径 。 path 是原始路径(去掉扩展名后的部分), sep 是分隔符, n 是增量编号, suffix 是扩展名。
p = f"{path}{sep}{n}{suffix}" # increment path
# 检查生成的增量路径 p 是否不存在。如果不存在,则跳出循环,表示找到了一个可用的路径。
if not os.path.exists(p):
break
# 将找到的可用增量路径 p 转换为 Path 对象,并赋值给变量 path 。
path = Path(p)
# 如果参数 mkdir 为 True 。
if mkdir:
# 则调用 path.mkdir() 创建路径对应的目录。 parents=True 表示如果需要,会创建所有父目录; exist_ok=True 表示如果目录已存在,不会抛出异常。
path.mkdir(parents=True, exist_ok=True) # make directory
# 返回最终处理后的路径(可能是原始路径、增量路径或已创建的目录路径)。
return path
# 这段代码实现了一个路径增量命名的功能,主要用于避免文件或目录命名冲突。它通过在路径后添加编号的方式生成新的路径,并且可以根据需要创建目录。通过参数 exist_ok 和 mkdir ,用户可以灵活控制路径的处理方式。
5.def file_age(path=__file__):
# 这段代码定义了一个函数 file_age ,用于计算指定文件自上次修改以来的天数。
# 定义了一个函数 file_age ,接受一个参数。
# 1.path :默认值为 __file__ (当前脚本文件的路径)。这意味着如果不传入参数,函数将计算当前脚本文件的修改时间。
def file_age(path=__file__):
# 返回自上次修改指定文件以来的天数。
"""Return days since the last modification of the specified file."""
# datetime.datetime.fromtimestamp(timestamp[, tz])
# datetime.fromtimestamp() 是 Python 中 datetime 模块的一个方法,用于根据 Unix 时间戳(自1970年1月1日以来的秒数)来创建一个 datetime 对象。
# 参数说明 :
# timestamp : Unix 时间戳,表示自1970年1月1日(UTC)以来的秒数。
# tz (可选): 时区信息。如果提供,方法将返回指定时区对应的 datetime 对象。如果没有提供时区信息,将使用系统本地时区。
# 返回值 :
# 返回一个 datetime 对象,表示给定 Unix 时间戳对应的日期和时间。
# 注意事项 :
# datetime.fromtimestamp() 默认返回的是本地时区的时间,如果你需要协调世界时(UTC),可以提供一个时区参数。
# 如果你在处理时间戳时需要考虑时区,确保正确地使用 tz 参数。
# 在使用 datetime 模块之前,需要先导入该模块。
# datetime.fromtimestamp() 是一个非常有用的函数,它允许你将 Unix 时间戳转换为人类可读的日期和时间格式。
# 获取文件的最后修改时间。
# Path(path).stat().st_mtime :使用 Path 对象的 stat() 方法获取文件的状态信息, st_mtime 是文件的最后修改时间(以时间戳形式表示)。
# datetime.fromtimestamp() :将时间戳转换为 datetime 对象。
# datetime.now() :获取当前时间的 datetime 对象。
# datetime.now() - datetime.fromtimestamp(...) :计算当前时间与文件最后修改时间的差值,结果是一个 timedelta 对象,存储在变量 dt 中。
dt = datetime.now() - datetime.fromtimestamp(Path(path).stat().st_mtime) # delta
# 返回 timedelta 对象的 days 属性,表示自文件上次修改以来的完整天数。 注释部分 # + dt.seconds / 86400 表示如果需要计算包含小数部分的天数(即包含小时、分钟和秒的部分),可以将 dt.seconds 除以 86400(一天的秒数)并加到 dt.days 上,但这段代码中并未启用。
return dt.days # + dt.seconds / 86400 # fractional days
# 这段代码实现了一个简单的功能。计算指定文件自上次修改以来的天数。它通过获取文件的最后修改时间戳,并与当前时间进行比较,最终返回天数差。默认情况下,它计算当前脚本文件的修改时间,但也可以通过传入其他文件路径来计算其他文件的修改时间。
6.def file_date(path=__file__):
# 这段代码定义了一个函数 file_date ,用于获取指定文件的最后修改日期,并将其格式化为 'YYYY-M-D' 格式。
# 定义了一个函数 file_date ,接受一个参数。
# 1.path :默认值为 __file__ (当前脚本文件的路径)。这意味着如果不传入参数,函数将获取当前脚本文件的修改日期。
def file_date(path=__file__):
# 以“YYYY-M-D”格式返回文件修改日期。
"""Returns the file modification date in 'YYYY-M-D' format."""
# 获取文件的最后修改时间。
# Path(path).stat().st_mtime :使用 Path 对象的 stat() 方法获取文件的状态信息, st_mtime 是文件的最后修改时间(以时间戳形式表示)。
# datetime.fromtimestamp() :将时间戳转换为 datetime 对象,并将其存储在变量 t 中。
t = datetime.fromtimestamp(Path(path).stat().st_mtime)
# 使用格式化字符串将 datetime 对象 t 的年、月、日部分提取出来,并按照 'YYYY-M-D' 的格式拼接成字符串返回。例如,如果文件的最后修改时间是 2025年2月7日 ,则返回的字符串为 '2025-2-7' 。
return f"{t.year}-{t.month}-{t.day}"
# 这段代码实现了一个简单的功能。获取指定文件的最后修改日期,并将其格式化为 'YYYY-M-D' 格式。它通过获取文件的最后修改时间戳,并将其转换为 datetime 对象,最后提取年、月、日并拼接成字符串返回。默认情况下,它获取当前脚本文件的修改日期,但也可以通过传入其他文件路径来获取其他文件的修改日期。
7.def file_size(path):
# 这段代码定义了一个函数 file_size ,用于计算文件或目录的大小,并以兆字节(MB)为单位返回结果。
# 定义了一个函数 file_size ,接受一个参数。
# 1.path :该参数可以是字符串或 Path 对象,表示文件或目录的路径。
def file_size(path):
# 以兆字节 (MB) 为单位返回文件或目录的大小。
"""Returns the size of a file or directory in megabytes (MB)."""
# 检查输入的 path 是否为字符串或 Path 对象。如果不是,函数将直接返回 0.0 。
if isinstance(path, (str, Path)):
# 定义一个变量 mb ,表示 1 兆字节(MiB)的字节数。 1 << 20 是通过位移操作计算出 1024 ** 2 (即 1048576),这是从字节(bytes)到兆字节(MiB)的换算系数。
mb = 1 << 20 # bytes to MiB (1024 ** 2)
# 将输入的 path 转换为 Path 对象,以便后续操作。
path = Path(path)
# 检查 path 是否是一个文件。如果是文件,进入以下逻辑。
if path.is_file():
# 使用 path.stat().st_size 获取文件的大小(以字节为单位),然后除以 mb (1048576),将结果转换为兆字节(MiB)并返回。
return path.stat().st_size / mb
# 如果 path 不是一个文件,则检查是否是一个目录。如果是目录,进入以下逻辑。
elif path.is_dir():
# 使用 path.glob("**/*") 遍历目录及其所有子目录中的所有文件( **/* 表示递归匹配所有文件和目录)。
# 使用列表推导式 f.stat().st_size for f in path.glob("**/*") if f.is_file() 获取每个文件的大小(以字节为单位),并使用 sum() 函数计算总大小。
# 最后,将总大小除以 mb ,将结果转换为兆字节(MiB)并返回。
return sum(f.stat().st_size for f in path.glob("**/*") if f.is_file()) / mb
# 如果输入的 path 既不是字符串也不是 Path 对象,或者路径无效(既不是文件也不是目录),则返回 0.0 。
return 0.0
# 这段代码实现了一个功能。计算文件或目录的大小,并以兆字节(MB)为单位返回结果。它通过以下逻辑实现。检查输入路径是否有效(字符串或 Path 对象)。如果路径是文件,直接获取文件大小并转换为 MB。如果路径是目录,递归遍历目录中的所有文件,计算总大小并转换为 MB。如果路径无效或既不是文件也不是目录,返回 0.0 。这种实现方式既支持单个文件的大小计算,也支持整个目录的大小计算,具有较高的通用性。
8.def get_latest_run(search_dir="."):
# 这段代码定义了一个函数 get_latest_run ,用于在指定目录中查找最新的 last.pt 文件,通常用于恢复训练模型。
# 定义了一个函数 get_latest_run ,接受一个参数。
# 1.search_dir :默认值为 "." ,表示当前工作目录。该参数指定了要搜索的目录。
def get_latest_run(search_dir="."):
# 返回指定目录中最新的“last.pt”文件的路径,以恢复训练。
"""Returns the path to the most recent 'last.pt' file in the specified directory for resuming training."""
# 使用 glob.glob 函数搜索指定目录及其所有子目录( recursive=True )中所有匹配 last*.pt 的文件路径。
# search_dir 是搜索的根目录。
# /**/last*.pt 是搜索模式,表示在所有子目录中查找以 last 开头且以 .pt 结尾的文件。
# 结果存储在变量 last_list 中,它是一个 包含所有匹配文件路径的列表 。
last_list = glob.glob(f"{search_dir}/**/last*.pt", recursive=True)
# 如果 last_list 不为空(即找到了匹配的文件),使用 max 函数找到其中“最新”的文件。
# key=os.path.getctime :指定 max 函数的比较标准为文件的创建时间( getctime )。
# max(last_list, key=os.path.getctime) :返回列表中创建时间最新的文件路径。
# 如果 last_list 为空(即没有找到匹配的文件),返回空字符串 "" 。
return max(last_list, key=os.path.getctime) if last_list else ""
# 这段代码实现了一个功能。在指定目录及其子目录中查找最新的 last.pt 文件,通常用于恢复训练模型。它通过以下逻辑实现。使用 glob.glob 在指定目录及其子目录中搜索所有匹配 last*.pt 的文件。如果找到文件,通过文件的创建时间找到最新的文件。如果没有找到文件,返回空字符串。这种实现方式简单高效,适用于需要恢复训练模型的场景,例如在深度学习项目中。
9.def update_models(model_names=("yolo11n.pt",), source_dir=Path("."), update_names=False):
# 这段代码定义了一个函数 update_models ,用于更新指定模型文件,并将其保存到目标目录中。
# 定义了一个函数 update_models ,接受以下参数 :
# 1.model_names :一个元组,默认值为 ("yolo11n.pt",) ,包含需要更新的模型文件名。
# 2.source_dir :一个 Path 对象,默认值为当前目录( Path(".") ),表示模型文件所在的源目录。
# 3.update_names :一个布尔值,默认值为 False ,表示是否更新模型的类别名称。
def update_models(model_names=("yolo11n.pt",), source_dir=Path("."), update_names=False):
# 更新并重新保存“updated_models”子目录中的指定 YOLO 模型。
"""
Updates and re-saves specified YOLO models in an 'updated_models' subdirectory.
Args:
model_names (Tuple[str, ...]): Model filenames to update.
source_dir (Path): Directory containing models and target subdirectory.
update_names (bool): Update model names from a data YAML.
Examples:
Update specified YOLO models and save them in 'updated_models' subdirectory:
>>> from ultralytics.utils.files import update_models
>>> model_names = ("yolo11n.pt", "yolov8s.pt")
>>> update_models(model_names, source_dir=Path("/models"), update_names=True)
"""
# 导入了 YOLO 类和 default_class_names 函数,这些是用于加载和更新模型的工具。
from ultralytics import YOLO
from ultralytics.nn.autobackend import default_class_names
# 定义目标目录路径,位于 source_dir 下的 updated_models 文件夹。
target_dir = source_dir / "updated_models"
# 使用 mkdir 方法创建目标目录, parents=True 表示如果需要,会创建所有父目录; exist_ok=True 表示如果目录已存在,不会抛出异常。
target_dir.mkdir(parents=True, exist_ok=True) # Ensure target directory exists
# 遍历 model_names 中的每个模型文件名。
for model_name in model_names:
# 构造模型文件的完整路径,位于 source_dir 下。
model_path = source_dir / model_name
# 打印正在加载的模型路径,用于提示用户。
print(f"Loading model from {model_path}") # 从 {model_path} 加载模型。
# Load model
# 使用 YOLO 类加载模型文件。
model = YOLO(model_path)
# 将模型转换为半精度(16位浮点数)格式,通常用于加速推理并减少模型大小。
model.half()
# 如果 update_names 为 True 。
if update_names: # update model names from a dataset YAML
# 则更新模型的类别名称。这里使用 default_class_names("coco8.yaml") 从 coco8.yaml 文件中获取类别名称,并将其赋值给模型的 names 属性。
# def default_class_names(data=None):
# -> 用于从输入的 YAML 文件中提取类别名称,或者在无法提取时返回默认的类别名称。提取 names 字段,返回类别名称列表。如果无法从输入数据中提取类别名称(或未提供输入),则返回默认的类别名称字典。
# -> return yaml_load(check_yaml(data))["names"] / return {i: f"class{i}" for i in range(999)} # return default if above errors
model.model.names = default_class_names("coco8.yaml")
# Define new save path
# 定义 保存更新后模型的目标路径 ,位于 target_dir 下。
save_path = target_dir / model_name
# Save model using model.save()
# 打印正在保存的模型路径,用于提示用户。
print(f"Re-saving {model_name} model to {save_path}") # 将 {model_name} 模型重新保存至 {save_path}。
# 使用 model.save() 方法将更新后的模型保存到目标路径。
model.save(save_path)
# 这段代码实现了一个功能。更新指定的 YOLO 模型文件,并将其保存到目标目录中。它通过以下逻辑实现。创建目标目录,用于存放更新后的模型。遍历指定的模型文件名,加载每个模型。将模型转换为半精度格式。如果需要,更新模型的类别名称。将更新后的模型保存到目标目录。这种实现方式适用于需要批量更新模型文件的场景,例如在深度学习项目中对模型进行优化或更新类别名称。