downloads.py
utils\downloads.py
目录
[2.def is_url(url, check=True):](#2.def is_url(url, check=True):)
[3.def gsutil_getsize(url=''):](#3.def gsutil_getsize(url=''):)
[4.def url_getsize(url='https://ultralytics.com/images/bus.jpg'):](#4.def url_getsize(url='https://ultralytics.com/images/bus.jpg'):)
[5.def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''):](#5.def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''):)
[6.def attempt_download(file, repo='ultralytics/yolov5', release='v7.0'):](#6.def attempt_download(file, repo='ultralytics/yolov5', release='v7.0'):)
1.所需的库和模块
python
import logging
import os
import subprocess
import urllib
from pathlib import Path
import requests
import torch
2.def is_url(url, check=True):
python
# 这段代码定义了一个名为 is_url 的函数,它用来检查一个字符串是否是一个有效的URL,并且如果 check 参数为 True ,它还会检查这个URL是否在线存在。
# 这行定义了一个名为 is_url 的函数,它接受两个参数。
# 1.url :是你想要检查的URL字符串。
# 2.check :是一个布尔值,默认为 True ,表示是否检查URL是否存在于互联网上。
def is_url(url, check=True):
# Check if string is URL and check if URL exists 检查字符串是否为 URL 并检查 URL 是否存在。
# 开始了一个 try 块,用于捕获并处理可能发生的异常。
try:
# 确保 url 参数是一个字符串,如果不是,它会被转换成字符串。
url = str(url)
# result = urlparse(urlstring, scheme='', allow_fragments=True)
# urlparse() 函数是 Python 标准库 urllib.parse 模块中的一个函数,用于解析 URL(统一资源定位符)并将其分解为组件。这个函数在处理网络地址时非常有用,因为它可以将复杂的 URL 分解成易于管理的部分。
# 参数 :
# urlstring : 要解析的 URL 字符串。
# scheme : (可选)如果提供,将用于覆盖 URL 中的方案部分。
# allow_fragments : (可选)一个布尔值,指示是否允许解析 URL 的片段部分(即 # 后面的部分)。默认为 True 。
# 返回值 :
# urlparse() 函数返回一个 ParseResult 对象,该对象包含以下属性 :
# scheme : URL 的方案部分(例如 http 、 https )。
# netloc : 网络位置部分(例如域名和端口)。
# path : URL 的路径部分。
# params : URL 的参数部分( ? 后面的部分)。
# query : URL 的查询部分( ? 后面的部分,不包括 # )。
# fragment : URL 的片段部分( # 后面的部分)。
# urlparse() 函数是处理 URL 的基础工具,常用于网络编程、Web 开发和任何需要解析或构造 URL 的场景。
# 使用 urllib.parse.urlparse 函数来解析 url 字符串。这个函数会返回一个 ParseResult 对象,其中包含了URL的组成部分,如协议(scheme)、网络位置(netloc)等。
result = urllib.parse.urlparse(url)
# 使用 assert 语句来确保解析后的URL对象中既包含协议(scheme)也包含网络位置(netloc)。如果这两个条件不满足, assert 会抛出 AssertionError ,表示这不是一个有效的URL。
assert all([result.scheme, result.netloc]) # check if is url
# urllib.request.urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, *, cafile=None, capath=None, cadefault=False, context=None)
# urllib.request.urlopen() 是 Python 标准库 urllib 模块中的一个函数,它用于打开一个 URL 并返回一个类似文件对象的东西,可以用来读取从 URL 获取的数据。这个函数是 urllib.request 模块的一部分,该模块提供了用于处理 URL 的函数和类。
# 参数 :
# url : 要打开的 URL 字符串。
# data : 可选参数,用于发送 POST 请求的数据。如果是字节字符串,将作为请求体发送。如果是字典或序列的元组,将被编码为表单数据并发送。
# timeout : 可选参数,指定等待服务器响应的超时时间(以秒为单位)。默认值为 socket._GLOBAL_DEFAULT_TIMEOUT 。
# cafile : 可选参数,指定包含受信任的 CA 证书的文件路径,用于验证 SSL 证书。
# capath : 可选参数,指定包含多个 CA 证书的目录路径。
# cadefault : 布尔值,如果为 True ,则使用默认的 CA 证书。
# context : 可选参数,用于指定 SSL 上下文。
# 返回值 :
# 返回一个 urllib.response.addinfourl 对象,该对象继承自 io.IOBase ,提供了一个文件类的接口来读取服务器的响应。这个对象包含了响应的状态码、响应头等信息。
# 异常 :
# urllib.error.URLError : 如果无法打开 URL,抛出此异常。
# urllib.error.HTTPError : 如果 HTTP 请求返回了错误的状态码,抛出此异常。
# urllib.response.addinfourl 对象中常用的属性和方法 :
# 属性 :
# url :返回请求的 URL,这在你处理重定向时非常有用,因为它可以告诉你实际请求的是哪个 URL。
# status :返回 HTTP 响应的状态码,例如 200、404、500 等。
# headers :返回一个类似字典的对象,包含了响应的头部信息。
# version :返回 HTTP 协议的版本,例如 10(HTTP/1.0)或 11(HTTP/1.1)。
# reason :返回一个字符串,表示与状态码对应的原因短语,例如 "OK"、"Not Found" 等。
# 方法1 :
# geturl() :返回资源检索的 URL,如果发生了重定向,将返回最终的 URL。
# info() :返回页面的元信息,如头部信息,作为一个 email.message_from_string() 实例返回。
# getcode() :返回响应的 HTTP 状态码。
# read() :读取响应的内容。如果响应内容是压缩的,可以使用 io.BufferedReader 或 gzip.GzipFile 等来解压缩。
# readline() :读取响应的一行内容。
# readlines() :读取所有响应内容,并按行分割返回列表。
# fileno() :返回文件描述符。
# close() :关闭响应对象,释放系统资源。
# 函数的返回部分。如果 check 参数为 True ,它会尝试使用 urllib.request.urlopen 打开URL,并检查返回的HTTP状态码是否为200(表示请求成功)。如果 check 为 False ,则不进行在线检查,直接返回 True 。
return (urllib.request.urlopen(url).getcode() == 200) if check else True # check if exists online
# 定义了 except 块,用于捕获 try 块中可能抛出的 AssertionError 和 urllib.request.HTTPError 异常。
except (AssertionError, urllib.request.HTTPError):
# 如果在 try 块中抛出了异常, except 块会捕获这些异常,并返回 False ,表示URL无效或不存在于互联网上。
return False
# 这个函数用于检查给定的字符串是否是有效的URL,并且如果需要,还可以检查该URL是否能够在线访问。
3.def gsutil_getsize(url=''):
python
# 这段 Python 代码定义了一个名为 gsutil_getsize 的函数,其目的是获取通过 Google Cloud Storage 的 gsutil 工具指定 URL 的文件大小(以字节为单位)。
# 定义了一个名为 gsutil_getsize 的函数,它接受一个参数。
# 1.url :默认值为空字符串。这个参数应该是指向 Google Cloud Storage 上的文件或目录的 URL。
def gsutil_getsize(url=''):
# gs://bucket/file size https://cloud.google.com/storage/docs/gsutil/commands/du
# subprocess.check_output(*popenargs, input=None, timeout=None, **kwargs)
# subprocess.check_output 是 Python 标准库 subprocess 模块中的一个函数,用于执行一个命令并获取它的输出。如果命令执行失败(即返回一个非零退出状态),该函数会抛出一个 CalledProcessError 异常。
# 参数 :
# *popenargs :这是一组参数,它们将被传递给 subprocess.Popen 构造函数。通常,这些参数包括命令和它的参数,类似于在 shell 中执行命令的方式。
# input :(可选)一个字符串或字节序列,将被发送到 subprocess 的 stdin。
# timeout :(可选)一个浮点数或整数,指定等待 subprocess 完成的秒数。如果超时,将抛出 TimeoutExpired 异常。
# **kwargs :(可选)其他参数将被传递给 subprocess.Popen 构造函数。
# 返回值 :
# 函数返回 subprocess 的 stdout 输出,作为一个字节串(bytes)。
# 异常 :
# CalledProcessError :如果 subprocess 返回一个非零退出状态。
# TimeoutExpired :如果 subprocess 超过指定的 timeout 时间。
# 注意事项 :
# 默认情况下, check_output 函数返回的输出是字节串。如果你需要字符串,可以指定 text=True 参数(在 Python 3.7+ 中)或者使用 universal_newlines=True 参数(在 Python 3.6 及以下版本中)。
# 使用 shell=True 参数时需要特别小心,因为它可能会使程序容易受到 shell 注入攻击。只有当你能够完全信任输入数据时,才应该使用 shell=True 。
# timeout 参数在 Python 3.3 及以上版本中可用。
# 使用 Python 的 subprocess 模块来执行外部命令。 subprocess.check_output 函数运行指定的命令并捕获其输出。
# 这里,命令是 gsutil du {url} ,其中 {url} 是函数参数 url 的值,用于指定要检查大小的 Google Cloud Storage 上的对象。 shell=True 参数允许直接在 shell 中运行命令,这可能带来安全风险,因为它可能会受到 shell 注入攻击的影响。输出被解码为 UTF-8 格式的字符串。
s = subprocess.check_output(f'gsutil du {url}', shell=True).decode('utf-8')
# eval(expression, globals=None, locals=None)
# eval() 函数是 Python 的内置函数,用于计算表达式字符串,并返回表达式的值。
# 参数 :
# expression :一个字符串形式的 Python 表达式。这个表达式可以包含 Python 中有效的任意表达式,例如数字、变量、运算符、函数调用等。
# globals :一个字典,包含表达式将会使用的全局变量。如果为 None (默认值),则使用当前环境的全局变量。如果提供了 globals 参数,它将覆盖当前环境的全局变量。
# locals :一个字典,包含表达式将会使用的局部变量。如果为 None (默认值),则使用当前环境的局部变量。如果提供了 locals 参数,它将覆盖当前环境的局部变量。
# 返回值 :
# 返回 expression 表达式计算后的结果。
# 安全性 :
# 使用 eval() 时需要特别小心,因为它可以执行任意代码。如果 expression 来自不可信的源,那么它可能会执行恶意代码,导致安全问题。
# 在实际编程中,通常推荐避免使用 eval() ,除非完全信任输入源,或者确实需要动态执行代码。在很多情况下,可以使用更安全的方法来替代 eval() ,例如使用 ast.literal_eval() 来计算字面量表达式。
# 首先检查 s (即命令输出的字符串)的长度,如果 s 不为空,则使用 split(' ') 方法将字符串按空格分割成列表,并取列表的第一个元素(即文件大小的值),然后使用 eval 函数将其转换为 Python 表达式。 eval 函数能够将字符串形式的 Python 表达式转换为相应的 Python 对象。
# 如果 s 为空,则函数返回 0 。这里的注释 # bytes 表示返回的大小是以字节为单位的。
return eval(s.split(' ')[0]) if len(s) else 0 # bytes
# 这个函数通过调用 gsutil du 命令来获取 Google Cloud Storage 上文件的大小。它使用 subprocess.check_output 来执行命令并捕获输出,然后解析输出以获取文件大小。
# 然而,这个函数存在两个潜在问题 :
# 安全性问题 :使用 shell=True 可能会使函数容易受到 shell 注入攻击。如果 url 参数来自不可信的源,攻击者可能会注入恶意命令。
# 使用 eval :虽然在这个特定场景中可能安全,但 eval 函数通常被认为是不安全的,因为它可以执行任意代码。如果输出格式不可预测或不受信任,使用 eval 可能会导致安全问题。
# 为了提高安全性,可以考虑以下改进 :
# 避免使用 shell=True ,而是将命令参数分开传递给 subprocess.check_output 。
# 使用更安全的方法来解析输出,例如直接转换为整数,而不是使用 eval 。
4.def url_getsize(url='https://ultralytics.com/images/bus.jpg'):
python
# 这段代码定义了一个名为 url_getsize 的函数,它用于获取指定 URL 的可下载文件大小(以字节为单位)。
# 定义了一个名为 url_getsize 的函数,它接受一个参数。
# 1.url :默认值为 `'https://ultralytics.com/images/bus.jpg'`。这个参数应该是指向可下载文件的 URL。
def url_getsize(url='https://ultralytics.com/images/bus.jpg'):
# Return downloadable file size in bytes 返回可下载文件的大小(以字节为单位)。
# 使用 requests 库的 head 方法发送一个 HEAD 请求到指定的 URL。HEAD 请求用于获取资源的元数据,但不包括资源本身。 allow_redirects=True 参数允许自动处理重定向。
response = requests.head(url, allow_redirects=True)
# 从响应头中获取 content-length 字段的值,它表示文件的大小(以字节为单位)。如果 content-length 不存在,则返回 -1 。最后,使用 int() 函数将字符串转换为整数。
return int(response.headers.get('content-length', -1))
# 这个函数通过发送 HEAD 请求来获取文件的大小。它的优点是不需要下载整个文件,只需获取元数据即可。然而,需要注意的是,并非所有服务器都会在响应头中包含 content-length 字段,特别是在使用压缩或分块传输编码时。
5.def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''):
python
# 这段代码定义了一个名为 safe_download 的函数,它用于安全地从提供的 URL 下载文件,并确保下载的文件大小符合最小字节要求。
# 函数定义。
# 1.file : 要下载的文件的本地路径。
# 2.url : 用于下载文件的主要 URL。
# 3.url2 : 备用 URL,如果主要 URL 下载失败时使用。
# 4.min_bytes : 定义文件的最小字节大小,用于检查下载是否成功。
# 5.error_msg : 如果下载失败,显示的额外错误信息。
def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''):
# Attempts to download file from url or url2, checks and removes incomplete downloads < min_bytes 尝试从 url 或 url2 下载文件,检查并删除不完整的下载 < min_bytes。
# LOGGER -> LOGGER 是一个日志记录器对象,用于记录日志信息。
from utils.general import LOGGER
# 将文件路径转换为 Path 对象,以便使用路径操作。
file = Path(file)
# 创建一个断言消息,用于检查下载的文件是否存在或大小是否小于 min_bytes 。
assert_msg = f"Downloaded file '{file}' does not exist or size is < min_bytes={min_bytes}" # 下载的文件"{file}"不存在或大小 < min_bytes={min_bytes}。
# 尝试从 url 下载文件。
try: # url1
# 使用 PyTorch 的 torch.hub 模块下载文件,并根据 LOGGER 的日志级别显示进度。
LOGGER.info(f'Downloading {url} to {file}...') # 正在下载 {url} 至 {file}...
# torch.hub.download_url_to_file(url, dst, hash_prefix=None, progress=True)
# torch.hub.download_url_to_file 是 PyTorch 的一个函数,用于从指定的 URL 下载文件并保存到本地路径。
# 参数 :
# url (str): 要下载文件的 URL。
# dst (str): 文件保存的完整路径,例如 /tmp/temporary_file 。
# hash_prefix (str, 可选): 如果提供,下载的 SHA256 文件应以 hash_prefix 开头。默认为 None 。
# progress (bool, 可选): 是否在标准错误输出(stderr)显示进度条。默认为 True 。
# 返回值 :该函数没有返回值,它会直接将下载的文件保存到指定的本地路径。
# 注意事项 :
# 该函数会检查下载文件的完整性,如果提供了 hash_prefix 参数,它会验证文件的 SHA256 哈希值是否与 hash_prefix 匹配。
# 如果下载过程中出现任何问题,例如网络错误或文件损坏,函数会抛出异常。
# progress 参数控制是否显示下载进度,这对于大型文件下载特别有用,可以让用户了解下载进度。
# torch.hub.download_url_to_file 函数是 PyTorch 提供的一个方便的工具,用于从远程服务器下载预训练模型或其他文件,它是 PyTorch 模型加载和迁移学习流程中的一个重要组成部分。
torch.hub.download_url_to_file(url, str(file), progress=LOGGER.level <= logging.INFO)
# 断言检查文件是否存在且大小是否大于 min_bytes 。
assert file.exists() and file.stat().st_size > min_bytes, assert_msg # check
# 如果下载过程中出现异常,执行 except 块中的代码。
except Exception as e: # url2
# 如果文件已存在,则删除不完整的下载文件。
if file.exists():
# Path.unlink(missing_ok=False)
# unlink() 函数是 pathlib 模块中 Path 类的一个方法,用于删除文件系统中的一个文件。
# Path : 这是 pathlib 模块中的 Path 类,用于表示文件系统路径。
# unlink() : 这是 Path 类的方法,用于删除路径所指向的文件。
# 参数 :
# missing_ok : 这是一个可选参数,默认值为 False 。如果设置为 True ,则在文件不存在时不会抛出异常,而是静默地忽略这个错误。
# 功能 :
# unlink() 方法用于删除文件系统中的一个文件。如果文件不存在,并且 missing_ok 参数为 False (默认值),则会引发一个 FileNotFoundError 。
# 注意事项 :
# 使用 unlink() 方法时要小心,因为一旦文件被删除,就无法恢复。
# 确保在删除文件之前有适当的错误处理和文件存在性检查,除非你确定文件存在,或者你不在乎文件是否实际存在。
# 在多线程或多进程环境中,文件可能会在不同的执行线程或进程中被访问或修改,因此在使用 unlink() 时要特别注意同步和竞态条件。
file.unlink() # remove partial downloads
LOGGER.info(f'ERROR: {e}\nRe-attempting {url2 or url} to {file}...') # 错误:{e}\n重新尝试将 {url2 或 url} 连接到 {file}...
# os.system(command)
# os.system() 函数是 Python 的 os 模块中的一个函数,用于执行指定的命令行字符串。这个函数会调用系统的命令行解释器(通常是 shell)来执行命令,并返回命令执行后的退出状态码。
# 参数说明 :
# command :一个字符串,包含要执行的命令。
# 返回值 :返回命令执行后的退出状态码。在 Unix 和类 Unix 系统中,通常 0 表示成功,非 0 表示失败。在 Windows 系统中,返回值的具体含义取决于命令本身。
# 异常 :
# 如果发生错误(例如,无法找到命令解释器),可能会抛出 OSError 异常。
# 需要注意的是, os.system() 函数会创建一个新的 shell 来执行命令,这意味着它可能会受到当前工作目录的影响,并且不会继承当前 Python 进程的环境变量。
# 此外,由于安全原因,通常不推荐在程序中使用 os.system() ,因为它可能会执行任意命令,存在安全风险。
# 在可能的情况下,可以考虑使用 subprocess 模块中的函数,如 subprocess.run() 或 subprocess.call() ,因为它们提供了更多的控制和安全性。
# 使用 curl 命令尝试从 url2 或 url 重新下载文件,包含重试和断点续传选项。
os.system(f"curl -# -L '{url2 or url}' -o '{file}' --retry 3 -C -") # curl download, retry and resume on fail
# 最后,再次检查文件是否存在且大小是否符合要求。
finally:
# os.stat(path, *, dir_fd=None, follow_symlinks=True, dir_fd=None)
# 在Python中, stat() 函数是 os 模块中的一个方法,用于获取文件或目录的状态信息。
# 参数说明 :
# path : 要获取状态信息的文件或目录的路径。
# dir_fd : 可选参数,文件描述符;如果提供,表示 path 是相对于此文件描述符的。
# follow_symlinks : 可选参数,布尔值;如果为 True (默认值),则 stat() 会跟随软链接,并返回链接目标的状态信息;如果为 False ,则返回链接本身的状态信息。
# 返回值 :
# 返回一个对象,该对象包含了文件或目录的许多属性,如修改时间、访问时间、文件大小等。
# 返回对象的属性 :
# 返回的对象包含以下属性(这些属性在不同的操作系统中可能有所不同) :
# st_mode : 文件模式(类型与权限)。
# st_ino : inode(节点)编号。
# st_dev : 设备编号。
# st_nlink : 硬链接数。
# st_uid : 文件所有者的ID。
# st_gid : 文件所有者组的ID。
# st_size : 文件大小,单位为字节。
# st_atime : 最后访问时间,以Unix时间戳表示。
# st_mtime : 最后修改时间,以Unix时间戳表示。
# st_ctime : 最后状态改变时间(inode修改时间),以Unix时间戳表示。
# 注意事项 :
# 使用 stat() 函数时,需要确保提供的路径是存在的,否则会抛出 FileNotFoundError 。
# stat() 函数返回的属性在不同的操作系统中可能有所不同,因此在编写跨平台代码时需要注意这一点。
# 在使用 os 模块之前,需要先导入该模块。
# stat() 函数是处理文件系统操作时常用的一个函数,它提供了获取文件或目录状态信息的直接方法。
# 如果文件不存在或大小小于 min_bytes ,则删除文件并记录错误信息。
if not file.exists() or file.stat().st_size < min_bytes: # check
if file.exists():
file.unlink() # remove partial downloads
LOGGER.info(f"ERROR: {assert_msg}\n{error_msg}") # 错误:{assert_msg}\n{error_msg}。
# 在日志中添加一个空行,用于分隔日志信息。
LOGGER.info('')
# safe_download 函数提供了一个健壮的文件下载机制,它尝试从提供的 URL 下载文件,并确保文件完整且未损坏。如果下载失败,函数会尝试从备用 URL 下载,并在最终检查中删除不完整的文件。这个函数对于确保模型文件和其他重要资源的完整性非常有用。
6.def attempt_download(file, repo='ultralytics/yolov5', release='v7.0'):
python
# 这段代码定义了一个名为 attempt_download 的函数,其目的是尝试从 GitHub 仓库的发布资产中下载文件,如果本地找不到该文件。
# 函数定义。
# 1.file : 要下载的文件的路径或 URL。
# 2.repo : GitHub 仓库的名称,默认为 ultralytics/yolov5 。
# 3.release : GitHub 仓库的发布版本,默认为 v7.0 。
def attempt_download(file, repo='ultralytics/yolov5', release='v7.0'):
# Attempt file download from GitHub release assets if not found locally. release = 'latest', 'v7.0', etc. 如果本地找不到,则尝试从 GitHub 发布资产下载文件。release = 'latest'、'v7.0' 等。
# 从 utils.general 模块导入 LOGGER 对象,用于记录日志信息。
from utils.general import LOGGER
# 定义一个内部函数 github_assets ,它用于从 GitHub 仓库的指定版本中获取发布标签和资产列表。
# 函数定义。
# repository : GitHub 仓库的名称,例如 'ultralytics/yolov5' 。
# version : 指定的版本标签,默认为 'latest' 。
def github_assets(repository, version='latest'):
# Return GitHub repo tag (i.e. 'v7.0') and assets (i.e. ['yolov5s.pt', 'yolov5m.pt', ...]) 返回 GitHub repo 标签(即 'v7.0')和资产(即 ['yolov5s.pt', 'yolov5m.pt', ...])。
# 检查 version 参数是否不是 'latest' 。
if version != 'latest':
# 如果 version 不是 'latest' ,则将其格式化为 'tags/{version}' ,以便用于 GitHub API 请求。
version = f'tags/{version}' # i.e. tags/v7.0
# 使用 requests 库向 GitHub API 发起 GET 请求,获取指定仓库和版本的发布信息,并解析响应内容为 JSON。
response = requests.get(f'https://api.github.com/repos/{repository}/releases/{version}').json() # github api
# 返回 仓库的标签名称 和 资产列表 。
return response['tag_name'], [x['name'] for x in response['assets']] # tag, assets
# 这个函数依赖于 requests 库来发送 HTTP 请求。 函数假设 GitHub API 返回的 JSON 响应包含 'tag_name' 和 'assets' 键。 函数返回的资产列表是一个包含资产名称的列表。
# 这段代码是 attempt_download 函数的一部分,它处理从 URL 下载文件的逻辑。
# 将输入的 file 变量转换为字符串,去除前后空格,并替换掉所有单引号。然后,使用 Path 对象确保路径操作的兼容性和便捷性。
file = Path(str(file).strip().replace("'", ''))
# 检查处理后的文件路径是否指向一个已存在的文件。
if not file.exists():
# URL specified
# parse.unquote(s, encoding='utf-8', errors='replace')
# parse.unquote() 是 Python 标准库 urllib.parse 模块中的一个函数,用于对 URL 编码的字符串进行解码,将百分号编码(%XX)转换回普通字符。
# 参数 :
# s :要解码的 URL 编码字符串。
# encoding :(可选)用于解码的字符编码,默认为 'utf-8' 。
# errors :(可选)指定如何处理解码错误,默认为 'replace' ,意味着将无法解码的字符替换为一个替代字符(通常是 ? )。
# 返回值 :
# 返回解码后的字符串。
# 函数逻辑 :
# 解码百分号编码 :将字符串中的 %XX 序列转换为对应的字符。
# 字符编码转换 :将原始的百分比编码字符串(通常为 ASCII)转换为指定的编码。
# 在例子中, unquote 函数将 URL 编码的字符串 "Hello%2C%20World%21" 解码为普通字符串 "Hello, World!" 。
# 注意事项 :
# 当处理来自用户的 URL 编码数据时,使用 unquote 函数可以确保正确地解释这些数据。
# 如果 URL 包含非 ASCII 字符,确保指定正确的 encoding 参数,否则解码可能会失败或产生意外结果。
# 如果遇到无法解码的百分号序列, errors 参数决定了如何处理这些错误。常见的选项包括 'strict' (抛出 UnicodeDecodeError )、 'replace' (用替代字符替换无法解码的字符)和 'ignore' (忽略无法解码的字符)。
# 如果文件不存在,解析文件的 URL,解码 URL 中的百分号编码(例如将 %2F 解码为 / ),并获取文件名。
name = Path(urllib.parse.unquote(str(file))).name # decode '%2F' to '/' etc.
# 检查文件路径是否以 http:/ 或 https:/ 开头,确定是否需要下载文件。
if str(file).startswith(('http:/', 'https:/')): # download
# 修复路径中的协议部分,将 :/ 替换为 :// ,以形成有效的 URL。
url = str(file).replace(':/', '://') # Pathlib turns :// -> :/
# 从文件名中移除 URL 的查询参数,只保留文件名本身。
file = name.split('?')[0] # parse authentication https://url.com/file.txt?auth...
# 再次检查本地是否存在该文件。
if Path(file).is_file():
# 如果文件已存在,使用 LOGGER 记录文件已在本地找到的信息。
LOGGER.info(f'Found {url} locally at {file}') # file already exists 在本地的 {file} 上找到了 {url}。 文件已存在。
# 如果文件不存在,执行下载操作。
else:
# 调用 safe_download 函数,下载文件并保存到指定路径。 min_bytes 参数确保下载的文件大小至少为 100,000 字节。
# def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''): -> 用于安全地从提供的 URL 下载文件,并确保下载的文件大小符合最小字节要求。
safe_download(file=file, url=url, min_bytes=1E5)
# 返回处理后的文件路径。
return file
# 这段代码负责处理文件下载的逻辑,包括检查文件是否存在、解析 URL、下载文件,并记录日志信息。 safe_download 函数用于执行实际的下载操作,确保文件被安全地下载到本地。这段代码是 attempt_download 函数的核心部分,用于从给定的 URL 下载文件,如果文件已经在本地存在则直接使用本地文件。
# 这段代码是尝试从GitHub仓库获取模型资产列表的逻辑,如果直接获取失败,则会尝试其他方法来确定标签(tag)。
# GitHub assets
# 创建一个列表 assets ,包含所有可能的YOLOv5模型文件名。这些文件名基于模型大小( n , s , m , l , x )和后缀(空字符串、 6 , -cls , -seg )的组合。
assets = [f'yolov5{size}{suffix}.pt' for size in 'nsmlx' for suffix in ('', '6', '-cls', '-seg')] # default
# 开始第一个 try 块,尝试执行以下代码。
try:
# 调用 github_assets 函数,传入仓库名 repo 和版本 release ,尝试获取对应版本的标签和资产列表。
# def github_assets(repository, version='latest'):
# -> 用于从 GitHub 仓库的指定版本中获取发布标签和资产列表。返回 仓库的标签名称 和 资产列表 。
# -> return response['tag_name'], [x['name'] for x in response['assets']] # tag, assets
tag, assets = github_assets(repo, release)
# 如果在尝试获取特定版本的资产列表时发生异常,进入第一个 except 块。
except Exception:
# 开始第二个 try 块,尝试执行以下代码。
try:
# 再次调用 github_assets 函数,这次没有传入版本号,尝试获取最新版本的 标签 和 资产列表 。
# def github_assets(repository, version='latest'):
# -> 用于从 GitHub 仓库的指定版本中获取发布标签和资产列表。返回 仓库的标签名称 和 资产列表 。
# -> return response['tag_name'], [x['name'] for x in response['assets']] # tag, assets
tag, assets = github_assets(repo) # latest release
# 如果在尝试获取最新版本的资产列表时发生异常,进入第二个 except 块。
except Exception:
# 开始第三个 try 块,尝试执行以下代码。
try:
# subprocess.check_output(cmd, *args, **kwargs)
# check_output() 函数是 Python 标准库 subprocess 模块中的一个函数,它用于执行指定的命令并获取命令的输出。如果命令执行成功, check_output() 会返回命令的输出;如果命令执行失败(即返回非零退出状态),则会抛出一个 CalledProcessError 异常。
# 参数说明 :
# cmd :要执行的命令,可以是字符串或者字符串列表。如果是字符串,会被 shell 解释,这与 shell=True 相同;如果是字符串列表,则直接传递给底层的 execvp() 函数。
# *args :传递给 subprocess.Popen() 的其他参数。
# **kwargs :传递给 subprocess.Popen() 的其他关键字参数。常用的关键字参数包括 :
# shell :如果为 True ,则 cmd 会被 shell 解释。默认为 False 。
# stdout :子进程的 stdout 管道。默认为 subprocess.PIPE ,即捕获输出。
# stderr :子进程的 stderr 管道。默认为 subprocess.PIPE ,即捕获错误输出。
# universal_newlines 或 text :如果设置为 True ,则 check_output() 返回一个字符串而不是字节对象。在 Python 3.7 及更高版本中, text 参数被引入, universal_newlines 被废弃。
# 返回值 :
# 返回执行命令后的标准输出(stdout)。
# 异常 :
# 如果命令返回非零退出状态, check_output() 会抛出 subprocess.CalledProcessError 异常。
# 使用 subprocess 模块执行 git tag 命令,获取当前git仓库的所有标签,并解码输出,取最后一个标签作为 tag 。
tag = subprocess.check_output('git tag', shell=True, stderr=subprocess.STDOUT).decode().split()[-1]
# 如果在尝试通过 git tag 获取标签时发生异常,进入第三个 except 块。
except Exception:
# 如果所有尝试都失败,将 tag 设置为初始传入的 release 参数。
tag = release
# 这段代码展示了一种健壮的错误处理策略,用于在不同情况下获取GitHub仓库的标签和资产列表。首先尝试获取特定版本的资产列表,如果失败,则尝试获取最新版本的资产列表。如果这两个尝试都失败,最后通过执行 git tag 命令来获取标签。如果所有尝试都失败,则回退到使用初始的 release 参数。这种方法确保了在不同网络条件和环境配置下,代码能够尽可能地获取所需的信息。
# 这段代码是 attempt_download 函数的一部分,它负责处理从 GitHub 仓库下载模型文件的逻辑。
# 确保文件 file 的父目录存在。如果父目录不存在,则创建它。 parents=True 表示创建所有必需的父目录。 exist_ok=True 表示如果目录已经存在,则不抛出异常。
file.parent.mkdir(parents=True, exist_ok=True) # make parent dir (if required)
# 检查文件名 name 是否在从 GitHub 仓库获取的资产列表 assets 中。
if name in assets:
# 定义一个备用的 Google Drive 链接,用于在 GitHub 链接不可用时作为备份。
url3 = 'https://drive.google.com/drive/folders/1EFQTEUeXWSFww0luse2jB9M1QNZQGwNl' # backup gdrive mirror
# 调用 safe_download 函数,从 GitHub 仓库的指定版本 tag 下载文件 name 。
# def safe_download(file, url, url2=None, min_bytes=1E0, error_msg=''): -> 用于安全地从提供的 URL 下载文件,并确保下载的文件大小符合最小字节要求。
safe_download(
file,
# url 参数构建了下载链接,指向 GitHub 仓库的特定发布版本的特定文件。
url=f'https://github.com/{repo}/releases/download/{tag}/{name}',
# 确保下载的文件至少为 100,000 字节,以避免下载不完整的文件。
min_bytes=1E5,
# error_msg 提供了一个错误消息,如果下载失败,建议用户尝试从 GitHub 仓库或备用 Google Drive 链接手动下载。
error_msg=f'{file} missing, try downloading from https://github.com/{repo}/releases/{tag} or {url3}') # 缺少 {file},请尝试从 https://github.com/{repo}/releases/{tag} 或 {url3} 下载。
# 返回文件的路径,以字符串形式。
return str(file)
# 这段代码处理了从 GitHub 仓库下载文件的逻辑,包括确保父目录存在、检查文件是否在资产列表中、构建下载链接、调用下载函数,并在下载失败时提供备用下载选项。这种方法确保了即使在网络问题或 GitHub 链接不可用的情况下,用户也能找到并下载所需的文件。
# attempt_download 函数提供了一个方便的方式来下载 GitHub 仓库中的文件,如果本地不存在。它处理了文件名的解析、URL 的构建、直接下载和 GitHub 资产的下载。这个函数是自动化下载和更新模型文件的实用工具,特别是在需要确保模型文件是最新版本时。