分类目录归档:计算机

停止使用传统的 Commits 信息

下面是 Sumner Evans 的博文(原文地址是:https://sumnerevans.com/posts/software-engineering/stop-using-conventional-commits/) ,让 Gemini 翻译了:

你几乎肯定以前遇到过 常规提交格式 。它可能在你使用过的某个开源项目的变更日志中出现过,也可能是你参与贡献的某个开源项目强制要求的提交格式。很多人对它赞不绝口,而我却 对它 深恶痛绝。

尽管许多流行的开源项目都在使用 传统 提交 它却 一个 糟糕的标准,它 鼓励 人们关注错误的事情 ,并且 未能兑现其承诺

焦点失败

Conventional Commits 承诺为提交信息添加语义,以帮助开发者和最终用户理解提交中所做的更改。然而,Conventional Commits 在这方面做得并不尽如人意。为了说明这一点,让我们来看一下传统提交的结构。根据 Conventional Commits 网站的 说明 ,提交信息的格式应如下所示:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

提交的主题行包含一个 <type> (例如\<commit> fix \feat \ chore <commit>\docs \<commit>\refactor [^1] )来描述更改类型。之后,可以指定更改范围,最后是更改描述。

这种格式存在一个重大缺陷: 类型优先于作用域 。这完全本末倒置。

范围 > 类型

变更范围(变更对象)是提交操作中最重要的部分。为了说明这一点,我们来探讨一下为什么以下每个利益相关者 更关心变更范围而不是变更类型:

  • 贡献者: 作为项目的贡献者,您经常需要阅读提交日志,以识别代码库中与特定代码区域相关的更改。原因有很多,包括:
    • 想了解一下自您上次贡献以来发生的事情。
      • 试图了解项目的整体惯性在哪里。
      • 查找在拉取或变基时可能与您正在进行的工作发生冲突的提交。
        阅读提交日志时,你关注的是 哪些区域 被修改过。你并不关心具体修改了什么 类型 ,你关心的是 修改的 范围。
  • 调试人员: 在调查 bug 时,您通常需要查看提交日志,了解哪些更改可能影响了与 bug 出现的组件相关的区域。再次强调,范围是最重要的信息。更改类型完全没有用,因为任何类型的更改都可能引入 bug。(我相信我们都经历过编写 bug 修复程序却导致另一个 bug 的情况。)
  • 事件响应人员: 当生产环境宕机时,扫描提交日志,查找宕机前后发生的变更,是识别问题根源的有效方法。此时,范围(scope)仍然是最重要的信息。例如,如果在 auth API 错误涌入高峰期发现与该范围相关的提交,那么它很可能就是问题的罪魁祸首。再次强调,类型(type)无关紧要,因为任何变更都可能引入错误。

那么,常规提交到底做了什么?它把作用域的优先级降到了最低,甚至变成了 可选项作用域 怎么会变成可选项呢?提交没有作用域就像句子没有主语一样!更糟糕的是,常规提交还把 类型 放在了提交信息的最前面。常规提交完全搞错了作用域和类型的优先级。

类型是冗余且具有限制性的

你可能会想:“所以这听起来有点本末倒置,但提交类型至少还是很重要的,对吧?”对此我的回答是“不”。提交描述几乎总是应该告诉你更改的类型! 例如,请看 以下提交信息:

fix(compiler): prevent namespaced SVG <style> elements from being stripped

即使只有描述,也能明显看出这是个 bug 修复!提交主题的空间本来就很宝贵,再浪费字符写类型说明毫无意义!而且,这样做往往比没用更糟糕,还会造成限制。 例如,看看 这条提交信息:

refactor(core): Update webmcp support to use document.modelContext

这次提交更新了 webmcp 组件的功能, core 使其同时支持 \and\ document.modelContext 和 \or\ navigator.modelContext ,所以这算是 bug 修复、重构还是新功能呢?我认为三者兼具!但话说回来,真正重要的是组件本身发生了变化 core/webmcp

传统的提交方式从根本上关注了错误的事情(提交类型),贬低了范围(这才是人们真正关心的事情)。

违背承诺

所以我们已经确定,常规提交格式很糟糕,但它肯定也有 一些 好处。让我们阅读 “为什么要使用常规提交” 部分,看看其中的理由是否合理。

  • 自动生成变更日志。
    这是 Conventional Commits 的最大优势:你可以运行像git-cliffconventional-changelog 这样的工具 ,根据你上次发布以来的提交记录生成变更日志。但这真的是个好主意吗?当然不是!变更日志的受众与提交日志的受众完全不同!
    变更日志是面向用户的,用户关心的是了解不同版本之间的功能差异。他们关心的是从 业务/功能 角度来看发生了哪些变化。
    提交日志是面向开发人员的,开发人员关心的是代码库随时间推移的变化历程。他们关心的是从 作用域 角度来看发生了哪些变化。
    如您所见,这是两种完全不同的谷物,任何试图将它们混合的尝试都会导致品质不佳的结果。原因有很多:

    • 在任何中等复杂程度的项目中,实现任何重要的功能都需要多次提交。功能实现的过程(由提交日志记录)对开发者和贡献者来说很有价值,但对最终用户来说毫无意义。最终用户只关心新功能本身,而不是它的实现方式!
      • 正如 Rich 指出的那样 ,回滚操作对于常规提交来说存在问题。从提交日志的角度来看,回滚提交对开发者来说很重要,但对最终用户而言,被回滚的更改就等同于没有进行过更改。
  • 自动确定语义版本号提升(基于已合并的提交类型)。
    这听起来不错,但软件工程的实际情况往往会严重影响这项任务的准确性。请考虑以下情况:

    • 回滚: 想象一下,你引入的破坏性变更实际上影响巨大,以至于你不得不回滚它?你的工具会检测到这一破坏性变更,并递增主版本号,即使实际上该变更已被回滚,不再存在任何破坏性变更。
      • 意外破坏: 也许破坏很细微,你在进行更改时并没有意识到这是一个破坏性更改。只有在事后才发现它造成了破坏。例如,你可能会在需要提升主版本号时错误地增加了次版本号/补丁版本号。
      • 追溯性修复: 假设你之后添加了一个提交,该提交与之前导致破坏的提交组合后,产生的差异不再是破坏性的。与回滚情况类似,工具会错误地将其识别为破坏性变更。
        在这种情况下,虽然可以使用变基操作来改写历史记录,但这通常会破坏工作流程或被工作流程阻止。此外,它还会向试图为项目做贡献的贡献者呈现一个经过修正的历史记录,从而降低提交日志所反映的信息的可靠性。
  • 向团队成员、公众和其他利益相关者传达变更的性质。
    正如我们之前所讨论的,团队成员和公众对变更日志和提交日志的需求截然不同。传统的提交方式无法满足这两种需求。
  • 触发构建和发布流程。
    这完全是个馊主意。假设你只对修改代码的提交运行自动化安全检查,然后有人创建了一个名为“tmp”的木马提交, docs: fix typos 该提交实际上在身份验证子系统中引入了漏洞?显然,这种恶意活动在代码审查中应该会被发现,但自动化工具却被绕过了,最终只能由人工来识别问题。
    计算成本很低,只需 git diff 用于识别已更改的文件(再次强调作用域),并基于此运行构建/发布流程即可。
  • 通过允许人们探索更结构化的提交历史记录,使他们更容易为您的项目做出贡献。
    结构更清晰了,没错。让贡献变得更容易了吗?完全没有(我们已经详细论证过了)。

传统承诺的“卖点”没有一个站得住脚。

传统提交方式也很难应用于项目。虽然它要求开发者定义自己的“类型”,但几乎所有人都直接采用 commitlint 的 默认设置,而这些默认设置往往与具体项目的具体情况不符。这个问题在企业环境中尤为突出,因为变更管理和审计要求通常规定每个提交消息都必须包含工单编号。虽然该 <scope> 字段显然是放置工单编号的最佳位置,但这最终却用一个完全无用的工单编号替换了传统提交方式中唯一有用的元数据。

更好的方法

那么,你应该怎么做呢?不妨借鉴 Linux、FreeBSD、Git、Go 和 NixOS 等真正成功的软件项目!这些项目有什么共同点?它们都使用带有 scope 前缀的提交信息(其中“scope”的定义与实际项目相关)。通常,特定项目应该使用哪个 scope 是显而易见的。对于 Linux 内核,子系统是自然的 scope。对于 Go 项目,包路径是自然的 scope。对于使用微服务架构的项目,微服务名称是自然的 scope。

以下是一些项目及其提交格式指南的示例。

项目 格式 例子
Linux subsystem: description i2c: virtio: mark device ready before registering the adapter
FreeBSD prefix: Description linuxulator: Return EINVAL for invalid inotify flags
Git area: description gitlab-ci: update macOS image
package: description net/http/cookiejar: add godoc links
nixpkgs pkg-name: description xwayland: 24.1.11 -> 24.1.12
Node.js subsystem: description stream: fast-path stateless transform flush results

遗憾的是,尽管一些最成功的开源项目都采用了这种提交方式,但它似乎在品牌推广方面已经败下阵来。我打算改变这种现状。隆重推出 scopedcommits.com 。该网站致力于倡导回归提交信息的清晰规范,并将变更日志的生成与提交日志的管理分开。

结论

传统提交模式所谓的优势实际上只是假象,业界并未从将其作为标准中看到任何实际益处。然而,不幸的是,传统提交模式似乎在开源项目中相当流行,因此人工智能似乎也习惯于默认使用它来编写提交信息。这导致了充斥着反模式的提交信息在项目中传播。

本文旨在挑战传统提交信息的垄断地位,并论证构建提交信息有更优方案。但如果本文未能说服你放弃使用传统提交信息,我期待在评论区看到一场激烈的辩论。

[^1]: 严格来说,Conventional Commits 规范只定义了 fix \commit\ 和 \ feat commitlint\,其他类型则留给各个项目自行指定。然而,大多数项目最终都使用 commitlint 定义的 类型,所以我在此列表中包含了其中一些

如何优雅地用 Python 为项目安装和创建 Python 包依赖?

在接手他人项目的时候,或者创建一个项目依赖时:

  • 如果项目用 anaconda,项目就需要导出一个 environment.yaml ,存在下面问题:

    • 文件过大:Anaconda 的 environment.yaml 往往包含了大量的包和依赖,其中一些可能与项目不直接相关。这不仅增加了环境搭建的复杂性,而且可能引入不必要的依赖。
    • 平台依赖性:environment.yaml 文件可能包含特定于操作系统或平台的包,这会在不同系统间迁移项目时造成问题。
    • 最大的问题是:environment.yaml 文件无法复现用 whl 包或者 gz 包等这些本地包安装的库。也无法安装第三方源安装的包,比如 pytorch。
  • 如果项目用 pip,项目就需要导出一个 requirements.txt 文件,但这种方法也有其局限性:

    • 不完整的依赖管理:requirements.txt 文件通常只列出了项目直接依赖的包,而不包括这些包的依赖(即二级依赖)。这可能导致缺失依赖或版本冲突。
    • 版本冲突问题:在没有明确指定依赖版本的情况下,pip 可能会安装最新版本的包,这可能与项目的实际兼容性不符。
    • 安装顺序问题:pip 不保证按照 requirements.txt 中的顺序安装包,这在某些包有特定安装顺序要求时可能导致问题。

相信平常接手过其他人项目的人应该很清楚其中的血与泪,因此我们更推荐 Poetry,它有以下的优点:

  1. 更简洁的依赖文件:Poetry 使用 pyproject.toml 来管理依赖,这使得依赖声明更为简洁和直观。同时,Poetry 通过 poetry.lock 文件锁定具体版本,确保环境的一致性。
  2. 完整的依赖解析:Poetry 能够解析和管理项目的全部依赖(包括子依赖),减少了版本冲突的可能性。
  3. 虚拟环境管理:Poetry 自动管理虚拟环境,无需手动创建和激活,简化了环境配置的步骤。
  4. 更好的跨平台兼容性:Poetry 在处理依赖时考虑了跨平台的问题,使得在不同操作系统间迁移和设置项目变得更加容易。
  5. 支持本地包和多源包:Poetry 支持从本地路径、私有仓库或多个源安装包,提供了比 pip 更灵活的包管理选项。

Poetry 类似于 pip,能帮助你进行包管理(dependency management),但它比 pip 强大得多,因为它还包含了以下 pip 没有的功能:

  • 虚拟环境管理
  • 包依赖管理
  • 包的打包与发布

虚拟环境管理

指的是使用内置的 venv 或 virtualenv 包来创建和管理 Python 的虚拟环境,不同的虚拟环境之间是相互独立的,也就是说,它们对应的路径各不相同。

包管理

包管理(dependency management) 指的是使用像 pip 这类的包管理器来管理 Python 环境(不一定是虚拟环境),即管理环境中安装的所有包(package、dependency)及其版本。

依赖性管理

在这个语境下,dependency 基本上就是你安装的包(package)。

包的“依赖性管理”(重要) 这个有点难以定义,因为它不是一个有广泛共识的术语,在英文中也难以找到对应的单词。这里指的是管理包之间的“依赖关系”和“版本冲突”这两件事:

  • 依赖关系指的是,当一个包被安装时,它所依赖的包也必须一并安装(这个比较简单)。相反,当一个包被移除时,它所依赖的包也必须一并移除——除非这些包还被其他包所依赖(这就复杂了)。
  • 而版本冲突,指的是单一包被两个以上的包所依赖,但不同的包对依赖的包有不同的最低或最高版本要求,若两者要求的范围“没有交集”,则会发生冲突,导致包失效或无法安装。

这两大问题,都是 pip 无法解决的,也是 Python 开发上的两大痛点。

How? Conda + Poetry

由于我们大多数人都有 Conda,用 Conda 创建 Python 版本,然后用 Poetry 创建虚拟环境,然后管理包依赖。

Conda 创建预期 Python

conda 在这里用于创建特定的 Python 版本。

  • 如果是接手项目,参考 pyproject.toml 中内容(如下),如果发现是 Python 3.8,则创建一个环境 conda create -n python_env_name python=3.8

    [tool.poetry.dependencies]
    python = "^3.8"
  • 如果是自己创建项目,只需要用 conda 创建自己想要的环境。

安装 Poetry

pip install poetry==1.7.1 -i https://pypi.tuna.tsinghua.edu.cn/simple

下面的指令都需要在包含 pyproject.toml 文件的项目目录中运行:

  1. 打开终端或命令提示符:首先,确保你可以访问命令行界面。
  2. 定位到你的项目目录:使用 cd 命令切换到包含 pyproject.toml 文件的项目目录。

    cd path/to/your/project

创建项目虚拟环境

  1. 确认pyproject.toml存在:
  • 若存在 pyproject.toml,则运行

    conda activate python_env_name    # 激活对应 Python 环境
    poetry env use python # 使用当前 conda

    使用 Poetry 创建项目的虚拟环境是一个自动化过程,这可以确保你的项目依赖被正确地隔离。

  • 若不存在pyproject.toml,则运行下一节 初始化项目的 pyproject.toml

  1. 初始化虚拟环境:在项目目录中,运行以下命令来使用 Poetry 创建虚拟环境。
poetry env use python   #   使用当前环境的 Python
poetry shell    #   进入虚拟环境

这个命令会创建一个新的虚拟环境(如果尚未创建),并激活它。如果虚拟环境已经存在,它会被重新激活。

  1. 检查虚拟环境信息:要确认虚拟环境已经被正确创建并激活,可以使用以下命令:
poetry env info

这个命令会显示关于当前激活的虚拟环境的信息,包括它的位置和使用的 Python 版本。

初始化项目的 pyproject.toml

如果是创建项目则需要用 poetry init 初始化项目文件,它会引导你创建一个 pyproject.toml 文件,这个文件是 Poetry 用来管理项目的依赖和各种配置的主要文件。以下是如何使用 poetry init 的步骤:

  1. 运行 poetry init 命令:在项目目录中,运行以下命令:

    poetry init
  2. 跟随提示进行操作:执行该命令后,Poetry 会引导你通过一系列问题来创建 pyproject.toml 文件。这包括:

    • 项目的基本信息:例如项目名称、版本、描述、作者等。
    • 定义依赖:Poetry 会询问是否要交互式地定义项目依赖。如果选择是,它会提示你搜索并添加包。你也可以跳过这一步,稍后手动添加依赖到 pyproject.toml 文件中。
    • 开发依赖:类似地,Poetry 也会询问是否要添加开发时依赖(例如,用于测试或构建的包)。
  3. 完成并查看 pyproject.toml:完成上述步骤后,Poetry 会创建 pyproject.toml 文件。打开这个文件,确认里面的内容是否符合你的预期。
  4. (可选)手动编辑 pyproject.toml:如果需要,你可以直接编辑这个文件来添加或修改依赖、修改项目信息等。如果你想要用第三方源(比如清华源),请看下下节【添加镜像源】。

安装项目依赖

  • 如果是接手项目,直接 poetry install就可以,它会自动根据依赖使用最佳的安装方式。poetry install 命令是 Poetry 中用于安装项目依赖的主要命令。当你在项目中运行此命令时,它会根据 pyproject.toml 文件和 poetry.lock 文件(如果存在)安装所有必要的依赖。以下是使用 poetry install 的基本步骤:

    1. 运行 poetry install 命令:在项目目录中,运行以下命令:

      poetry install

      这个命令将会:

      • 检查 pyproject.toml 文件中列出的依赖。
      • 如果存在 poetry.lock 文件,Poetry 会根据该文件中锁定的版本来安装依赖。这确保了环境的一致性。
      • 如果不存在 poetry.lock 文件,Poetry 会解析 pyproject.toml 中的依赖并生成一个新的 poetry.lock 文件。
      • 安装所有必要的依赖到项目的虚拟环境中。如果项目的虚拟环境还未创建,Poetry 会先创建它。
    2. 验证安装:安装完成后,你可以通过运行项目或其测试来验证依赖是否正确安装。

poetry init 是一个非常方便的命令,特别是当你开始一个新项目而还未确定所有依赖时。它不仅帮助你创建了项目的基本结构,还使得后续的依赖管理变得简单。此外,即使在项目已经开始的情况下,poetry init 也可以用于引入 Poetry 作为依赖管理工具。在这种情况下,它将帮助你创建 pyproject.toml 文件,而不会影响现有的项目文件。

添加镜像源

poetry source add 用于向 Poetry 项目添加一个新的包源(repository)。这在你需要从不是默认的 PyPI 源安装包时非常有用,比如你可能需要从私有仓库或其他第三方仓库安装包。以下是如何使用 poetry source add 的步骤:

  1. 添加新的包源:使用 poetry source add 命令添加一个新的源。这个命令通常需要三个主要参数:源的名称、源的 URL 和源的类型(默认为 legacy)。

    poetry source add   [--type ]

    例如,如果你想添加一个名为 my-private-repo 的私有仓库,你可以运行:

    poetry source add my-private-repo https://private-repo.example.com/simple/

    如果该仓库是一个 PyPI 兼容仓库(例如,使用 PyPI 格式的私有 Artifactory 仓库),你可能不需要指定 --type 参数,因为默认值 legacy 适用于标准的 PyPI 格式仓库。

  2. 验证源是否已添加:完成添加源后,你可以通过查看 pyproject.toml 文件来验证新源是否已被正确添加。该文件现在应该包含一个 [tool.poetry.source] 部分,其中列出了刚刚添加的源信息。
  3. 安装或更新依赖:添加新的源之后,你可以通过 poetry addpoetry update 命令来安装或更新依赖,Poetry 将会考虑这些新添加的源。

使用 poetry source add 命令可以扩展你的项目以从多个源获取依赖,这在使用私有包或特定于项目的包时特别有用。

例子,添加清华源作为默认源:

poetry source add tsinghua https://pypi.tuna.tsinghua.edu.cn/simple --default

增加依赖包

poetry add 命令是用于向 Poetry 管理的项目中添加依赖的。当你执行这个命令时,Poetry 会将指定的包添加到你的 pyproject.toml 文件中,并自动更新 poetry.lock 文件,以确保依赖的一致性。以下是使用 poetry add 的基本步骤:

  1. 添加一个依赖:使用 poetry add <package-name> 命令添加一个依赖。例如,如果你想添加 requests 包,你应该运行:

    poetry add requests

    这会将 requests 包添加到你的项目中,并自动选择一个合适的版本。

  2. 指定依赖版本:如果你需要指定依赖的特定版本或版本范围,你可以在包名后加上版本号。例如:

    poetry add requests@^2.25.1 
    poetry add requests==2.25.1 # 或者像 pip 一样。

    这将会安装 requests 包的 2.25.1 版本或更高的兼容版本(但不会升级到 3.x 版本)。

  3. 添加开发依赖:如果你想添加一个仅在开发过程中使用的包(如测试框架),可以使用 -D--dev 选项。例如,添加测试库 pytest 作为开发依赖:

    poetry add pytest --dev
  4. 等待命令执行完成:Poetry 会解析和安装依赖,并更新 pyproject.tomlpoetry.lock 文件。
  5. 验证依赖:添加完依赖后,可以通过运行项目来验证新添加的依赖是否按预期工作。

poetry add 命令简化了依赖管理过程,它不仅添加了依赖,还考虑了版本兼容性和项目的整体依赖树。通过自动更新 pyproject.tomlpoetry.lock 文件,它确保了项目依赖的一致性和项目团队成员之间的依赖同步。

移除依赖包

在 Poetry 中删除一个包,即从项目的依赖中移除它,使用 poetry remove 命令实现。命令会从 pyproject.toml 文件中删除指定的包,并更新 poetry.lock 文件以反映这一变化。以下是详细步骤:

  1. 删除一个依赖包:使用 poetry remove <package-name> 命令删除一个依赖。例如,如果你想从项目中移除 requests 包,你应该运行:

    poetry remove requests

    这会从你的项目依赖中移除 requests 包,并更新 pyproject.tomlpoetry.lock 文件。

  2. 删除开发依赖:如果要删除的包是一个开发依赖(即原本是使用 poetry add --dev 添加的),你同样使用 poetry remove 命令,Poetry 会自动识别它是开发依赖并进行处理。

    poetry remove 
  3. 等待命令执行完成:Poetry 会处理依赖的移除,并更新相关文件。
  4. 验证依赖已被移除:完成命令执行后,你可以检查 pyproject.toml 文件以确认该依赖已被移除。同时,确保项目在移除了该依赖后仍然按预期运行。

通过使用 poetry remove 命令,你可以轻松地管理项目的依赖列表,确保它们保持最新且与项目需求一致。

如何发布

  • 编写 README 文件:一个清晰的 README 文件对于你的包的可用性至关重要。确保它包含了项目描述、安装指南、使用示例和任何必要的文档链接。

  • 检查版本号:确保 pyproject.toml 文件中的版本号是正确的,并且遵循语义化版本控制规则。如果是首次发布,通常版本号会是 0.1.01.0.0

  • 运行 poetry lock 命令poetry lock 命令在 Poetry 中用于创建或更新 poetry.lock 文件,这个文件精确记录了项目所有依赖(包括子依赖)的具体版本。通过使用这个命令,你可以确保在不同环境中重现相同的依赖安装,这对于项目的一致性和稳定性至关重要。以下是使用 poetry lock 命令的步骤:

    1. 在项目目录中,运行以下命令:

      poetry lock

      这个命令会执行以下操作:

      • 解析 pyproject.toml 文件中指定的依赖,包括版本范围。
      • 生成或更新 poetry.lock 文件,其中包含了所有依赖的精确版本号和其他必要信息,以确保在任何环境中安装相同的依赖集。
    2. 检查 poetry.lock 文件:执行命令后,检查项目目录中的 poetry.lock 文件。这个文件应该被更新,反映了当前依赖的精确状态。
    3. 提交 poetry.lock 文件到版本控制系统:为了保持团队或部署环境间的一致性,确保将更新后的 poetry.lock 文件提交到版本控制系统(如 Git)。

    使用 poetry lock 命令的场景包括:

    • 当你添加、删除或修改 pyproject.toml 中的依赖时。
    • 当你希望更新依赖到可用的最新版本,但又不想更改 pyproject.toml 中的版本规范时。
    • 当你需要确保项目依赖的一致性和稳定性时。

    记住,poetry.lock 文件应该与 pyproject.toml 文件一起被版本控制,以保证项目在不同开发和部署环境中的一致性。(注意:poetry add 运行时会自动运行 poetry lock

  • Git 发布:通过 git 命令 push 到远程仓库。

进阶用法

配置 Poetry 和 PyPI 仓库

为了更方便地管理和发布包,你可以配置 Poetry 以使用不同的 PyPI 仓库。

  1. 添加自定义仓库:如果你想使用除了官方 PyPI 以外的仓库,可以添加一个新的仓库配置:

    poetry config repositories.custom_repo_name https://custom-repo-url.com

    这里的 custom_repo_name 是你为仓库设置的名字,https://custom-repo-url.com 是仓库的 URL。

  2. 发布包到自定义仓库:如果你开发了自己的包并想发布到配置的仓库,可以使用:

    poetry publish --repository custom_repo_name

    确保你已经正确设置了认证信息,如需要的 API token。

使用 Poetry 进行版本控制

Poetry 还可以帮助你管理项目的版本。

  1. 版本号:查看当前项目的版本号,可以使用:

    poetry version
  2. 版本更新:要更新项目版本(如进行小的修改或发布新的版本),可以使用:

    poetry version patch  # 小更新
    poetry version minor  # 次要更新
    poetry version major  # 主要更新

    这将更新 pyproject.toml 中的版本号。

常见问题

HTTPResponse Error

一般来说,这是因为你在安装某个安装包的时候,影响了 poetry 库所依赖的库,如果你使用了 poetry 自带的虚拟环境,就不会出现这种问题。比如下面的 urllib3:

在遇到 'HTTPResponse' object has no attribute 'strict' 问题时,应该降级 urllib3,一般这种情况是因为 Poetry 本身不兼容更高版本的 urllib3

'HTTPResponse' object has no attribute 'strict' · Issue #7936 · python-poetry/poetry

Poetry has urllib3 pinned below 2.0, so you had to mess something up on your end (for example by installing your project and poetry into the same environment). In any case, this is not Poetry’s fault.

运行:

poetry add urllib3==1.26.15

安装 Pytorch 的例子

pytorch 官方源

Previous PyTorch Versions | PyTorch 从这里找地址,缺点是下载速度慢,容易超时失败。

建议设置环境变量 POETRY_REQUESTS_TIMEOUT 作为超时时长,单位秒。

poetry source add -p explicit pytorch https://download.pytorch.org/whl/cu118
poetry add --source pytorch torch torchvision

腾讯云源

缺点是这个源只支持 cuda 12.1 的 torch,不过一般情况下建议使用这个,能兼容我们的设备:

poetry source add -p explicit pytorch-tencent https://mirrors.cloud.tencent.com/pypi/simple/
poetry add --source pytorch-tencent torch torchvision

本地源

Previous PyTorch Versions | PyTorch 下载,比如从 https://download.pytorch.org/whl/cu118下载符合你系统环境的版本,建议安装本地包的时候上传到 dvc,加快下载速度:Tech / Kensho / mchat_x_LMA · GitLab 就是这样。

  • pyproject.toml 中添加内容就好了,例如:
[tool.poetry.dependencies]
python = "^3.8"
loguru = "^0.7.2"
dvc = "2.51.0"
dvc-webhdfs = "^3.1.0"
lit = { file = "model/torch2.0.1_cu118/lit-15.0.7.tar.gz" }
torch = { file = "model/torch2.0.1_cu118/torch-2.0.1+cu118-cp38-cp38-linux_x86_64.whl" }

添加之后运行 poetry install

  • 直接运行
poetry add model/torch2.0.1_cu118/torch-2.0.1+cu118-cp38-cp38-linux_x86_64.whl

尾声

进阶请查看官方文档:Introduction | Documentation | Poetry – Python dependency management and packaging made easy

本文说明了使用 Poetry 的动机、基本使用说明,以及在特定场景下的使用。poetry是什么poetry 中文poetry 中文poetry 中文poetry 中文poetry 中文poetry 中文poetry 中文poetry 中文poetry 中文poetry 中文poetry 中文poetry 中文poetry 中文poetry installpoetry install

使用 VScode 远程 ssh 连接

服务器安装 ssh

以 Ubuntu 为例,通过使用Ctrl+Alt+T键盘快捷键或单击终端图标打开终端,然后openssh-server通过键入以下命令安装软件包:

sudo apt update
sudo apt install openssh-server

安装完成后,SSH服务将自动启动。要验证安装是否成功并且SSH服务正在运行,请键入以下命令,该命令将显示SSH服务器状态:

sudo systemctl status ssh

Ubuntu附带了一个名为 UFW 的防火墙配置工具。如果系统上启用了防火墙,请确保打开SSH端口:

sudo ufw allow ssh

username使用实际用户名和ip_address安装SSH的Ubuntu计算机的IP地址更改。

如果您不知道IP地址:

ip a

修改 ssh 配置,在目录 /etc/ssh/sshd_config

PermitRootLogin yes
PubkeyAuthentication yes
AuthorizedKeysFile  .ssh/authorized_keys
PasswordAuthentication no
ChallengeResponseAuthentication no

什么意思看英文就知道了。具体查看 OpenSSH服务嚣的配置和使用 – APWABC,如下:

PasswordAuthentication密码验证关闭,并同时关闭ChallengeResponseAuthentication键盘交互方式

#PasswordAuthentication yes
PasswordAuthentication no

#ChallengeResponseAuthentication no
ChallengeResponseAuthentication no

通过PubkeyAuthentication开启密钥验证

#PubkeyAuthentication yes
PubkeyAuthentication yes

由于想要无密码登录,我们需要生成公钥和私钥,把 id_rsa.pub 改成 ~/.ssh/authorized_keys (若没有该目录,请执行mkdir ~/.ssh创建)

sudo service ssh start

客户端使用 vscode 连接 ssh

vscode 需要安装 Remote SSH 等。

vscode 编辑 settings.json,添加"remote.SSH.showLoginTerminal": true

然后点击远程资源管理器,新建连接并且

ssh username@ip_address

就大功告成了。

在Ubuntu上禁用SSH

如果出于某种原因要在Ubuntu计算机上禁用SSH,则可以通过运行以下命令停止SSH服务:

sudo systemctl stop ssh

要再次启动它,请运行:

sudo systemctl start ssh

要禁用SSH服务在系统引导运行期间启动:

sudo systemctl disable ssh

要再次启用它,请输入:

sudo systemctl enable ssh

参考链接

VSCode远程编辑,Remote-SSH,Remote Development – 亱_風 – 博客园

ubuntu 安装ssh – SegmentFault 思否

不要再讨论值传递和引用传递了

网上有太多这样的无效讨论了,像「Java 是值传递还是引用传递?」,「Python 传可变对象就是引用传递,传不可变对象就是值传递!」、「一切都是值传递!」的话题真的是起码一个月来一次。

什么是值传递和引用传递?

传引用调用、传值调用是计算机科学里面的求值策略。求值策略定义何时和以何种次序求值给函数的实际参数,什么时候把它们代换入函数,和代换以何种形式发生。

  • 值传递意味着传 作为参数。
  • 引用传递意味着传 变量 作为参数。

注意这里的引用传递,它和引用类型毫无关系,更和 Java 和 Python 的引用类型没有关系( Java 之类的引用类型更像是 C/C++ 里面的指针)。C++ 里面的类似别名一样的引用类型,和 Java 、Python 的引用类型不同,它有时候在传递过程中符合引用调用的特点。

函数调用

要清楚为什么,就得先明白是什么。

以 C++ 为例:

void func(int x){
    x++;
}

int main(){
    int test = 2;
    func(test);
    return 0;
}
  • func 是我们调用的函数。
  • test 是一个变量,同时是 func 的实际参数 ( argument )。
  • x 也是变量,而且是局部变量,同时是 func 的形式参数 ( parameter )。
  • 实际参数是,它可以由字面量值或者变量提供。
  • 形式参数是变量,它只能是变量。
  • main 是调用者 ( caller )。
  • func 是被调用者 ( callee )。

传值调用 Call by value

值传递策略中,一旦开始函数调用过程,形式参数就会以实际参数的值初始化,且二者互不影响

相当于我有一份 doc 文档,复制了一份给你。你怎么修改你那一份,对我的文档都不会有影响。

如果是值传递,第一段代码相当于:

int test = 2;
int x = test;
x++;

C 语言是传值调用的。

传引用调用 Call by reference

引用传递策略中,函数调用将形式参数就是实际参数的别名,二者是同一个变量

相当于我有一份 doc 文档,你修改这份文档,对我来说是可见的。

如果是引用传递,第一段代码相当于:

int test = 2;
test++;

传共享对象调用 Call by sharing

传值、传引用已经不适用于现代编程语言,现在编程语言一般是传入一个特殊的引用类型,相当实际参数复制了一份地址给形式参数,但我们可以根据这个地址去修改对象,使得该函数之外的作为实际参数的变量也会发生改变。

1974 年,Barbara Liskov 意识到自己的 CLU 语言不是二者的任一种,于是命名了传共享对象调用。Python、Java、JavaScript、Scheme、OCaml 等语言都使用了传共享对象调用。(Evaluation strategy – Wikiwand

传共享对象调用中,函数传递的是一个可以共享的对象,这样就可以达到引用调用的效果:一旦被调用者修改了对象,调用者就可以看到变化。

如果坚持要一分为二,那么现在的 Java、Python 都是传值调用,只不过传的是一个可以被函数改变的对象。例如 Python:

def func(alist):
    alist.append(1)
    alist = [0]

def main():
    src = []
    func(src)
    print(src)

if __name__ == '__main__':
    main()

上面代码会打印出 [1],因为列表是可变对象,append 方法改变了 alist。而赋值局部变量的 alist = [0]对函数调用之外的作用域没有影响。

像 Java 也是这样:

class Test {
    public void addBrand(String[] book) {
        book[0] = "0";
        book = new String[]{"1"};
    }

    public static void main(String[] args) {
        Test test = new Test();
        String[] sci_book = new String[1];
        test.addBrand(sci_book);
        out.println(Arrays.toString(sci_book));
    }
}

打印出 [0] 而非 [1]

在这类语言中赋值是给变量绑定一个新对象,而不是改变对象。

等我考完试接着写!

第 10 课 NumPy 计算和广播原理

前情提要

第 9 课补

  • arr[2, :]

  • np.ix_()

    import numpy as np
    
    arr2 = np.arange(32).reshape((8,4))
    print(arr2)
    
    arr2[np.ix_([1,5,7,2],[0,3,1,2])]
    
    arr2[np.ix_([1,5,7,2],[0,1,2])]

第 9 课答案

    1. numpy.random.randint

      np.random.seed(3)
      x1 = np.random.randint(low=1, high=10, size=100)
      np.save("save.npy", x1)
      
      x1 = np.load("save.npy")
      print(x1)

课程纲要

  • 数学计算

    • 逻辑运算np.where np.all() np.any()
    arr = np.random.rand(2, 3)
    result = np.where(arr > 0.5, 1, 0)
    #   condition ? arr1: arr2
    arr = np.random.rand(2, 3)
    result = np.all(arr > 0.1)
    arr = np.array([2, 2, 3, 3, 1])
    result = np.unique(arr)
    • 线性代数:numpy.linalg 库文档

    • np.trace np.inner()

    • linalg.qr linalg.svd

      arr = np.array([[2, 2, 3, 3], [4, 3, 2, 1]])
      U, s, V = np.linalg.svd(arr)
    • 统计运算

    • 求和 arr.sum(axis=0)

      arr = np.random.rand(2, 3)
      result = np.sum(arr)
      
      result = np.sum(arr > 0.5)    #   根据条件求真值的和
    • 最值 arr.max() arr.min() arr.argmin() arr.argmax() 文档

      arr = np.random.rand(2, 3)
      result = np.max(arr)
      
      arr = np.random.rand(2, 3)
      result = np.argmin(arr)
    • 算术平均数 np.mean()

      arr = np.random.rand(2, 3)
      result = np.mean(arr)
    • 标准差、方差 np.std() np.var()

  • Numpy 的形状操作

    • 添加维度
    arr = np.array([1, 2, 3])
    result = arr[np.newaxis, :, ]
    print(arr)
    print(result)
    • 改变维度个数和大小 np.resize() np.reshape() ,前者改变源数组,后者不会。
    arr = np.random.random((4, 4))
    print(arr)
    arr.resize((2, 3))
    print(arr)
    arr.resize((1))
    print(arr)
  • NumPy 的广播原理

    • 维度和维度大小

    • 广播(broadcasting)

    • 什么是广播

      arr = np.ones((2, 4))
      arr2 = 1
      print(arr)
      print(arr2)
      print(arr + arr2)
    • 规则1:数组维度和大小,从后往前有连续的相同部分

      arr = np.ones((2, 4, 4))
      arr2 = np.ones((4, 4))
    • 规则2:不相同的部分维度大小为1

      arr = np.zeros((2, 4, 4))
      arr2 = np.ones((1, 1))
  • Matplotlib

    • 画一个三角函数吧
    import numpy as np
    import matplotlib.pyplot as plt
    
    X = np.linspace(-np.pi, np.pi, 100)
    COS, SIN = np.cos(X), np.sin(X)
    ax = np.zeros(100)
    plt.plot(COS)
    plt.plot(SIN)
    plt.plot(ax)
    plt.show()