我正在转向 Python 并且真的喜欢它

大约六个月前,我开始更多地使用 Python 进行编码。为什么?显然是因为人工智能。在我看来,如今人工智能领域到处都是巨大的商机。而猜猜人工智能的事实上的编程语言是什么?没错,就是那个狡猾的家伙。

我之前也用过Python,但仅限于编写小脚本。例如,这个脚本会从我所有的视频中提取元数据 我的YouTube频道上的所有视频中提取元数据。元数据被导出为一个JSON文件 格式导出,我将其用于在此静态页面上美观地展示视频统计信息。如您所见此处, 这个小脚本每周一通过 GitHub Actions 以独立模式运行。在 Python 中做这类事情比使用批处理程序方便得多。不仅因为 Python 的语法更易于理解,还因为 Python 解释器在所有 Unix 发行版中都原生集成。这难道不酷吗?

所以,Python 确实很强大,而且它与如今无处不在的 VSCode 编辑器配合得非常默契。但我直到最近才认真对待它,1 就在我想要构建人工智能应用程序(RAG、代理、生成式人工智能工具等) 等)时,我才意识到,无论你喜不喜欢,Python都是这些领域的首选语言。

因此,我决定认真尝试一下,令我惊讶的是,我发现Python及其周边生态系统在过去几十年里确实有了很大的改进。

元素周期表

以下是三个例子:

  1. Python 已经构建了一个非常完整的库和工具生态系统,用于处理和分析数据。2
  2. Python 通过优化后的静态编译器(如 Cython)变得更快。
  3. Python 成功掩盖了其历史遗留的丑陋之处(如 __init____new__ 等异常),通过优化语法以适应具有良好品味的开发者。

正因如此,我如今对这门语言产生了特别的喜爱之情。

然而,在此期间,我发现使用 Python 开发“生产就绪”3 应用与常规的 Jupyter 笔记本或脚本工作流之间仍存在巨大差距。

因此,在这篇文章中,我将分享那些带给我快乐的工具、库、配置以及其他集成,这些是我现在用于构建 Python 应用程序的。

⚠️ 这篇文章高度偏向于我目前使用的工具,如果你认为我遗漏了某些宝贵的工具,请告诉我/我们(最好在下方评论区)。

注意:这篇文章在Hacker News上获得了600多条评论,这再次证明了你永远无法预料结果。

项目结构

我倾向于使用单仓库结构(后端和前端)来管理我的 Python 项目。4

为什么?

  1. 因为我记忆力不好:我不喜欢代码片段分散在多个仓库中(这绝对不方便搜索)。
  2. 因为多个仓库大多是多余的:我只是一个人,我相信如果一个项目发展到需要拆分成多个仓库的程度,那说明这是过度工程化的表现。
  3. 因为我懒惰:我喜欢将事情尽可能简化,从单一位置进行编译、测试、容器化及部署。5

我希望有一个工具能为我生成项目结构,但目前尚未找到适合我的工具。过去我使用过CCDS,这是一个主要针对数据科学项目的初始化工具。它非常出色,但其核心用户群体并非全栈开发者。

以下是一个前端-后端架构项目的典型结构(我将在本文后面的部分详细介绍每个子部分):

project/
│
├── .github/ # GitHub Actions workflows for CI/CD pipelines
│   ├── workflows/ # Directory containing YAML files for automated workflows
│   └── dependabot.yml # Configuration for Dependabot to manage dependencies
│
├── .vscode/ # VSCode configuration for the project
│   ├── launch.json # Debugging configurations for VSCode
│   └── settings.json # Project-specific settings for VSCode
│
├── docs/ # Website and docs (a static SPA with MkDocs)
│
├── project-api/ # Backend API for handling business logic and heavy processing
│   ├── data/ # Directory for storing datasets or other static files
│   ├── notebooks/ # Jupyter notebooks for quick (and dirty) experimentation and prototyping
│   ├── tools/ # Utility scripts and tools for development or deployment
│   ├── src/ # Source code for the backend application
│   │   ├── app/ # Main application code
│   │   └── tests/ # Unit tests for the backend
│   │
│   ├── .dockerignore # Specifies files to exclude from Docker builds
│   ├── .python-version # Python version specification for pyenv
│   ├── Dockerfile # Docker configuration for containerizing the backend
│   ├── Makefile # Automation tasks for building, testing, and deploying
│   ├── pyproject.toml # Python project configuration file
│   ├── README.md # Documentation for the backend API
│   └── uv.lock # Lock file for dependencies managed by UV
│
├── project-ui/ # Frontend UI for the project (Next.js, React, etc.)
│
├── .gitignore # Global Git ignore file for the repository
├── .pre-commit-config.yaml # Configuration for pre-commit hooks
├── CONTRIBUTING.md # Guidelines for contributing to the project
├── docker-compose.yml # Docker Compose configuration for multi-container setups
├── LICENSE # License information for the project (I always choose MIT)
├── Makefile # Automation tasks for building, testing, and deploying
└── README.md # Main documentation for the project (main features, installation, and usage)

我的 project 是根目录,也是我的 GitHub 仓库的名称。我喜欢为项目使用简短的名称,理想情况下不超过 10 个字符。不使用 snake_case;使用连字符分隔对我来说是可以接受的。请注意,项目应是自包含的,这意味着它应包含文档、构建/部署基础设施以及任何其他必要的文件,以便独立运行。

project-ui 中不应进行任何复杂的数据处理步骤,因为我选择将前端逻辑与后端职责分离。相反,我选择向包含 Python 代码的 project-api 服务器发送 HTTP 请求。这样,我们可以保持浏览器应用程序轻量级,同时将复杂的计算和业务逻辑委托给服务器。

project-api/src/app目录下有一个__init__.py文件,用于标识app是一个Python模块(可被其他模块导入)。

Python工具箱

uv

我使用uv作为Python包管理器和构建工具。它是我安装和管理依赖项所需的全部工具。

以下是设置 uv 的核心命令:

# Install uv globally if not already installed
curl -sSfL <https://astral.sh/install.sh> | sh

# Initialize a new project (adds .gitignore, .python-version, pyproject.toml, etc.)
uv init project-api

# Add some dependencies into the project and update pyproject.toml
uv add --dev pytest ruff pre-commit mkdocs gitleaks fastapi pydantic

# Update the lock file with the latest versions of the dependencies (creates a .venv if not already created)
uv sync

# Activate the .venv
uv venv activate

请注意,uv 最重要的文件是 pyproject.toml6 该文件包含元数据以及构建和运行项目所需的依赖项列表。

ruff

我非常喜欢 ruff。它是一款超快的 Python 代码检查工具和代码格式化工具,专为像我这样的懒惰开发者设计,帮助我们保持代码库的整洁和可维护性。Ruff 将 isortflake8autoflake 等工具整合到一个命令行界面中:

# Lint all files in `/path/to/code` (and any subdirectories).
ruff check path/to/code/

# Format all files in `/path/to/code` (and any subdirectories).
ruff format path/to/code/

Ruff 默认支持 PEP 8 风格指南。

ty

ty 是 Python 的类型检查器。它与 typing 模块(用于添加静态类型的流行 Python 模块)配合使用效果极佳。我认为 typing 确实有助于我在开发初期就发现类型错误。我并不介意需要编写更多代码,事实上,如果这能提升代码质量并降低运行时错误的概率,我更倾向于这样做。

注意:截至撰写本文时,ty 仍处于 Astral 公司(与 uvruff 同一公司)的早期开发阶段,但我一直使用它,目前尚未发现任何明显缺陷。

pytest

pytest 是 Python 的 THE 测试库。使用它编写简单且可扩展的测试用例非常轻松。它支持测试 fixture、参数化测试,并拥有丰富的插件生态系统。只需在 project-api/src/app/tests/ 目录下创建一个名为 test_<unit_or_module>.py 的文件,然后运行:

uv run pytest

就这样!

Pydantic

Pydantic 是 Python 中的数据验证和设置管理库。它可帮助管理各类配置设置,例如 API 密钥、数据库连接详情或模型参数(顺便说一句,硬编码这些值是一种非常不好的做法)。

特别是,Pydantic Settings 允许您使用 Pydantic 模型定义应用程序配置。它可以自动从环境变量或特殊的 .env 文件中加载设置,验证其类型,并使其在代码中易于访问。

以下是一个示例:

from pydantic import BaseSettings

class Settings(BaseSettings):
    api_key: str
    db_url: str

    class Config:
        env_file = ".env"

settings = Settings()

现在,当你运行这段代码时,Pydantic 将自动从 .env 文件或环境变量中加载 api_keydb_url 的值。这些值将根据 Settings 模型中定义的类型进行访问和验证。太棒了!

MkDocs

我使用 MkDocs 进行文档编写和项目网站的静态生成。7 我不是设计师,所以我更倾向于从另一个类似的开源项目中复制一个美观的设计,并对 CSS 进行一些简单的修改(如更改字体和颜色)。

FastAPI

我使用FastAPI来构建API。它对我来说是一个巨大的改变;它允许轻松创建带有自动验证、序列化和文档的RESTful API。FastAPI基于Starlette和Pydantic构建,这意味着它提供了卓越的性能和类型安全。它快速、易于使用,并能与Pydantic无缝集成进行数据验证。

数据类

数据类 不是一个库,而是 Python 的一项功能,用于定义主要用于存储数据的类。它们提供了一种简单的语法来创建类,这些类会自动生成特殊方法,如 __init__()__repr__()__eq__()

这大大减少了创建数据容器时的冗余代码。

以下是一个示例:

from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

p = Point(1, 2)
print(p)  # Output: Point(x=1, y=2)

告别冗余代码和晦涩难懂的代码!

版本控制

GitHub Actions

我是GitHub Actions的忠实粉丝,尤其是在跨不同操作系统进行持续集成(CI)时。我建议将其用于API和UI管道。

project-api 的典型工作流程如下:

name: CI-API

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build-and-test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Build Docker image
        run: docker build -t project-api:ci ./project-api
      - name: Run tests
        run: docker run --rm project-api:ci pytest

请注意,此工作流程使用 Docker 在隔离环境中运行测试。8 您可以通过将 runs-on 参数设置为 windows-latestmacos-latest 来更改操作系统。

Dependabot

管理依赖项是一项繁琐的工作,但 Dependabot 使其更加轻松。它会自动检查过时的依赖项并创建拉取请求进行更新。

以下是 .github/dependabot.yml 文件中 Dependabot 的示例配置:

version: 2
updates:

- package-ecosystem: "uv"
    directory: "/"
    schedule:
      interval: "weekly"

Gitleaks

如果有什么事情会损害我们的声誉,那就是将敏感信息(如 API 密钥或密码)直接提交到仓库中。幸运的是,Gitleaks 可以帮助防止这种情况发生。没有理由不使用它。

提交前钩子

我使用 pre-commit 在提交前运行检查并格式化代码。这有助于确保代码始终处于良好状态并遵循项目编码规范。例如,我在提交代码前会使用ruff-pre-commitgitleaks进行检查。

以下是我使用的 .pre-commit-config.yaml 文件示例:

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.12.3  # Ruff version.
    hooks:
      - id: ruff-check # Run the linter.
        args: [ --fix ]
      - id: ruff-format  # Run the formatter.
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.27.2
    hooks:
      - id: gitleaks

基础设施管理

Make

Make 是一个多功能工具,用于自动化任务。我用它来创建常见开发命令的简单快捷方式。无需记住并输入冗长的 CLI 命令来运行测试、构建 Docker 镜像或启动服务,我将这些任务定义在 Makefile 中。然后只需执行 make testmake infrastructure-up 等命令。

如您所见,project-api 和全局 project 目录中均存在 Makefile

  1. project/project-api/Makefile:用于代码检查、测试和运行 API。
  2. project/Makefile:用于构建和运行基础设施(通过 docker-compose)。

以下是 project-api Makefile 的一个极简示例:

DIR := . # project/project-api/Makefile

test:
 uv run pytest

format-fix:
 uv run ruff format $(DIR)
 uv run ruff check --select I --fix

lint-fix:
 uv run ruff check --fix

现在,如果我想运行测试,只需执行 make test,它会在当前目录中执行 uv run pytest

对于全局项目,我使用以下 Makefile

infrastructure-build:
 docker compose build

infrastructure-up:
 docker compose up --build -d

infrastructure-stop:
 docker compose stop

make 是一个强大的工具,可以帮助你自动化开发工作流中的几乎任何任务。尽管上面的示例非常简单,但想象一下你可以根据需要添加更多复杂的任务。

Docker

Docker 是一个工具,允许你将应用程序及其依赖项打包到一个容器中,包括运行所需的一切:依赖项、系统工具、代码和运行时操作系统。在本地工作时,我使用 Docker Compose 将所有 Docker 镜像连接到同一个网络。就像 Docker 用于依赖项一样,Docker Compose 允许封装整个应用程序堆栈并将其与本地环境的其余部分分离。

为了完全理解这个概念,让我们看看一个简单的 docker-compose.yml 文件:

version: '3.8'
services:
  project-api:
    build:
      context: ./project-api
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    volumes:
      - ./project-api:/app
    environment:
      - ENV_VAR=value
    networks:
      - project-network

  project-ui:
    build:
      context: ./project-ui
      dockerfile: Dockerfile
    ports:
      - "3000:3000"
    networks:
      - project-network

networks:
  project-network:
    driver: bridge

在此文件中,我们定义了两个服务:project-apiproject-ui。每个服务都有自己的构建上下文(Dockerfile)、端口、卷和环境变量。

以下是 project-api 服务的示例 Dockerfile

FROM python:3.11-slim

# Install system dependencies

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

WORKDIR /app

COPY uv.lock pyproject.toml README.md ./
RUN uv sync --frozen --no-cache

# Bring in the actual application code

COPY src/app app/
COPY tools tools/

# Define a command to run the application

CMD ["/app/.venv/bin/fastapi", "run", "project/infrastructure/api.py", "--port", "8000", "--host", "0.0.0.0"]

如您所见,该 Dockerfile 基于 Python 基础镜像,安装依赖项,复制项目文件,并定义运行 FastAPI 应用程序的命令。

这样,您就可以通过单个命令运行整个应用程序堆栈:

docker compose up --build -d

脚注

  1. 如果你了解我,就知道我过去主要使用 Java/JavaScript/R 语言。
  2. 例如,如今Jupyter已被几乎所有主要云服务提供商作为交互式数据科学和科学计算的默认工具捆绑提供。
  3. 对我来说,“生产就绪”意味着我可以直接将应用程序部署到云端,无需进行大量基础设施更改。
  4. 别误会我的意思,我明白在某些情况下多仓库结构是必要的,比如多个团队负责项目不同部分,或项目间需要共享依赖项时。
  5. 我认为避免过早分解是一个好主意。如果一个代码库的行数少于,比如说,50万行,那么在它上面添加一个网络层(比如API调用)只会让维护工作变得痛苦,对于~非亚马逊~的理性开发者来说更是如此。
  6. pyproject.toml 文件类似于 Node.js 中的 package.json 或 Java 中的 pom.xml
  7. 顺便说一句,我认为 GitHub 上的每个项目都应该拥有自己的网站(通过 GitHub Pages 实现非常简单),所以没有借口,抱歉。
  8. 使用 Docker 进行持续集成(CI)可以确保与生产环境的一致性,但可能会增加一些冷启动开销。你知道的……妥协,生活就是这样。

本文文字及图片出自 I'm Switching to Python and Actually Liking It

共有 679 条评论

  1. 关于链接脚本中的代码,有几点需要说明:

        API_KEY = os.environ.get(“YOUTUBE_API_KEY”)
        CHANNEL_ID = os.environ.get(“YOUTUBE_CHANNEL_ID”)
    
        if not API_KEY or not CHANNEL_ID:
            print(“缺少 YOUTUBE_API_KEY 或 YOUTUBE_CHANNEL_ID。”
            exit(1)
    

    当没有必要使用“或”时,向用户显示“缺少 X 或 Y”会让用户感到非常沮丧,而这样做几乎没有好处,只是少了一个 if 语句。

        if not API_KEY:
            print(“缺少 YOUTUBE_API_KEY。”
            exit(1)
        if not CHANNEL_ID:
            print(“缺少 YOUTUBE_CHANNEL_ID。”
            exit(1)
    

    用户体验明显更好,开发时间仅慢 0.00001%。

    • 这有点吹毛求疵,但这是 := 运算符的好用例:

        if not (API_KEY := os.getenv(“API_KEY”)):
            ...
      

      对于内部工具,我让 os.environ[“API_KEY”] 引发 KeyError。这样描述性足够强。

      • 我写了不少 Python 代码,但觉得 walrus 运算符不太直观。API_KEY 在 `if` 之外可用有点奇怪,可能是因为我最初在 Go 语言中看到 walrus 运算符时,它的作用域被限制在代码块内。

        • 这其实并非海象运算符独有,只是Python的一般特性(尽管我认为这非常令人烦恼)。for i in range(5): ...会在循环结束后将i绑定为4。

          • 奇怪的是,“except”变量不会保持绑定!

                try:
                    x = int(‘cat’)
                except Exception as e:
                    pass
                print(e)  # <- NameError: name ‘e’ is not defined
            

            因此,Python实际上拥有三个变量作用域(全局、局部、异常块)?

            • 不,情况比这更复杂:

                  e = ‘before’
                  try:
                      x = int(‘cat’)
                  except Exception as e:
                      e2 = e
                      print(e)
                  print(e2) # <- 这段代码能正常运行!
                  print(e)  # <- NameError: name ‘e’ is not defined
              

              这不是作用域问题,异常块中的绑定变量实际上会在异常块结束后被删除,即使它在异常发生前已经绑定!

              • 哈哈 – Raku 可能有点奇怪,但至少它有合理的变量作用域

            • JavaScript在ES5之前也是如此,另一种乍一看似乎只支持函数作用域的语言:它实际上支持块作用域,但仅限于在`catch`块中引入的变量。据我所知,这是笨拙的转译器模拟`let`的标准方式。

              • 我好奇这种做法是否曾流行过,考虑到try/catch的去优化效果,以及块作用域也可以通过重命名变量来管理。

            • 异常作为例外,这有点好笑

            • 异常块不会创建不同的作用域。相反,在try/except块执行后,名称会明确地(虽然是隐式但有意为之)从作用域中删除。这是因为否则会产生引用循环并延迟垃圾回收。

              https://stackoverflow.com/questions/24271752

              https://docs.python.org/3/reference/compound_stmts.html#exce…

              • 这就是让我对所有“天啊 JavaScript”帖子翻白眼的原因[0], 确实有许多随机的类型转换和不少怪癖(我最喜欢的是 document.all 是一个非空集合,但它不等于 false,却会在 if 语句中转换为 false)

                但从底层来看,这门语言是有道理的,作用域、值、绑定都有其大致合理的规则,并不难理解。

                相比之下,Python 似乎是一座由临时规则堆砌而成的无限例外塔,虽然看起来更简单,但无论你往哪里看,都会发现无尽的复杂性[1]

                [0] 而其中一半的抱怨都与“我不喜欢 NaNs 的存在”有关

                [1] 我最喜欢的例子是,双下划线方法是对象实际行为的“同步视图”,即 a + b 实际上是 a. __add__ 从未被检查,相反,在创建时,a 的 add 行为被定义为其 __add__ 方法,但这种关联纯粹是一种约定,例如,任何 C 扩展类型都需要重新实现所有这些同步以暴露正确的行为,并且可以为了好玩决定某种类将使用 __add__ 用于 repr 和 __repr__ 用于 add

            • > 奇怪的是

              这并不奇怪,因为这是唯一一种无法保持其边界的情况,除非你喜欢拥有可能定义也可能未定义的变量(海森堡变量?),这取决于异常是否已被抛出。

              与 if 语句相比,其中被测试表达式中的变量必然已定义。

              • > 与 if 语句相比,if 语句中被测试的表达式中的变量必然已被定义。

                    if False:
                        x = 7
                    print(x)
                
                    print(x)
                          ^
                    NameError: name ‘x’ is not defined
                

                Ruby 就是这样做的,变量基本上是按词法范围定义的(默认为 nil)。Python 并不这样做。在 Python 中,你可以拥有可能仅在特定上下文中存在的局部变量。

              • 虽然这在某种程度上是正确的,但这会绑定到什么呢?

                    for i in range(0):
                        pass
                
                • 写完这条评论后,我意识到 Python 解释器可以在受保护块和 except 块之间定义变量并将其设置为 None,然后在评估 except 块之前(当异常被触发时)隐式将其赋值给触发的异常。因此,从技术上讲,可以在 GP 示例中定义变量 e,并使其作用域为“受保护块之后的内容”,就像对 for 块所做的那样。

                  不过,这样做会不会引发问题呢?此外,为什么需要在except块之后让这个变量可访问?在for块的情况下,知道for块在何时“通过”可能会很有趣。

                  那么,“None”是否能回答你的问题?

                  • 答案是:它未绑定。当你尝试使用 for 循环中的值时,Intellisense 可能会告诉你它是 `Unbound | <type>`。是否有可能默认初始化为 `None`?当然可以,但 `None` 与未绑定的变量是截然不同的值,可能导致不同的处理方式。

              • 您是指这会导致名称错误吗?

                   if <true comparison here>:
                       x = 5
                   print(x)  # <- 是否应引发名称错误?
                
                • 我承认错误,这种例外情况确实是个异常,既是离群值,也是与 Python 语义相悖的奇怪行为。或者说,这是一种奇怪的行为?

                  在类似你示例的 if 语句中,并未对 x 的存在性做出规定。它可能已被提前定义,而这一行代码只是更新其值。

                  你的示例:

                     if True:
                         x = 5
                     print(x)  # 5
                  

                  如果x之前已定义:

                     x = 1
                     if False:
                         x = 5
                     print(x)  # 1
                  

                  那么这个呢?

                     if False:
                         x = 5
                     print(x)  # ???
                  

                  另一方面,“<异常值> as <名称>” 的语法看起来像是引入了一个新名称;如果该名称之前已经存在,会发生什么?它应该只是替换变量的内容吗?为什么使用 ‘as’ 关键字?为什么不使用类似 “except <名称> = <异常值>” 或海象运算符的语法?

                  在调查这个问题时,我尝试了以下内容:

                      x = 3
                      try:
                          raise Exception()
                      except Exception as x:
                          pass
                      print(x)  # <- 这应该打印什么?
                  
                • 在任何正常的语言中,它会这样做,是的。

              • 海森堡 bug 就是你正在寻找但可能找不到的词。

            • 我可能生疏了,但这种情况下不是有“finally”作用域吗?

              编辑:在沙发上用手机写,笔记本电脑看起来远在天边……

          • > `for i in range(5): …` 循环结束后,`i` 仍绑定为 4。回复

            这个“特性”导致了我职业生涯中见过的最严重的安全问题之一。我喜欢 Python,但作用域泄漏是个大问题。(而且是的,我知道其他语言也有类似问题,但这不应成为借口。)

            • 如果你能谈论的话,我很想听听这个安全问题

              • 我不记得确切的细节,但大致是这样的:

                1) 遍历一个权限列表的 for 循环

                2) 循环块结束后,检查用户是否拥有某个权限。执行检查的代码行缩进不正确,本应失败,但由于上一次循环中最后一个权限仍处于作用域内,因此意外通过。

                幸运的是,这并未造成实际影响,因为仅影响同一公司内的用户,但问题依然相当严重。

                • 哎呀,这真是险些酿成大祸。这种难以发现的问题正是让我夜不能寐的根源。虽然也许现在有些AI工具能够检测到这类问题

          • 我认为它这样做非常直观且有用。有时它不支持理解式表达式让我抓狂,但我也明白原因。

            但在REPL或Jupyter中运行循环时,如果出现错误,我已经可以访问相关变量。

            如果我想对形状大致相同的循环数据做点什么,我已经可以访问循环末尾的其中一个元素。

            短路/提前退出循环不需要额外的赋值操作。

            我真的看不到有什么缺点。

            • Python 2 确实允许列表推导式变量泄漏到周围作用域中。他们在 Python 3 中修改了这一行为,可能是因为用列表推导式变量覆盖现有变量会让人感到意外。

              • 哦天啊,也许这就是为什么我期待它以这种方式工作!我不敢相信自从我使用 Python 2 以来已经过去了这么久,以至于我忘记了它的怪癖。

              • 这几乎听起来像是拥有 “变量” eax、ebx、ecx 和 edx。

          • 哦对,这是个好点子。

            Python确实有点乱七八糟哈哈。

          • 我无法告诉你我调试时遇到过多少次类似问题。“你应该知道更好”——我知道,我知道,但我偶尔还是会卡在这里。

          • 否则就太荒谬了。遍历序列中所有元素的 for 循环。如果序列是一个字符串列表,例如,那么“最后一个元素之后的元素”会是什么?

            • 问题不在于i的值,问题在于循环结束后i仍然可用。在大多数其他语言中,如果i是由for-each循环实例化的,它会在for-each循环结束时消失

          • 也许Python有一天会得到一个let

        • Python中没有块作用域。最小作用域是函数。不过,列表推导式中的变量不会泄漏,这会导致一些奇怪的情况:

              >>> s = “abc”
              >>> [x:=y for y in s]
              [‘a’, ‘b’, ‘c’]
          
              >>> x
              'c'
          
              >>> y
              Traceback (most recent call last):
                File “<stdin>”, line 1, in <module>
              NameError: name ‘y’ is not defined
          

          列表推导式为其局部变量拥有独立的局部作用域,但海象运算符会向上追溯到最内层的“可赋值”作用域。

        • 这是 Python 的作用域规则,不受代码块限制,而是由函数决定。其他元素也会产生相同的效果。

        • 哇。我写 Python 已经 15 年了,居然不知道这个运算符的存在

          • 它只存在了6年,所以或许可以原谅你 🙂

            上次我在面试中编写Python时,其中一位面试官说:“等一下,我对Python不太熟悉,但这不是一种旧风格吗?”是的,我承认。我的Python编程风格暴露了我的年龄。

      • 我总是忘记这种语法存在。它是什么时候引入的?

      • 为什么你会使用它呢?我一直认为,遗留代码(如C或C++)在检查内部有这些副作用是一件坏事

      • 没有人使用walrus

        • 看来确实如此!我曾在它刚推出时短暂尝试过,但从未在任何生产代码中使用过。我也从未见过其他人使用过。

        • 我发现有趣的是,我用AI工具生成的海量Python代码中从未出现过walrus运算符。这可能反映了它们训练时使用的代码,我猜。

        • 我经常使用它

        • 我非常经常使用它。它可以避免在列表推导式中重复执行昂贵的操作。

      • 为什么人们要用 Python 写出像 Go 语言一样的代码?

        通常放任 KeyError 不管也没关系,但如果你想记录错误或能以某种方式恢复,那么:

            try:
              API_KEY = os.environ[“API_KEY”]
            except KeyError:
              logger.exception(“...”)
              ...
        
    • 更好的做法是检查所有条件并报告所有错误。别让我添加第一个变量,重新运行后又遇到另一个错误。

      有时这不可避免,但大多数时候会显得冗余。

    • 更好的做法是尝试获取所有环境变量,然后报告所有缺失的变量。

    • 为什么不添加一个布尔标志,并在最后使用单一的 exit(1)?这样用户就可以看到所有未设置的环境变量。

    •     exit(“缺少 ...”)
      

      这会打印消息并以代码 1 退出。

    • 新手请注意。对细节的关注正是区分真正优秀程序员与普通程序员的关键。不过,对于可供他人复用的脚本,应使用命令行参数。用环境变量替代命令行参数是严重的代码臭味。

      • 你通常不希望API密钥意外记录到他人的Bash历史记录中。

        • 您的威胁模型具体是什么?即攻击者可以读取~/.bash_history文件,但无法执行(或捕获输出)/usr/bin/env命令?

          • 威胁模型是:历史信息是持久的,而环境变量则不是。不过,在可能的情况下,您应该使用文件描述符而非环境变量来处理机密信息。

          • 持续集成(CI)和其他构建系统。过去曾发生过令牌被盗的情况,原因在于用户疏忽大意,且虚拟机未被妥善清理。

          • 如何避免在实时屏幕共享时意外显示令牌和凭据?

          • 您不应将密码保存在 shell 历史记录中,这极其不安全。首先,密码可被所有能列出任务的进程读取。

            您可以通过密码管理器保护密码。无需将密码保存在环境变量中,我也不会这么做。

            • > 首先,密码可以被任何能够列出任务的进程读取。

              为什么会有我尚未完全信任的进程在运行,能够执行此类操作?

              > 你可以使用密码管理器保护密码。

              在人们希望自动化使用该程序的情况下,你计划如何向程序提供密码?

          • 威胁模型:shell脚本

      • 对于这个示例,不要只使用命令行参数。那里有一个API密钥,你不想让API密钥在命令行中可见。

        • 那么,你最初是如何设置API密钥的?:) 这个论点完全没有意义。

          • 使用read或等效命令,大概是这样。仅仅因为你不知道为什么某种做法被不推荐,并不意味着没有更好的替代方案。

          • 我想是在某个.profile或.envrc之类的文件中设置吧。

            • 你期待有人能读取你的bash_history,却不能读取你的.profile?

      • Typer有一个很棒的功能,允许你可选地从环境变量中接受参数和标志值,只需提供环境变量名称:

        https://typer.tiangolo.com/tutorial/arguments/envvar/

        这对于存储密钥特别有用。两全其美 🙂

        • 不,这是个反功能。:) 这里的其他评论声称命令行参数会“泄露”,而环境变量不会。这是完全错误的。一个能够访问任意进程命令行的攻击者,当然也能访问其环境变量。将密钥存储在文件中,而不是环境变量中。现在你可以通过将 –secret-file 参数指向不同文件来轻松更改密钥。人们使用 BLABLA_API_KEY 变量唯一的原因是 Heroku 或类似服务在过去这样做过,而 everyone 盲目效仿了这个糟糕的模式。

          可以写一篇长篇大论来阐述环境变量的一切问题。避之如瘟疫。它们是巨大的可用性噩梦。

          • 这是糟糕的建议。请不要在缺乏依据的情况下对安全性做出断言。

            环境变量比纯文本文件安全得多,因为它们不具有持久性。有工具可以将密钥输入到环境变量中,而不会泄露到你的 shell 历史中。

            不过,你通常也不应该使用环境变量。你应该使用 shell 创建的临时文件,并传递相关的文件描述符。大多数 shell 都提供了此类功能,但细节有所不同(即据我所知没有完全可移植的方法)。

            另一种常见情况是,你允许密钥以明文形式存储在磁盘上,但不希望其意外提交到仓库。在这种情况下,你可以按照你提议的那样使用专用文件,或者从~/.bashrc等文件中设置环境变量。

    • 更好的做法是先检查两者

          if not API_KEY and not CHANNEL_ID:
              print(“缺少 YOUTUBE_API_KEY 和 YOUTUBE_CHANNEL_ID。”
              exit(1)
          if not API_KEY:
              print(“缺少 YOUTUBE_API_KEY。”
              exit(1)
          if not CHANNEL_ID:
              print(“缺少 YOUTUBE_CHANNEL_ID。”
              exit(1)
      

      这样你就不会在修复一个问题后,又被告知缺少另一个要求

      • 更好的做法是只检查每个变量一次,并缓冲决策:

            valid = True
            if not API_KEY:
                print(“缺少 YOUTUBE_API_KEY。”
                valid = False
            if not CHANNEL_ID:
                print(“缺少 YOUTUBE_CHANNEL_ID。”
                valid = False
            if not valid:
                exit(1)
        

        这样你只需检查每个值一次(因为你的逻辑可能比单纯检查值是否未设置更复杂,比如值可能格式错误),同时仍能执行任何你想要的逻辑。这同时也消除了组合问题。

        这是将决策与执行分离的一个相当普遍的原则。

    • 从环境中获取像频道 ID 这样的临时参数,对可观察性和用户体验更为不利

    • 或者

        API_KEY = os.environ.get(“YOUTUBE_API_KEY”)
        CHANNEL_ID = os.environ.get(“YOUTUBE_CHANNEL_ID”)
      
        assert(API_KEY, “缺少 YOUTUBE_API_KEY”)
        assert(CHANNEL_ID, “缺少 CHANNEL_ID”)
      
      • 使用 assert 时需要加括号吗?在 Python 中,assert 语句不是函数,对吧?

        之前我就是因为这个被坑了……它生成了一个值为 true 的元组。

      • 通过 `python -O` 可以禁用断言,所以可能不应该这样使用。

        • 另一方面,如果这些值未设置,程序最终可能会因权限/请求错误而崩溃

          • 这无关紧要。断言应用于始终为真的情况(除非程序存在 bug)。这里并非如此。

    • 我惊讶于没有类似 argparse 的工具来记录预期环境变量。

    • 最好两者都做:打印出详细的字符串或字符串,然后如果其中任何一个被打印出来就退出。

    • 我通常会遍历一组必须定义的设置,并列出任何缺失的设置,以避免用户/部署者重复尝试。

    • 我认为在做出这些决策时,用例和受众群体很重要。在这种情况下,用户很可能是通过控制台运行 Python 脚本的人(我假设是通过 print 命令),因此我认为这并不重要——用户会检查这两个设置是否已配置。是否还应为他们提供关于设置环境变量的文档?是否应根据用户运行的操作系统定制该文档?等等。

      如果用户是使用典型消费者界面的普通消费者,那么确实需要提供更多指导。

    • 为什么使用print然后退出(1)而不是引发异常?

      • 哦,我知道这个:

          $ python3 -c “print(‘clear messaging’); exit(1)”
          clear messaging
        
          $ python3 -c “raise ValueError(‘text that matters’)”
          Traceback (most recent call last):
            File “<string>”, line 1, in <module>
          ValueError: text that matters
        

        而当某些程序在“辅助”模块内部引发异常时,情况会变得糟糕得多,你最终会得到8行Python垃圾代码,而实际的信号只有一行

    • [已删除]

      • 我希望你永远被困在调试“错误发生”而没有跟踪信息,并且“x、y 或 z”永远缺失。

        • 我最喜欢的错误信息是iverilog遇到问题后简单地打印:我放弃了

      • 如果认为这种PR评论值得负面评价,那我庆幸自己不用和你合作。

        • 我本可以直接回复“哎呀,出错了。”

          如果它还没有被标记的话。

        • 有趣的是,这类声明能获得赞同而不被标记,但我的“Lmao”却被标记了。看来我应该更毒舌一点。我以为这比“Lmao”更糟糕,但嘿,HN知道得最好。

          (“Lmao”没用,但绝对不如某些其他回复糟糕。)

      • 你一定是个令人永远头疼的菜鸟。

      • 在我的HN上分享一些有用的编程技巧?

  2. > 我希望有一个工具能为我生成项目结构,但目前还没找到适合我的。

    我推荐使用cookiecutter。我用它创建了几个常用的模板:

    python-lib: https://github.com/simonw/python-lib

    click-app: https://github.com/simonw/click-app

    datasette-plugin:https://github.com/simonw/datasette-plugin

    llm-plugin:https://github.com/simonw/llm-plugin

    你可以这样运行它们:

      uvx cookiecutter gh:simonw/python-lib
    
    • 我对这个的实现(使用 Ruby)是 https://github.com/coezbek/baker

      它不会复制模板仓库,而是创建一个需要执行的命令列表。步骤可以是手动(获取 API 密钥并将其存储在此处)和自动(运行 ‘uv init’)。支持 Markdown 语法、Ruby 字符串插值和 Bash 命令。

      这源于对基于 YML 的配置文件的深深厌恶。

    • Copier 是当前最热门的工具

      https://copier.readthedocs.io/en/stable/

    • 我为 cookiecutter 制作了一个小工具,用于处理一些基本的 Git 集成(创建仓库并进行首次提交),并更好地适应我的工作流程(先编写一些初始代码,然后将其转换为“项目文件夹”):https://github.com/zahlman/cookiebaker

      不过,我并不太喜欢 cookiecutter 的美学原则。我认为它为这样一个简单任务选择了过于臃肿的依赖项。PyYAML 包含大量编译后的 C 代码,而性能其实并不重要,我更倾向于使用 TOML。我尚未见过依赖 Rich 的项目会使用其超过极小部分的功能;这个项目也不例外。更重要的是,Rich引入了体积庞大的Pygments(用于语法高亮;其中大部分是各种编程语言的实际语法定义规则),而你无法禁用它。说实话,我也不喜欢整个“使用集成客户端下载他人模板”的模式;Requests及其依赖项也相当臃肿。

    • 我是不是唯一一个喜欢手动设置新项目的人?我不想自动化这个过程。

      • 这取决于你的工作性质……

        如果你在代理机构工作或作为自由职业者,需要构建多个类似的应用程序,且使用相似的工具和基础设置,那么能够快速搭建框架并自动完成配置,避免手动操作浪费数小时,确实非常重要。同样,如果你在开发各种小型开源包,你希望工具保持一致,README文件格式一致等,一个脚本或工具来“生成”基本结构会很方便。

        另一方面,如果你在搭建应用或大型开源包,并且你将只专注于这个项目数年,那么单独、有机地搭建项目就很有意义。

    • 我查看了click-app仓库。如果你今天创建这个项目,你会从uv切换到pip吗?

      运行cookiecutter时,你仍然使用pipx,还是已经切换到uv tool install

      • 我快要切换到uv来管理我的模板了。

        我自己已经使用“uvx cookiecutter”三天了。

    • 这种东西似乎已经成熟,可以融入代理型大语言模型(LLM)开发工作流程了,不是吗?

    • 嗯,从未听说过这个。谢谢你的推荐。

    • 说实话,现在直接让AI为你生成一个就行了。

  3. > 不仅因为语法更人性化,还因为Python解释器在所有Unix发行版中都原生集成

    这评价有点过于乐观——只要超出“import json”的范围,很可能就会陷入虚拟环境的泥潭。在 Ubuntu 22.04 或甚至 24.04(LTS 版本)/Rocky 9 上运行使用 Python 3.13.x 创建的程序,整个问题就来了。

    虚拟环境 + 容器(类似 Docker)/版本管理器很快就会成为必需品。

    • “import json”这类操作需要在不包含库的语言中手动选择并安装库,而它只是标准库中众多模块中的一个。这并非大型项目的有力依据,但多年来我已交付了大量无需超出标准库范围的实用生产代码,因此从未花时间考虑部署或安全补丁问题。

      此外,现在已经不是2000年代了。使用venv来隔离应用程序安装已经不再困难,而且多年来一直有不错的包管理器。

      • 官方包管理器是pip,它有问题,而且每年都有一个新的“永久解决方案”来替代它。

        • “有问题”是夸张的说法。它每天都能为数百万用户正常工作。如果你有特定场景希望它改进,最好直接说明需求而非仅仅抱怨开源项目。

          • 数百万用户将其部署到 Docker 中,或直接忍受其缺陷,结果就是大量 Stackoverflow 问题涌现

            • > 数百万用户将其部署到 Docker 中,或直接忍受其缺陷,结果就是大量 Stackoverflow 问题涌现

              傲慢且错误的观点。

              我用Python编程已有近20年。其中许多年,它都是我工作中主要使用的语言。

              2024年是我第一次真正需要使用虚拟环境。在此之前,我一直乐于使用pip安装所需的任何软件包,从未遇到过因版本冲突导致的问题。

              我经常遇到一些初级开发者,他们默认使用虚拟环境,不是因为他们真的需要,而是因为他们被灌输了一种观念,认为“如果你不使用虚拟环境,你就做错了”。

              无论如何,现在人们应该使用uv。

              • 有些包是由非工程领域的科研人员编写的。有时你不得不使用虚拟机(VM)。

                • 我并不否认有些场景确实需要虚拟机或虚拟环境(virtualenv)。它们的存在自有其道理。

              • 我指的是pip,不是venv。我也不使用venv,不是因为我觉得这是个坏主意,而是因为我懒得去弄。除非使用Docker(笑)或uv,否则确实会出现冲突。

                • 如果你使用uv,你就等于在使用虚拟环境。你只是被免除了几分钟关于它们如何工作的教育(https://chriswarrick.com/blog/2018/09/04/python-virtual-envi…)。

                  • 嗯,我也不用处理 venv 了,uv 会帮我搞定。我猜这就是它的运作方式,因为我不知道还有其他方法。

                  • 不知为何,复制 venv 目录会导致内部链接出现问题。真是有趣。

                • 这解释了为什么你认为 pip 有问题

                  • 因为我按照官方 pip 的“入门”文档操作了?即使在 venv 中也存在问题,比如不管理 requirements.txt 或安装冲突的包。网上有很多关于这个问题的问题,答案通常涉及安装其他东西。

                    • pip freeze

                      但确实,pip 诞生于包管理器尚未普及的时代,当时用户需要自行管理依赖文件,而非由包管理器代劳

                • 正如另一位评论者所指出的:你对该主题的了解显然不足。

                  在这种情况下保持沉默是明智的。

                  • 哦,是的,pip实际上是正确的,而大家都在错误地使用它

              • >2024年是我第一次真正需要虚拟环境。在此之前,我一直乐于使用pip安装任何我想要的软件,从未遇到过因版本冲突导致的问题。

                好吧,我来问:当你需要同时处理两个需要不同版本同一依赖项的项目时,你是如何处理的?

        • 帖子的其余部分表明,你期望一个“包管理器”能做比Pip更多的事情。

          Pip 专注于安装软件包,而不是真正地“管理”它们。问题是,对于“管理”应该包括什么,人们有相当多的不同看法。这就是所有替代方案的来源。

          • 无论你怎么称呼它,归根结底,在 Python 中遇到依赖问题比在 NodeJS、Rust 等中更常见。

            此外,忽略Pip本不该做的事情,比如版本管理,只专注于安装,Pip默认将包安装到某个不明确的系统位置,这始终令人困惑,尤其是在Py2与Py3并存的时代。

        • 我不明白这里对Python的厌恶。

          Pip没问题。它至少在过去5到10年里一直没问题。

          • 我并不讨厌 Python,我每天都是主动选择使用它。这只是一个缺点。

        • Poetry 已经很好用了多年。uv 是新的但很棒,并且很可能继续保持下去。

          • uv 比 Poetry 快得多,尤其是在依赖项解析方面。

            • 我想尝试 uv,但我不觉得 Poetry 在依赖项解析时间上有多糟糕。如果每周只需等待 2 分钟,那也不是世界末日。也许如果你一直在安装难以解析的新东西,那是个问题。

              • 这不算世界末日,但确实令人烦躁。我之前在一家公司工作,那里有一个巨大的单仓库项目,依赖项繁多。Poetry 需要花 4 到 5 分钟来处理。对于小型项目,我同意这不算大问题。但 uv 还有其他酷炫功能,而且速度极快!

              • 这不算世界末日,但为什么等2分钟,而可以只等2秒?

            • 这就是我想说的,不久前人们还在说“直接用Poetry”。它和uv一样,也有自己的锁文件格式。

            • conda 速度有提升吗?

      • 我不是在争论用 venv 隔离安装,我是说依赖来自类 UNIX 操作系统的 Python 对于至少与网络相关的项目来说几乎不可能。例如,Ansible 在生成代码时做了很多工作,以确保与系统上可能存在的任何 Python 3 版本兼容 (远程主机)

    • 我有一个半开玩笑的理论,认为如果 Docker/容器没有如此出色地解决 Python 依赖地狱的问题,它可能不会发展得如此迅速。当你发现只有通过复杂的 chroot 环境才能以一种人性化的方式部署一个可工作的系统时,你就知道问题有多严重了。

      我第一次接触Python是在2012年左右,当时作为系统管理员在服务器上安装一个用Python编写的服务。依赖地狱、愚蠢的venv命令,为了让一个该死的Web服务器运行起来,简直是折磨。天啊,这让我对Python失去了兴趣,长达十多年。几乎每次看到它,我都会转身离开,不感兴趣,谢谢。那些我没有转身离开的时候,我又重新陷入了那堆烂摊子,并记起了为什么我通常会避开它。在macOS上,brew处理依赖的方式也令人极其沮丧,它破坏了基本的pip安装命令,将库安装为命令,但以一种使它们无法被其他Python脚本访问的方式,真是个该死的灾难。

      而且,我真的不知道自己在说什么,因为作为一个初学者,这简直愚蠢至极,令人困惑,所以我只能转向更具生产力和愉快的任务,心里想着“也许当Python理清头绪时,我会重新审视它”。

      然而,uv至少在我这个初学者和愤世嫉俗的眼中,已经清除了大部分垃圾。至少从表面上看,在我开始用 Python 做的这些小玩具项目中(正是因为它提供了一种更愉快的体验),它清除了大部分令人讨厌的垃圾。`uv init`,`uv add`,`uv run`。它就是这么简单地工作*。

      • > 我有一个半开玩笑的理论,认为如果Docker/容器没有如此出色地解决Python依赖地狱的问题,它就不会发展得如此迅速。

        我认为这根本不是一个愚蠢的理论。唯一可能愚蠢的部分是,容器专门帮助解决了Python的这个问题。用其他语言构建的许多其他软件系统也存在“依赖地狱”。

        • 在 Redhat 的早期,RPM 并没有很好的依赖管理。虽然有 RPM 包,虽然可以下载它们,但获取完整的依赖树是一件很麻烦的事。大多数人选择安装完整的 Linux 发行版而不是轻量级版本,就是因为这个原因。

          Debian 的 apt-get 在当时推出时非常“apt”。它彻底解决了Debian的依赖问题。曾有一段时间,Red Hat也有过apt-rpm工具。Yum试图为Red Hat解决这个问题,但效果并不理想——尤其是当需要将包固定在特定版本时。

      • 我也不会碰Python,因为我曾在调试大型Python程序时吃过亏。在静态类型语言中只需一分钟就能完成的任务,却需要花数小时追踪程序中的数据才能弄清楚字典中应该包含什么。存在一些简洁、静态类型、能快速编写程序且能发展成可维护的大型代码库的替代语言;因此,今天没有理由用 Python 启动新项目。

        • 我在.NET和Java中也见过类似情况,那里有800层接口和实现,试图在所有间接层中找到实际业务逻辑简直是一场冒险

          >因此,今天没有理由用Python启动新项目

          没有其他语言拥有可比拟的机器学习/数据生态系统。Perl/Go或许是遥遥领先的第二名

          • 我经常遇到这种情况,因为相邻团队使用Java时伴随大量冗余代码和框架。此时静态类型已不再那么静态。他们修改代码需要耗费大量时间,以至于我开始用Python代码接手相关职责。

        • 在大规模项目中,Python通常会使用类型(TypedDict)和/或Pydantic来确保安全,绝不会直接使用普通的字典对象。这是任何语言中的代码臭味,我们可以整天将数据塞入map[string]interface{}中,从而在下游引发问题。

        • 如果作者选择不做可怕的事情,那没问题。如果你想要类型,也有类型可用,但我不要。

      • > 然而,uv 至少在我这个初学者和怀疑论者的眼中,已经清除了大部分垃圾。

        uv 比以前的版本好得多。作为一个在职业生涯中只与 Python 有过短暂接触的人(在工作中与 conda 和 pip 有过糟糕的经历),我觉得 uv 就像 Python 试图真正进入 21 世纪的软件包管理领域一样。它采用 Rust 编写,显然是从 Cargo 获得灵感,而 Cargo 则是从 Ruby 和 Bundler 获得灵感。

        • 对我来说最有趣的是,你可以用 uv 在 mise 中以一种令人惊讶的优雅方式安装 Python 命令行程序。

          • 只是好奇:怎么操作?

            • 你可以通过各种 mise 命令实现,但最简单的方法是使用一个类似于下面的 mise.toml 文件

                [tools]
                python = latest
                uv = latest
                “pipx:yt-dlp” = latest
              
                [settings]
              
                [settings.pipx]
                uvx = true
              
                [settings.python]
                uv_venv_auto = true
              

              这些都是 mise 的 [pipx 后端] 的功能,文档中讨论了在处理 Python 更新等情况时应如何操作。采用这种方式的优势,尤其是对于全局 mise 配置而言,在于您可以将这些 Python 工具视为与其他 mise 工具无异的普通工具,因此其版本控制变得更加简单。

              我明白 mise 严格来说并非包管理器。但凭借其对 Python、Ruby、npm 或 Cargo 等工具的支持,以及来自 Ubi 和即将推出的 GitHub 后端等更广泛的支持,它正迅速成为我最喜爱的包管理器。我有几个项目使用了特别有用的基于 Node 的工具,如 markdown-lint 或 prettier,这些工具与 JavaScript 毫无关联。携带一个 package.json 文件感觉很奇怪,而 mise 处理所有这些的方式,现在我不用再这样做了

              [pipx 后端]: https://mise.jdx.dev/dev-tools/backends/pipx.html

            • 他们可能指的是 uvx:https://docs.astral.sh/uv/guides/tools/

              也可能是其他内容,不确定。

            • 在mise.toml中我写的是:

              “` [tools] python = “3.12.11” ruff = “latest” “`

              这样就能顺利安装ruff和其他所需工具,无需额外操作。

            • `uv tool install` 我认为

      • > 当你发现只有通过复杂的 chroot 环境才能以一种人性化的方式部署一个能正常工作的东西时,你就知道事情不对劲了。

        每种语言似乎都存在这个问题。否则我们如何解释 AppImage、Flatpak、Snap 等的普及?

      • 是的,如果你把 Python、Node 和 Rust 这三个大型存储库放在一起比较,你会发现 Python 往往是唯一一个有 Dockerfile 的。

      • 基本上,这涉及C语言静态链接与动态链接的区别。这是一个与编程历史同样悠久的权衡问题。

    • 你应该始终使用虚拟环境。它们只是一个目录,怎么会是深渊呢?Pip现在会抱怨如果你试图全局安装包。

      • 对于影子 IT,任何需要“安装”而非直接包含在基础系统中或作为文件添加到项目中的操作都是一种麻烦。

        这就是我喜欢使用 Bottle 作为小型 Python 前端的原因:下载文件并导入即可。

        (我基于过去与 IT 部门的个人经历进行吐槽。是的,总体而言虚拟环境是基本标准)

        • 将Python编译为可执行文件始终是一个选项。

          • 如果你在处理受管系统,很可能编译器是被禁止的。你将不得不冒险在受限环境外工作——这可能违反政策、合同、法规和法律。

      • > 你应该始终使用虚拟环境。

        如果你不使用依赖项,并且在编写 3.x 代码,那么几乎没有理由不使用虚拟环境。

        • 不使用依赖项的情况相当小众。大多数人选择 Python 就是因为想使用现有的库。

      • 在 JavaScript 或 Go 等其他语言中,我无需处理此问题。我只需提供一个包含所有版本信息的 package.json 文件。npm install 会在本地设置依赖项,但无需复制整个 Node.js 运行时环境。

        • … node_modules 就是你的虚拟环境。

          如果我们使用 TFA 中的 uv,命令几乎是 1:1 对应的:

            npm install     <=> uv sync
            npm install foo <=> uv add foo
          

          > 它不需要也存储 NodeJS 的本地副本

          … 这是 Node 开发者也喜欢的东西,叫做 nvm,用 bash 编写

          • 区别在于它是默认的,它在任何地方都以相同的方式工作,它实际上会记录依赖项,我无需手动在它们之间切换(或设置复杂的 bashrc 触发器)像 venv 一样。

            • > 它是默认的

              这一点是正确的;生态系统不可能一夜之间改变。uv正在朝这个方向发展,我希望如此。

              > 它在任何地方都以相同的方式工作

              `uv`在任何地方都以相同的方式工作?

              > 它实际上会记录依赖项

              `uv add`也会这样做。

              > 我无需手动在它们之间切换(或设置复杂的 bashrc 触发器)就像 venvs 一样.

              使用 `uv` 也不需要这样做?

              • 希望他们能让 uv 成为默认选项。它很不错,但需要单独创建项目才能使用,且常规 Python 命令无法与之兼容,这两点都源于它不是默认选项。但即使这样也无法解决所有旧项目的问题。

                • Node.js 在 JS 项目中也不是默认选项,它只是目前最流行的管理工具。旧 JS 项目自有其独特挑战。

                  • 基本上现在就是这样。这是前端 JavaScript 的近期趋势,但 NodeJS(与 Python 更直接可比)从一开始就有了 npm。

                    不过通过 <script> 标签安装的浏览器 JavaScript 库,说实话在某种程度上还是挺方便的。

    • 是的,请使用虚拟环境或容器。我知道这看起来似乎过于繁琐且难以管理,但你可不希望陷入一种不敢更新系统的境地,因为库的升级可能会破坏你的部分 Python 代码。

    • +1 – 我对在软件交付过程中遇到的诸多兼容性问题感到震惊,即使是在相对受限的同事环境中也是如此。

      不同发行版版本之间的细微差异可能产生重大影响,而并非所有使用 Python 脚本的人都懂得如何使用 pyenv 等工具来管理不同版本。

    • 在某些较旧的案例中,预装的 Python 是 v2 版本,系统依赖于它,这反而成了一个隐患。

    • 直接使用 uv

      • 我同意——没有 uv 的 Python 简直是自虐。但这实际上否定了“Python 已经可用”的优势。如果你必须安装 uv,那么你也可以轻松安装 Rust、Go 或 Deno。

    • uv 可以解决这个问题

      • > 虚拟环境之类的东西

        我认为我的观点在 UV 中仍然有效,你想表达什么?

        关于UV的具体问题——假设’asdf’能从官方源代码直接在你的系统上编译Python,这意味着使用你的SSL库等。UV提供Python二进制文件,我对此感到担忧。

        • UV与任何你想要使用的Python版本都兼容——你无需使用UV能够在你的机器上安装的Python版本。

      • UV 确实正在努力弥补 Python 世界中数十年的罪孽和血腥历史,并且在赎罪方面做得很好。

    • 更像是蛇窝而不是一罐蠕虫。

    • 我同意内置的 Python 通常不适合开发,尤其是如果你计划分发你的代码并/或关心其依赖项的版本。(尤其是在 Debian 及其衍生版本中,根据我的经验;他们甚至会移除标准库的一部分,如 Tkinter [0])。

      我不同意虚拟环境代表一个“深渊”。学习它们的工作原理只需要很少的努力[1],而且有各种工具可以以各种方式包装这个过程[2]。环境本身是一个非常简单的概念,只需要很少的组件;默认实现中包含的一些便利功能实际上是没有必要的。

      特别是,你实际上不需要“激活”虚拟环境;在99%的情况下,你可以通过显式指定环境中Python的路径来运行Python,而在极少数情况下,代码依赖于环境变量被设置(例如,因为它做了一些像`subprocess.call([‘python’, ‘foo.py’])`来在新进程中运行更多代码,而不是像应该的那样检查`sys.executable`,或者因为它明确检查`VIRTUAL_ENV`因为它有理由关心激活),那么你可以自己设置这些环境变量。

      创建虚拟环境实际上非常快速。内置的 venv 标准库模块在我的测试中实际上比等效的 uv 命令更快。缓慢的部分是从其自身的轮子中初始化 Pip——但你不需要这样做 [2])。你只需告诉 `venv` 不要这样做,使用 `–without-pip`,然后你可以使用一个独立的 Pip(对于最近的版本——几乎是过去三年的版本)跨环境使用 `–python`(这是一个技巧,但如果你不需要维护任何版本的终止支持版本,它就有效)。如果你需要更强大的支持,还有第三方工具 `virtualenv` [3]。

      管理虚拟环境的许多相同工具——特别是 pipx 和 uv,以及希望在不久的将来推出的 PAPER [4]——还可以在临时虚拟环境中运行一次性脚本,并根据脚本中描述的依赖项安装依赖项,遵循新的生态系统标准 [5])。uv 的缓存系统(当然我也遵循这一设计)使得重新创建具有常见依赖项的虚拟环境非常快速:它缓存了解压后的 wheel 包内容,因此几乎所有工作只是将文件树硬链接到新环境中。

      [0]: https://stackoverflow.com/questions/76105218

      [1]: https://chriswarrick.com/blog/2018/09/04/python-virtual-envi…

      [2]: https://zahlman.github.io/posts/2025/01/07/python-packaging-…

      [3]: https://virtualenv.pypa.io/

      [4]: https://github.com/zahlman/paper

      [5]: https://peps.python.org/pep-0723

      • 我指导过的许多人在寻求帮助后,对这些建议都会感到震惊。

        激活一个虚拟环境至少是他们能理解的事情。

    • 不,不。我不知道你是怎么做到让自己变得这么困难的。

      假设你想使用一个在Ubuntu上不可用的特定版本的Python。

      1. 安装构建依赖项 https://devguide.python.org/getting-started/setup-building/#…

      1. 下载你想要的 Python 源代码版本,https://www.python.org/downloads/source/。使用 tar 解压它

      2. 运行 ./configure –enable-optimizations –with-lto

      3. 运行 make -s -j [核心数]

      4. sudo make altinstall

      这将安装该特定版本,而不会覆盖系统默认的 Python。

      您可以通过将 pip 别名设置为 python3.xx -m pip,确保其运行正确版本。

      所有库和通过 pip 安装的可执行文件都将安装在特定 Python 版本下的 ~/.local 文件夹中。

      或者,如果您使用其他工具(如 Node)并希望管理不同版本,可以使用 asdf,因为它支持按文件夹选择版本。

      虚拟环境主要用于生产代码,您希望使用特定版本进行测试并锁定这些版本。

      • 虚拟环境用于隔离不同项目的依赖项,而不仅仅是隔离解释器。

        (我的意思是,除了在 Windows 上,你的 venvs 默认会链接解释器和其他共享组件,所以你实际上并没有隔离解释器,只是隔离了依赖项。)

        • 据我所知,即使在 POSIX 系统上也是如此,当你创建一个 venv 时,“./bin/python” 是符号链接,而 pyvenv.cfg 文件中硬编码了当前 Python 解释器的绝对路径,这是 venv 模块在创建时使用的。它实际上并没有隔离解释器。

          (这也是为什么,如果你手动调用 venv,你绝对需要从正确的 Python 作为模块调用它(python3.13 -m venv),以确保你实际上选择了 venv 的“正确 Python”)

          • 我的意思是,符号链接行为在 Windows 以外的系统上是默认的(尽管它可能仅限于 POSIX 平台,而我将其视为“除 Windows 以外”是因为我个人从未在非 Windows 非 POSIX 平台上遇到过 Python。)

      • > 自己给自己增加难度

        仅看第一个链接,看起来比 venv 复杂得多。而我是一名 C++ 开发者,想象一下那些经验不足或甚至不熟悉 C 工具链的人。

        • 其实并不难;你回复的人提供了很多细节。我已经记不清自己构建过多少次 Python 解释器了。虚拟环境过去与某些工具和依赖项配合得不好,所以我不得不经常这样做。现在情况好多了,我只编译Python以获得Linux发行版未提供的版本。

        • 第一个链接是一个sudo apt install命令,你只需将其复制粘贴到终端中。在什么情况下这比venv更复杂?

          为了清晰起见,这里是命令:

          sudo apt-get install build-essential gdb lcov pkg-config libbz2-dev libffi-dev libgdbm-dev libgdbm-compat-dev liblzma-dev libncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev lzma lzma-dev tk-dev uuid-dev zlib1g -dev libmpdec-dev libzstd-dev

          • 你刚刚粘贴的内容本身就有问题。如果使用它,会导致令人困惑的错误。尝试执行“sudo apt install vim make”来验证。

            这是经验丰富的工程师不会遇到太多困难的事情,但你应该能够意识到编译复杂的C代码库需要多少经验知识,以及可能出现哪些愚蠢的错误。

            你可能不需要做页面上大部分内容来构建,但“什么是dnf?”、“预提交钩子重要吗?” “是否需要cpython?”“什么是ABI转储?”这些问题是许多人在阅读时会遇到的困惑。

          • >第一个链接是一个sudo apt install命令,你只需将其复制粘贴到终端中。在什么情况下这比venv更复杂?

            venv本身也不复杂。

            我之前多次从源代码构建过 Python。我这样做是为了测试我的代码与 Python 版本的兼容性,调查性能特征等。

            重新构建相同的 Python 版本,仅仅是为了支持一个使用相同版本但依赖关系不同的独立项目,这是一种巨大的时间和磁盘空间浪费(每次安装占用数百 MB,加上大部分共享的开发依赖项)。只需创建虚拟环境。它们并不难理解。希望有一个工具来帮助他们理解这一点的人,可以选择占用较少磁盘空间(约35MB)的uv。pip的完整安装包大小为10-15MB,安装时间可能只需几秒钟;通常只需安装一份,但避免重复安装确实需要一些操作。

      • 个人偏好,但我现在更倾向于使用’mise’而不是’asdf’:https://mise.jdx.dev/

  4. 也许我只是觉得Python既冗长又缺乏效率?要么需要500个依赖项才能以简单的方式完成某件事,要么需要几十行(甚至上百行)代码来处理琐碎任务。我尽量避免编写Python代码,因为需要添加太多无用内容。我更偏好Perl,因为可以用它快速完成任务。Python感觉像是为了编程而编程。

    • 我有很多零依赖的项目。你可以用标准库和单文件项目完成很多事情,这些项目可以在大多数系统上运行,无需做任何事情,只需下载并运行,因为Python是可用的。

      例如,这是一个约2000行代码的Python项目,是一个命令行财务收入和支出追踪器:https://github.com/nickjj/plutus/blob/main/src/plutus

      它使用了大约十几个标准库模块。

      大约25%的代码使用argparse解析命令和标志。不过,我更倾向于为了清晰度而让代码行数更多。例如,这段代码理论上可以缩减到1或2行,但我喜欢每个参数都单独一行。

          parser_edit.add_argument(
              “-s”,
              “--sort”,
              default=False,
              action="store_true",
              help="排序您的配置文件并在有更改时显示差异",
          )
      
      • Argparse 是我对 Python 的问题之一!我总是记不住它收集所有这些属性和功能的奇怪方式,所以我总是翻阅手册页面或在网上搜索示例。同样,如果我修改现有的 Argparse 代码,我不知道如何让它做我想做的事情。它可以为你做很多事情,但使用起来很麻烦。

        编写一个简单的 usage() 函数,将所有帮助文本放在一个大块中,并使用 Getopt 带 case 块,要简单得多。任何人都可以直接查看并编辑它,无需查阅文档。

        • 参数可能会变得相当复杂。

          Argparse 的一个优点是它提供了一个辅助函数,用于处理可选或必填的互斥组。例如,你可以使用 –hello 或 –world,但不能同时使用两者。

    • > 我更喜欢 Perl,因为我能快速完成任务

      Python 允许你直接嵌套数据结构,无需费脑。你想在字典值中嵌套列表中的元组:只需直接写出来,并使用统一的语法访问。一气呵成。无需担心引用混乱和上下文问题。这是我通常需要快速完成任务并五年后仍能理解自己如何实现的关键。这一定有其价值。Python 在这方面很无聊,Perl 很有趣。但这正是我对它的困扰。它太过聪明反被聪明误,编写它会对你的大脑造成影响(至少对我来说是这样)。

      • 这就是我放弃 Perl 的原因。我喜欢它,对 Python 持怀疑态度,但有一次在尝试了大约一周后,我好奇地想知道将一个包含列表的字典传递给函数,然后引用其中的项会是什么样子。我的第一次尝试成功了:我创建了一个字典,其中包含一个列表,而该列表中又包含一个字典,然后将其作为参数传递,无需任何符号或引用装饰器等。

        那大概就是我停止使用 Perl 进行新项目的时候。我再也不想回去用了。

        • 这真是个出人意料的简洁观点。这可能实际上影响了 Perl 的衰落,除了 Perl 的破坏性更改。

          PowerShell 也会对嵌套数组感到非常反感。Bash 也是如此。

      • Perl也允许嵌套数据结构,无需任何脑力激荡。Perl本身不支持元组,但可以通过模块添加。你也可以在复杂数据结构中嵌套对象。

          perl <<‘EOF’
          my $object = bless sub { my ($s) = @_; if ($s eq “thingy”) { return(“hello world!”) } else { return(“goodbye world!”) } };
          my %hash1  = ( baz => “here we go”, foo => $object );
          my @array1 = ( undef, undef, undef, %hash1 );
          my %hash2  = ( bar => @array1 );
          my $reference = %hash2;
        
          print( $reference->{bar}->[3]->{baz}, “n” );
          print( $reference->{bar}->[3]->{foo}->(‘thingy’) , “n” );
          print( $reference->{bar}->[3]->{foo}->(‘something’) , “n” );
          EOF
          here we go
          hello world!
          goodbye world!
        

        Perl 就是 Perl,TMTOWTDI,但你可以通过 linters 和 formatters 来强制执行自己的风格。

      • 字典在 Python 中如此有用,但这也带来了一个主要缺点……每个人都在到处使用字典。我非常高兴有 dataclasses 和 attr,因为不得不检查字符串键是否正确,而不用那些丑陋的常量,真是令人痛苦。

    • 该死的后端还在泄露2007年的旧评论

    • 因人而异,但根据我的经验,Python 的主要应用场景通常需要更少的依赖项,而不是更多。许多简单任务已经内置于语言中。我不是说 Python 是完美的,但它在这一点上确实做得很好。你遇到过哪些应用场景是相反的情况?

      我认为经典的 Python 与 Perl 之争最终归结于使用你最熟悉的工具。人们各不相同,这完全没问题。

    • 你能分享一个 Perl 更快速且更强大的使用示例吗?

      • 将其用作 Bash 的替代品。无需环境配置,Perl 已预装在地球上的每台计算机上。Perl 在处理以字符串为中心的流程时通常更快速——而这正是 Bash 的领域。正则表达式快速且易于使用。

        当需要结构化处理或即使是小型项目时,Perl 就会显得力不从心。对于那些原本用 Bash 完成的短脚本,它堪称救星。更安全、更便携、摩擦更小。

        • Perl 在需要结构时不会崩溃,它是面向对象的。事实上,它的结构比 Python 更好(你好,PyPI?这是 CPAN 在呼唤,我们想让你知道分层包是一个东西)。如果你指的是“强迫人们以一种有意见的方式编程”,它默认不会这样做,但这就是 linters 和格式化器的作用 (就像 Python 程序员使用的那些工具)

    • Python 是一种标准库足够大的语言,因此无需依赖其他库

      • 从技术上讲,C 或汇编语言也满足这一“要求”。

        然而,似乎很少有聪明人会在实践中认同这一点。

      • 我需要带注释和尾随逗号的 JSON

    • 当你说“我实际上能快速完成事情”时,这是否包括一年后再阅读同一个源代码?

    • Python 最棒的地方在于,它为一切都提供了库。

    • 你见过 Java 吗?

      • “只在我的网页浏览器中。”

        那真是美好的日子……

  5. 我很高兴有人也发现自己喜欢Python。

    我被迫为一个项目学习Python,当时我提议使用Ruby,但客户坚持要用Python。那是在几年前,当时Ruby的速度要慢得多。我当时很生气,但后来习惯了,现在多年后我反而享受它。

    我对“make”的描述和使用有异议!:-D 如果不使用依赖项,使用它的意义何在?还不如直接写一个带有case语句的脚本……我加了表情符号,因为我不是在开玩笑,但我真的认为,当今年轻人无法掌握Make,是我们的文化的一种悲哀……滚出我的草坪,好吗? 🙂

    • Ruby 的语法要优雅得多。我就是无法接受用空格来限定作用域的想法。

      • 我以前也这么想……但后来我接受了。

        我最终觉得这消除了在C类语言中关于大括号最佳风格的内部争论。

        • 这只是奇怪。也许如果你习惯了,它不会困扰你,但我发现很容易在作用域上出错。

      • 我希望Python能支持可选大括号。也许我该复制一些代码片段或将其他项目集成到我的项目中,然后担心别人是用2个空格、4个空格还是制表符。

        • 如果你担心这个问题,可以试试Ruff或类似的工具。

        • 是的,但他们似乎认为这是个功能,而不是障碍。我真的不明白这个想法。

      • Ron Garrett 注意到 Emacs 可以使用 “pass” 作为自动缩进的闭合大括号。

        天才。

      • 我喜欢它,没有大括号的噪音,更加简洁和干净。

    • > 干脆直接写个带case语句的脚本算了……

      我最初就是这么开始的,后来演变为简单的扁平化Makefile,因为它们本质上相同,但Make感觉更标准(这里有个Makefile,而不是随意的Bash脚本)。

  6. 对我来说,Python是最接近能正常运行的伪代码的语言。每次我在编写代码时有种想略过某个细节的冲动(因为在脑海中它显得显而易见),结果发现Python恰好提供了直观的抽象实现。

    作为数学背景出身的人,我曾觉得这非常令人满意,尽管后来我逐渐接受了其他语言。

    • 作为一个数学背景的人,我发现面向对象编程(OOP)的倾向让我难以理解。更好的方程推理、更好的lambda表达式、更少的副作用需要担心、避免变异以便我可以在运行时定义某物并仍然知道它是什么,这些都是帮助我清晰思考的因素。对我来说,OOP是我使用过的与数学最遥远的编程范式。

      • 面向对象编程,尤其是现代版本,感觉与几乎所有事物都相去甚远。

        继承本应保持神秘。组合更贴近现实。

        关系、函数和值与应用程序的关联远比面向对象编程更紧密。

      • 这与数学无关。要处理数学,就专注于代码行、函数和模块。OOP应用于抽象数据类型、框架和建模。

        • 你说得对,OOP不适合数学,但不幸的是,人工智能/机器学习已深深植根于该生态系统,且没有明确的替代方案。

      • 避免使用面向对象编程其实很简单。

        • 我使用Python的唯一原因是其专用的AI/ML框架,而这些框架迫使你采用面向对象编程。

  7. 我做的项目几乎都遵循相同的模式。这有点诡异。也许Python开发者生态系统中的人们正在趋向于以一种相当统一的方式做大多数事情?我以为我的一些选择可能是“我自己的”,但看到如此一致性让我质疑自己的自由意志。

    这就像当人们为他们的孩子选择一个“独特”的名字,而几乎每个人都选择了同样的名字。你以为是独特的名字,结果却是第二受欢迎的名字。

    • 这种架构在Python中至少流行了10年左右,但你说得对——这种结构本身就合理,所以许多理性的工程师都会采用它。

    • 仿佛每个频谱中的次表面导波都将人类的自尊心作为构成粒子——成为存在,哈哈

      • 正是冰淇淋的这一面,让不可能驱动器变得与众不同。

  8. 据我所知,Python最初被视为系统管理员的瑞士军刀。它在2004年Canonical将其作为Ubuntu 4.10的主要语言采用后,开始获得更多关注。

    2005年,Guido van Rossum被谷歌聘请参与谷歌云项目。这为Python在学术界的广泛采用打开了大门,因为Python拥有强大的数学库,并与研究人员已使用的工具(如Hadoop)高度兼容,恰逢大数据和机器学习开始兴起之际。

    此外,在2005年至2006年间,发生了两件重要事件:Ruby on Rails问世并启发了Django的诞生,后者开始崭露头角;同时,网页开发者对Perl凌乱的语法感到厌倦。正是这些因素促使Python迅速成为不仅适用于服务器端脚本,更可用于构建完整网页应用的可靠选择。与此同时,另一种可直接嵌入HTML的语言正席卷网络:PHP。其语法与JavaScript相似,易于上手,降低了软件开发的门槛,开箱即用,且无需通过成千上万的打印语句来完成任务。

    3P(Python、PHP、Perl)创造了历史。据20年前的程序员回忆,它们就像宗教一样。每种语言都有自己的哲学体系和忠实的追随者群体,他们在网上展开激烈的辩论,试图说服更多人采用自己的语言。新一代开发者更加务实。如今,语言之争已不再是重点,而是选择适合任务的工具。

    • > 据20年前的程序员们描述,这些语言就像宗教一样。每种语言都有自己的哲学体系,拥有一群忠实的追随者在网上积极宣传,展开激烈的辩论,都试图吸引更多的采用者。

      以这种方式描述自己亲身经历的事物,感觉非常奇怪,仿佛是由人类学家在描述。

      不禁好奇未来会如何评价今天。

      “2025年,程序员使用’框架’来挂载’应用程序’到某种’网络’上。他们认为这些框架赋予了他们魔法般的力量。许多人甚至为此展开‘火焰战’,专家认为这涉及用火来摧毁竞争程序员编织的网络。”

    • 我认为关键因素是特拉维斯·奥利弗特(Travis Oliphant)在2005年将竞争的numeric和numarray库合并为numpy。这迅速成为Python作为开源数值处理核心环境的基础。

      这吸引了大量来自R和Matlab的用户。

      随后,Pandas、Matplotlib和ScikitLearn进一步巩固了Python作为学术和商业机器学习首选平台的地位。

    • Python 的成功完全归功于入门级编程课程。所有人都转向了 Python,因为这样可以减少解释的步骤。我记得在 2012 年之前,我从未听说过 Python 中的网络服务器。我猜 2005 年的电脑可能无法流畅地运行 Python 后端。

      PHP的流行并非始于2005-2006年。它在90年代末就已流行,其外观与JS相似,也像土豆一样。

      • 这完全是倒置的。当Python在系统管理员、Django以及数值/数据科学用户推动下已极度流行后,他们才开始转向Python。

        • 我也有同样的观察。当Python在行业中普及后,高校才开始教授它。

          我来自基础科学背景,主修物理学。在我所在的机构,编程语言的演变是这样的:FORTRAN -> 大量C语言,少量FORTRAN -> C -> Python。我学习的是C语言,但从第二年开始,教学内容就切换到了Python生态系统。

          直到Python成为研究型大学的标准语言、近期研究论文的常用语言等之后,这一转变才真正完成。

          一代科学家在大学或研究生阶段学习了C/FORTRAN/MATLAB,因为当时就是这样教授的,但他们在职业生涯早期或中期转而使用Python。本科阶段教授Python则是随后才跟进的。

          我也曾教过一位职业中期经济学教授学习 Python。她之前在论文中使用 SPSS。人文科学教授在进行数据分析时也开始转向 Python,这一趋势非常明显。

      • 使用 Python,你只需输入:‘print “hello, world!”’,这就是一个完整的程序。

        运行时只需输入python hello.py

        与2005年Java相比,你需要做多少繁琐操作才能让程序运行。

        ActivePython的糟糕体验以及在Windows上运行Python的困难确实是个障碍,但总体而言仍比其他语言更简单

        • 你可以这样做,但3.x版本已经废弃了不带括号的形式 🙂

        • 当然,但在80年代和90年代,你也可以用Perl做到同样的事情。

          • 你甚至可能在编写正则表达式时不小心召唤出一位古神 😀

            Perl 是一门优秀的语言,但它就像使用没有安全装置的木屑粉碎机。要避免 ASCII 字符四处飞溅并制造出无法维护的只读混乱,需要极大的谨慎和专业知识。

            对于我见过的每一个美丽且易于维护的 Perl 程序(主要是 irssi 脚本),我见过 99 个令人作呕的程序,重新编写它们比试图理解它们在做什么更快。

            • 确实,确实。我记得当年用 Perl CGI 脚本编写完整的应用程序。我应该看看是否能找到一些那时的东西。

        • 我的意思是,其他语言也能写出同样简洁的脚本。Common LISP 就是一个大家喜欢拿来吐槽的例子。

      • > Python 的成功完全归功于入门级编程课程。

        不,入门课程最初是 Scheme、C 语言和其他语言的混合,直到 Java 的工业普及使其成为流行选择,但并未完全取代其他语言。随后,随着 Python(以及更广泛的动态面向对象脚本语言类别)变得非常流行,Python 被加入到混合中,但与 Java 不同的是,它迅速取代了不仅是 Java,还有在入门课程中存在更久的大部分内容。

        Python在工业领域的成功推动了其在入门课程中的初始应用,但这并不能完全解释其流行原因,因为它做到了Java从未做到的事情。

        我认为,教授入门课程的人员发现Python在工业流行度方面比Java更少妥协。

        Python的成功是多方面的,其中一部分是恰逢其时,很大一部分是由于其构建的生态系统,但很大一部分是, 我认为,它最终成为了一种经过精心设计(无论是最初设计还是随时间推移的管理方式)的语言,非常适合普通人解决实际问题,尽管它并未遵循各种类型、性能和范式纯粹主义者所宣称的良好语言的本质特征。

      • 1. Python在入门级编程课程采用它之前就已经相当流行了。我认为他们采用Python是因为它是一种优秀的通用脚本语言,支持多平台,易于安装和使用,并且以更易于初学者理解的方式教授编程概念。

        2. Python在Web服务器上的应用早在2012年之前就已存在。1998年左右出现了Zope,它曾流行一段时间,并对后续的Web框架产生了巨大影响。Django于2005年左右发布。TurboGears、Pylons也于2005年左右出现。Flask于2010年发布……这些只是较为流行的框架。

        3. 我认为作者的意思是,PHP 的语法也与 C 语言和 JavaScript 有点相似。关键词是“有点”。它有一种常见的用大括号和分号来声明变量的方式。

        • 我一直都很喜欢PHP提供的庞大标准库,还有PHP文档页面上每个函数的注释部分。

      • > 我想2005年的电脑可能无法流畅地运行Python后端。

        Python从2000年就开始有Web服务器,包括Jim Fulton的Zope(实际上是一个完整的内容管理系统框架)和2002年Remi Delon的CherryPy。

        这两者在当时都非常有用,得到了Web托管公司的良好支持,与通常需要强大Sun Solaris服务器的商业Java系统相比,它们无疑非常轻量级。

        • 我直到最近在Django生日帖子中提到Turbogears时才想起来CherryPy。

          但确实,Python在网页开发和系统管理(早期DevOps?)工具领域正处于上升期,但后来因Ruby(如Rails)、Puppet、Vagrant和Chef等工具的兴起而受到冲击。

          但Python凭借数据科学工具的兴起实现了逆袭,而Ruby则因Web开发转向Node.js、DevOps转向Go语言而失去了昔日的热度。

      • > 他们都转向了Python,因为这样需要解释的内容更少

        我认为这可以归结为易用性。与Python相比,Java 1.6对新手来说简直是一场噩梦。没有顶级语句、显式类型、装箱、冗长的声明语法、漫长的导入语句、到处都是大括号……最重要的是,没有开箱即用的REPL。Java后来有所改进,借鉴了Kotlin和Lombok,但据我所知,这些改进来得太晚了。

        根据个人偏好和应用场景,这些特性中约有一半可能被视为以稳定性为代价的反功能,但这充分说明了人们为何选择并留在某个软件生态系统中。如果我再次参与 Python 项目,我会使用 mise 配合 uv,并严格检查代码库,将其精简为一个合理且完全类型化的语言子集,去除自定义装饰器魔法和双下划线滥用。

        • > 但据我所知,这些努力来得太晚了。

          来得太晚,以至于无法成为今天严肃服务器端软件的首选语言?

          Java的奇怪之处在于,人们自然会将其今天的流行度与它在21世纪初的 dominance 进行比较,而那是一个异常时期。生态系统早已回归碎片化状态,尽管Java不再像那段短暂时期那样主导市场,但也没有其他语言能做到这一点。

          • > 对于今天作为严肃服务器端软件的首选语言而言,是否为时已晚?

            我是在回复

            > Python的成功完全归功于入门级编程课程。它们都转向了Python,

            更不用说你的陈述中还有很多保留条件。当然,Java的工作机会仍然很多,但像PYPL、TIOBE、SO(不考虑“该死的谎言和统计数据”这句老话)等指标都将Python的流行度排在Java之上。

            这只是想说,如果我被撞晕了头,失去了所有的编程知识,不得不谋生,我会重新开始学习Python。这不是价值判断——在我看来,JVM和Python生态系统大致处于同等水平。这就是现实。

            • Python在用户数量上可能略微更受欢迎[1](部分原因在于很多Python编程并非用于发布软件,且许多Python用户并非专业程序员),但JS、Python和Java这三种当今最流行的编程语言,各自在特定领域占据主导地位,且在其他语言主导的领域中均未占据巨大市场份额。用 Python 或 Java 编写的客户端 GUI 软件相对较少,用 Java 或 JS 进行的数据分析相对较少,用 Python 或 JS 构建的严肃服务器应用也相对较少。

              [1]: https://www.devjobsscanner.com/blog/top-8-most-demanded-prog…

      • > Python 的成功完全归功于入门级编程课程。

        是的,自 2008 年后。到 2014 年,它已在许多计算机科学项目中超越 Java。但我指的是导致这一结果的事件。

      • 我认为 Python 的成功几乎完全归因于其未采用任何依赖管理最佳实践,且默认安装在系统中。

        默认安装无疑是更重要的因素。正如当人们开始使用不稳定的依赖项时所经历的痛苦所证明的。他们不得不添加特定支持,以防止通过 pip 安装系统安装的依赖项。

      • > 我认为在2012年之前,我从未听说过 Python 中的 Web 服务器。

        抱歉,但……什么?Django已经20岁了。

      • > Python的成功完全归功于入门级编程课程。他们都转向了Python,因为这样解释起来更简单。

        胡说八道。

        > 我认为在2012年之前,我从未听说过用Python搭建的Web服务器。

        胡说八道。

        > 我猜2005年的电脑可能无法流畅地运行Python后端。

        极度胡说八道。

        https://medium.com/signal-v-noise/ruby-has-been-fast-enough-…

        而那时Python在性能上正逐渐赶上Ruby。

      • 根据我的经验,AWS也对此贡献良多。Boto3是与AWS服务交互的优秀库

      • PHP的巅峰时期是2008年的Facebook!

    • > 3P(Python、Perl、PHP)创造了历史。据20年前的程序员所述,它们就像宗教一样。每个都有自己的哲学和一群忠实的追随者,他们在网上展开激烈的辩论,试图说服更多人采用。新一代开发者更加务实。如今,这更多是关于选择合适的工具,而不是语言之争。

      我认为这种宗教战争的方面被完全夸大了。过去20年里,关于语言的讨论并没有发生太大变化。看看你在HN上看到的事情,甚至就在这里的评论区。这些讨论与20年前的讨论如出一辙,同样充满实用主义。

      我认为这种鸿沟源于主要信息来源是发生在匿名用户之间的对话,他们通过在线昵称与他人互动,而非面对面交流。互动中始终存在夸张的成分。原本可能是“嗯,不”加上一个摇头的动作,却变成了“你妈妈是一只仓鼠,你爸爸闻起来像接骨木”。几乎所有参与者都会逐渐认识到不同论坛特有的文化差异,以及如何将所说的话转化为实际意图。

    • >据我所知,Python最初被视为系统管理员的瑞士军刀。

      是的,我大约在2000年左右(甚至更早)就是系统管理员,当时就是这么认识它的。

      >在2005年至2006年间,发生了两件重要事情:

      某种程度上——我在2001年使用Python开发基于Zope的Plone系统,当时Zope还算流行。用Python编写所有网页内容很有意义,因为Plone提供内容管理系统(CMS)并可集成维基功能。在Python中添加SQL调用也合乎逻辑。当时的竞争主要在PHP和Python之间,虽也有其他较不流行的选择。Ruby on Rails在那段时间确实开始流行起来。PHP直到2005年左右才开始流行,如果说有什么变化的话,人们开始更多地使用Python,并开始批评PHP社区中流传的糟糕代码。

      无论如何,那是一个有趣的时代,但回顾过去有什么意义呢?

    • 这与我的记忆一致,Guido加入Google也解释了这一切。2000年代中期,开源世界的主战场是PHP(Cake/Symphony/CodeIgniter等)与Ruby(Rails)的对决。我只在系统管理和Plone项目中听说过Python。随后,谷歌突然发布了大量关于各种主题的文章,全部采用Python编写。

      我曾对同事说:“谷歌单枪匹马地支撑着Python的生存。”

      然后,砰的一声。它无处不在。2010年代中期,我接受了一份网络安全工作,他们告诉我,在接受工作和开始工作之间的两周内要学习Python。“Python在网络安全领域无处不在,”我被告知。那时我意识到Python已经占据了学术界,这使它在机器学习领域占据了完美的位置。它的特性使其易于入门,但它也受益于恰逢其时。

      • Guido的热情和领导力是Python发展的核心动力。许多聪明的人都认同他的愿景。

    • > 如今,语言之争已不再是重点,而是选择适合任务的工具

      你应该和我公司里的Java支持者聊聊 🙂 语言之争依然存在,只是现在变成了Java对阵其他语言。

      • 我的同事中有一位退休的程序员,他在微软工作了20年,曾告诉我的学生们,过去是Java与Visual Basic的对决,直到微软引入了像Anders Hejlsberg这样的重量级人物。那时才变成了Java与.NET的对决,局势才真正开始白热化 🙂

        • Java毫无疑问赢得了这场战争。

          • C#是Lua的Java(得益于Unity),这永远都不会不奇怪。

          • 虽然我用过两者,但我还是不明白为什么。

            • 微软开放源代码太晚,拥抱Linux也太晚。在桌面端,.NET当时仅限于Windows,而Xamarin尚不成熟。在服务器端,Java领域存在框架之争,因此有众多选择和库可供选择——企业不愿完全拥抱微软生态系统(Windows SQL Server、Windows部署等)。随后Android采用Java,微软在移动生态系统战争中失利。

              我能理解为什么Swift最终可能会失去市场份额——开源得太晚,跨平台支持也来得太晚。

    • Python在科学领域早已无处不在(Numeric和numarray,即Numpy的前身,诞生于90年代末至21世纪初)。

    • 我认为转折点是发现 Google 搜索是用 Python 编写的(还记得错误页面 URL 是 .py 格式吗?),以及 YouTube 重新用 Python 编写。之后,Reddit 和 Dropbox 也跟进(后来 Instagram 也加入)。

      苹果也在 Mac OS X Jaguar 版本中添加了 Python,因此它开始引起关注。

      当大型企业开始使用它时,社区自然会迅速壮大。

      Django是这一趋势的延续,无疑推动了Python的普及,但热潮其实早在此前就已开始(事实上我们不得不忍受Zope/Plone/Twisted的时代:))。

      另一个关键日期是2010年左右,比利时出版的《用Python学习编程》一书问世。尽管当时Django已经面世5年,但它吸引了许多当时对编程一无所知的初学者。

  9. 多年来,Python 在稳步进行稳健改进方面表现出色。类型系统和工具链不断优化。不过,我认为异步编程仍然比其他运行时环境(如 Go 或 Elixir)要复杂得多(甚至在我的经验中,.NET 的异步实现也比 Python 更简单)。总体而言我喜欢 Python,但主要归功于其在机器学习、数据处理/分析等领域提供的强大库。

    • 我不知道自己哪里做错了,但用 Python 编写的代码从未成功运行过。我从 GitHub 或其他地方下载 .py 仓库并尝试运行——结果是错误。我尝试安装缺失的库,使用pip安装这个那个——错误。我与依赖项相关的无尽错误作斗争,当.py文件终于运行时——错误——错误的库版本,错误的补丁,或者“生产就绪”的.py在Windows上无法正确运行,或者单文件脚本使用的Excel库在两年内以不兼容的方式更改了三次。我从看起来很炫酷的网站下载所有文件,并严格按照所有说明操作——结果还是出错。Python 绝非“健壮”的语言。它是宇宙中最脆弱的运行环境,比 C、C++ 和 JavaScript 结合起来还要糟糕,至少这是我使用它的经历。

      • 这太离谱了。我上一次遇到这种情况还是十多年前的事。我承认,十年前确实需要了解太多关于虚拟环境、setuptools、wheels等工具的细节,才能让别人的项目正常运行,但依我之见,Poetry和UV已经彻底改变了这一切。

      • 我的经历也完全相同。我对Python软件避之不及,因为即使它能正常工作,我也无法保证在系统更新Python后它还能继续运行。

      • 你不能仅凭此对Python做出判断,因为你的经历明显是用户操作失误。我从未遇到过你所描述的情况,而我使用Python编程的时间甚至在我还不懂自己在做什么之前就已开始。

      • 你能给出一个例子吗?你可能只是期望太高了。手动检查/安装过程从来都不是像安装应用程序那样简单。

      • 我的亲身经历与之截然不同。事实上,我从未需要过查看一个“花哨的网站并严格按照所有指示操作”来安装软件。

        例如,安装 yt-dlp 时,我遵循了以下步骤:

          sudo apt install pipx
          pipx install yt-dlp
        

        实际上,我只执行了第二步,因为我已经安装了pipx(https://pipx.pypa.io/——一个用于基本虚拟环境管理的pip封装工具)。

        你能列举一些你在Python中尝试使用过的具体内容,并详细描述你是如何尝试设置它们的吗?

    • 这确实令人惊讶,因为 Python 的第一个版本发布于 1991 年,比 Java 早了 4 年。这是一种承载了大量历史包袱的语言,经历了 2/3 版本过渡,BDLF 被完全不同的项目领导体系取代,但它依然保持着强大的生命力。

      Brett Cannon(曾参与打包、导入、Visual Studio Code Python扩展等工作的老核心开发者)的这段访谈令人耳目一新:

      https://www.bitecode.dev/p/brett-cannon-on-python-humans-and

      这个人非常关心,他们有太多事情不能搞砸,你能从他说的每一句话中感受到那种热情和责任感。

  10. > Python 在隐藏其遗留的丑陋之处(如 __init__、__new__ 以及类似的异常)方面做得很好,通过优化语法来迎合有品味的开发者。

    __init__ 或 __new__ 到底有什么问题?@dataclass 是一个非常不错的语法糖,但我们在这里争论的是,拥有对初始化器/分配器/构造函数的 dunder 方法的访问权限是“遗留丑陋”吗?这是 Python 内置意识的核心。奇怪。

    • 奇怪的是你认为这不丑陋。

      Kotlin:构造函数要么是类定义的一部分,要么是关键字构造函数。

      Ruby:initialize

      JS:构造函数

      Python:______new______, _______init_______

      这简直就是这个梗:https://knowyourmeme.com/memes/three-headed-dragon

      • 我喜欢这些魔法方法的名称通常都遵循相同的格式,这样就能明显看出它们是魔法方法,而非公共 API 的组成部分。至于格式是使用双下划线还是其他符号对我来说并不重要,因为它们不会被直接调用。

        • 你认为构造函数也算是魔法方法吗?

          • “魔法方法”只是人们对这些特殊方法的称呼(另一种称呼是双下划线方法)。它们被称为“魔法”是因为你不能直接调用它们,而是由解释器在特定情况下自动调用。

          • 是的,它与其他魔术方法一样具有魔术性,即因为它在语法上是特殊的,并且可以通过除<instance>.<method>(…)或<class>.<method>(instance, …)之外的语法调用(主要由语法调用)。

            具体来说,在构造函数的情况下,通过<class>(…).

        • > 显然它们是魔法方法,不应成为公共 API 的一部分

          是否有替代 API?没有。无论意图如何,这都是公共 API。尽管“这很奇怪”并不是一个很强的反对理由。

          • 公共 API 指的是类的使用者,而非实现者。你永远不会直接调用 __init__。__new__ 允许实现者修改对象的创建过程,但类的使用者永远不会调用它。

          • 存在私有方法和公共方法。私有方法仅应在其他方法内部调用,即私下调用。公共方法是通常通过代码、解释器、用户或其他方式调用的方法。你不得在定义类本身及其方法之外的任何地方编写 `myclass.__add__(x)`。

            • 实际上共有 4 种类型:

                def foo(... # 公共
                def _foo(... # 内部
                def __foo(... # 混淆
                def __foo__(... # 魔法
              

              内部方法更多是约定,因为语言本身不会对它做任何处理,但会对混淆方法做处理,而魔法方法是专门用于语言中实现的特性。

              内部和修改后的方法并不完全对应于私有和受保护的方法,但它们有点类似。

              • 哦,对。我对 Python 并不是很熟悉,只是读过一些关于命名规范的文档,其中提到了几点。

                无论如何,我喜欢使用下划线来指示方法的暴露程度。这使得更容易知道哪些方法可以跳过,哪些不能。

          • 有几种替代的 API。@dataclass 是其中之一,Pydantic 提供另一种,还有 attrs,以及一些不太通用的东西,如 namedtuple。

            坦白说,当你习惯使用一种 API,而团队使用另一种时,这确实令人烦恼——这完全违背了 Python 禅: “做一件事应该只有一种显而易见的方式”。

            ……但这个目标本身就相当雄心勃勃。如果这意味着不必被锁定在只能通过更换语言才能更改的方案中,我愿意在多种替代 API 中穿行。我很高兴能够轻松区分有人在修改 Python 内部实现,还是在修改某个库。

            • @dataclass 只实现了覆盖双下划线方法功能的一个特定子集。它不是在复制抽象,而是在分层抽象。

      • 这就是重点。这些方法从来就不是为了直接调用而设计的。它们用于简化语法。

        我认为批评这些做法有些短视。顺便说一句,我第一次写 Python 时也犯过同样的错误。其他语言中类似的实现提供了有用的透明度。

        • 我认为没有人质疑其实用性,只是连续使用 2-5 个下划线(?)的语法并不符合开发体验友好性。

          • 这种丑陋是故意的。没有人愿意给他们的酷炫新变量起名叫__hot__singleton__in__your__area__。

          • 语法是确切的两个,而不是2-5。

            • 我认为重点是:仅凭肉眼观察就能分辨出来。

              • 任何使用等宽字体、下划线与其他字符分离的字体,或熟悉连字比例字体的人,因为下划线与其他字符的相对宽度。

                这三种情况加起来,几乎涵盖了所有人。

              • 我的意思是,也许你应该使用一种不会让别人难以分辨的字体。接下来你会说我们应该禁止使用0和O、I和l,因为你的字体不允许你分辨出是哪一个。

          • 它们被称为双下划线方法,意思是“双下划线”。就是两个下划线。

            我在编辑器里有这些代码片段大约15年了,所以不需要手动输入任何双下划线方法。推荐!

      • 其他语言用户抱怨Python的丑陋:_____new_____ 和 _____init_____

        同一其他语言用户:

        ((()))()()()({}{}{{(((())){}}}}{}{}{};;;;();)(;}}}

        *这不是正确的语法,只是个玩笑

      • 这就像猎人穿的亮橙色衣服,丑陋感某种程度上就是重点。它在说“这是另一种东西”。

        • > 这就像猎人穿的亮橙色衣服,丑陋本身就是重点。

          丑陋不是高可见度背心(hi-vis vests)的重点,重点是避免被其他猎人误伤。

          • …通过与环境形成强烈对比来实现。也许你的审美不同,但我觉得它相当丑陋。如果它不丑,就不会这么吸引我的注意。穿一件令人震惊的丑陋衣服是避免被误认为是鹿的好方法。

            相比之下,我认为一只伪装得很好的鹿相当美丽——一旦我注意到它。这种美丽来自于它与周围环境的完美融合。完全不像一顶亮橙色的帽子。

            • > 穿着令人震惊的丑陋服装是避免被误认为鹿的好方法。

              当然……是的,亮橙色很丑,但阻止你被射中的不是“丑陋”,而是那种明亮的不自然颜色。其他猎人不会想“哦,这真丑,肯定不是我可以射击的东西”,他们会想“亮橙色代表人类,我不能朝那个方向开枪”。

              > 如果不是这样,它就不会这么吸引我的注意。

              你是说,如果你觉得亮橙色很漂亮,你就不会想到不要朝那个方向开枪吗?

              • 如果我认为明亮的橙色是健康森林生态系统的一部分,我可能会从中看到美感。如果我的意图是射杀该生态系统的居民,那么是的,明亮的橙色会是一个糟糕的“不要在此射击”的指示。你最好选择一些丑陋的东西,一些明显不属于那里的事物。

      • 如果你觉得你关于__new__和__init__的论点是有效的,你很可能就会使用实际的名称,而不是夸张的修辞。

      • 你能详细说明这里的问题是什么吗?是字符的形状还是其他问题?

        • 令人惊讶。如果你需要问这个问题,我猜你花在Python上的时间太久了。没有其他语言会用这种下划线疯狂来表示特殊方法或其他内容。因为它只是,我不知道更合适的词来形容,愚蠢。

      • 你不会直接调用 __init__,因此这种不美观的定义仅限于方法本身,而这在我看来已经相当简洁了。这似乎是一种命名约定,用于那些在任何地方都不通过名称调用的方法,至少在阅读类定义时能提供一些信息。

        依我之见,这比 Go 语言坚持要求导出函数以大写字母开头要好得多——那确实会影响使用该模块的代码,而不仅仅是定义本身。

      • 我认为丑陋与美丽无关紧要。只要它能完成任务且不会引发重大问题即可。人生苦短,何必纠结语法。

        • 对吧?我有点惊讶几个下划线能引起人们如此强烈的感情。

        • > 人生苦短,何必纠结语法。

          你使用的每种编程语言都会在某些方面让你感到烦躁。用过几种后你就不在乎了。

      • 我坦白说,我从未考虑过作者可能在谈论“dunder方法”本身的视觉美学。说实话,这让我有点困惑;我只能说,如果你不喜欢下划线,就别选Python。

      • 比“initialize”或“constructor”少几个字符,明确标记为“非常规方法”。Python在这方面更优。

      • 我不明白你为何将__new__与其他语言的构造函数进行比较。

        Ruby也有类似机制,但它被称为‘new’。

        在 Kotlin 和 JS 中实现 __new__ 提供的自定义类型(符合语言习惯)并不更简洁。

      • 我认为更愚蠢的是必须创建一个名为 __init__.py 的空文件才能让包注册为包。而且相对导入完全一团糟。

        • > 我认为更愚蠢的是,为了让一个包注册为包,必须创建一个名为 __init__.py 的空文件。

          自 Python 3.3 以来,这种情况不再成立。你不再需要一个 __init__.py 文件让 Python 识别一个模块,但它在许多情况下仍然有用。

      • 我的意思是,在我使用 Python 的 26 年里,这从未成为问题,但如果你认为这是个大问题,也许你应该考虑以下内容: https://gist.github.com/josiahcarlson/39ed816e80108093d585df…

        我大概花了一个半小时写完这个。它似乎在Python 3.6和3.12上都能正常工作。现在你再也不需要手动编写双下划线魔法方法了。

      • 关键是将它们隔离在对象命名空间之外,而这一点做得相当不错。

      • 其他语言难道没有自己的缺点吗?例如 Ruby 和 JavaScript 的语法就很丑陋,与这种特殊的命名方式相比显得相当奇怪。

        但谁在乎呢?这只是语法,它有其存在的意义。

    • > 到底 __init__ 或 __new__ 有什么问题?@dataclass 是一个很棒的语法糖,但我们在这里争论的是,访问初始化器/分配器/构造函数的双下划线方法是“遗留丑陋”吗?

      我的理解是,“丑陋”在于方法命名,特别是双下划线,而不是方法本身的存在。

    • 可能是因为给本应普通的标识符赋予特殊含义的系统。__ 并不是 Python 中的保留关键字。但命名方法和属性时,你应该遵循一套约定,而这些约定显然是人为制定的。将语言定义中特殊的部分也作为语言语法中特殊的部分来处理,而不是让它们与其他不相关的东西混为一谈,会更干净利落。

      在 C++ 中,如果你想为一个类定义二进制 + 运算符,你需要为该类提供一个名为 `operator+` 的方法。在 Python 中,要实现相同功能,你需要为该类提供一个名为 `__add__` 的伪特殊方法。你认为 Python 的方式更差吗?

      • > 你不觉得 Python 的方式更糟糕吗?

        你是否考虑过熟悉度可能如何影响你对两者的反应?两者都是带有任意限制的特定模式,新学习者必须内化这些模式,而这对于大多数人来说是一个相当高级的任务,通常要等到他们熟悉语言语法后才会遇到。

        以下是反驳观点:Python 的方法声明是标准的方法声明,而 C++ 开发者必须学习“operator=”与“operator =”(或其他赋值语句)的含义不同,且这是唯一可以在方法名称中使用这些保留符号的上下文。

        为了明确起见,我认为这两者都不是什么大问题——概念和用法比声明语法更难掌握——但我认为,对于这类事物,人们很容易将熟悉程度与学习难度混为一谈。

        • > 你是否考虑过熟悉程度可能会如何影响你对两者的反应?

          不会。

          > 这两者都是带有任意限制的特定模式,新学习者必须内化这些模式,而这通常是大多数人在熟悉语言语法之前不会遇到的较为高级的任务。

          不,Python方法只是具有有效名称的普通方法。你所指的“任意限制”具体指什么?

          • 任意限制在于你必须学习并遵循特定模式。如果我讨厌下划线,就不能让我的 Python 类使用“addition”,或者如果我非常挑剔,就不能让我的 C++ 代码使用“mathematical_division”。在两种情况下,这只是语言开发者做出的特定设计决策,我必须学习,而在两种情况下,我都会在最初适应后不再多想。

            • Python中,你必须为每个想要定义/重写的双下划线方法学习一个任意的模式。+ → __add__,* → __mul__,等等

              C++中,只需学习一种模式,x → operatorx

      • 我不确定“operator+”与“__add__”在实际使用中有什么显著区别。

      • 有趣的是,仅仅在类方法/变量名称前加上__,就能产生类似关键字的魔力效果。具体来说,这会对类外部的调用者进行名称变形——`self.__foo`需要通过`obj._ClassName__foo`访问。这是 Python 实现私有方法的一种方式,尽管它在理念上对私有方法的存在持保留态度。

        不过,这不适用于双下划线方法。它们神奇地免受这种变形处理,因此你可以直接调用它们。 ¯_(ツ)_/¯

        > 你不觉得 Python 的方式更糟糕吗?

        它们似乎大致相当?我看不出来选择其中一种而非另一种的实质性理由,除了个人偏好。

        • __foo -> _ClassName__foo 的用例是什么?我虽然用过很多 Python,但从未用过这个特性,所以很好奇。

          • 它防止子类意外覆盖成员,从而避免父类私有实现细节被子类意外干扰。

            随着组合优于继承的编程风格越来越流行,这种情况可能越来越少,但只要继承被使用,它仍然有用。

          • 它几乎不可能意外引用该属性。基本上,如果类 Foo 定义了 “self.__bar”,Python 解释器会看到前导的 “__” 并对其进行处理。这相当于其他语言中的私有属性,不同之处在于 Python 会允许你尝试访问它。但如果你这样做,并且导致你的代码变得极其脆弱且容易出错,没有人会同情你或帮助你修复它。他们更可能告诉你不要这样做。

          • 这是在保持“我们都是成年人”的哲学下,尽可能接近私有方法的实现方式,这种哲学不会阻止你做一些愚蠢的事情。或者如果你使用了这个方法导致代码崩溃,你不能说自己没有被警告过。

        • 这个问题在解决什么?它并没有解决`__add__`是一个有效标识符的问题——`add`也是——而且它也没有解决符号`+`与名称`add`之间没有关联的问题。

    • 我从事 Python 开发已有相当长的时间(约 10 年左右,主要在后端网页开发和文件处理领域),虽然我不太喜欢作者将双下划线方法描述为“遗留”的方式,但这仍然不是我经常使用的语言特性,因为,嗯,从 C# 背景来看,这有点奇怪。作者有关于Java的博客文章,而Java与C#差异不大,或许这就是原因。

      或许随着我逐渐从架构层面思考代码的“大局观”,我会开始采用双下划线方法,但目前……

  11. 出于好奇,为什么我会选择使用Dataclass而不是Pydantic的Basemodel?如果没有Pydantic的依赖关系,我可能会考虑使用Dataclass。但是既然有Pydantic,为什么不到处都用它呢?

    • 为了帮助回答你的问题,这里是attrs项目中的一份详细比较:

      https://www.attrs.org/en/stable/why.html

      其中可能存在一些明显的偏见,但我认为大多数论点都很有道理。

      编辑:找到另一个我认为有帮助的:https://threeofwands.com/why-i-use-attrs-instead-of-pydantic…

    • 如果不需要验证或序列化,这会带来不必要的负担。

      我的经验法则是:如果需要序列化,就使用Pydantic;否则默认使用dataclasses。

      • 这不仅仅是因为性能原因。Pydantic模型代表了程序的I/O边界。这向阅读代码的程序员传达了大量信息。如果发现它实际上只是在内部传递而从未用于 I/O,这会非常具有误导性。这有点像使用 int 类型来存储布尔值。

        另一方面,如果我看到一个 dataclass,我可以通过它是否被冻结等来判断其用途。

        始终努力实现自文档化代码。

    • 主要原因是(过去)性能问题。但自pydantic 2.0起,这已不再是问题,因此我默认使用pydantic处理一切。

    • 数据验证在构造时会造成性能开销。我更倾向于使用msgspec,它更轻量且速度更快。

    • 一个明确的区别是,数据类不支持验证嵌套对象。

      我会用它来传递扁平结构作为函数参数,而不是一个巨大的单个参数列表。

    • 既然有 `TypeAdapter(MyDataclass)`,为什么还要使用 Pydantic 模型?

    • 我的经验法则是:数据类用于编译时类型检查,Pydantic 类用于运行时类型检查。

    • 最简洁的答案是使用 pydantic.dataclass

  12. 有趣的是,我最近做了相反的切换,也觉得不错。

    我对 Python 的看法:https://calvinlc.com/p/2025/06/10/thank-you-and-goodbye-pyth…

    下次我接触Python时,我会尝试uv、ruff和ty。

    • 我之前从Python切换到JS进行后端开发,感觉非常不错。我同意“Python的安装和包管理存在问题”,但异步功能是提升生产力的最大改进。我知道Python有asyncio,但拥有一个被广泛接受的实现方式与多个相互竞争且不兼容的方式之间存在巨大差异,而好的方式往往缺乏足够的势头。

      其余都是些小问题,但累积起来影响很大,比如Python的空格作用域,或者Python导入时不支持相对路径,又或者JS的对象语法更优雅:https://news.ycombinator.com/item?id=44544029

    • ty 仍处于 alpha 阶段,我目前不推荐使用。虽然它速度快,但存在大量误报。正如 Charlie 本人所言:https://bsky.app/profile/crmarsh.com/post/3lp2xikab222w

      uv + ruff(用于格式化和代码检查)是绝佳组合。

      使用 uv 越久,你越会发现它能解决许多 Python 陷阱的强大功能,比如在 shebang 中使用内联依赖,或是 “–with” 选项的妙用:

      https://www.bitecode.dev/p/uv-tricks

  13. > Python 解释器在所有 Unix 发行版中都原生集成

    它包含在大多数桌面/服务器 Linux 发行版的默认安装中(有许多例外),但我认为没有 BSD 发行版将其包含在基础系统中。

    据我所知,macOS 曾经在默认安装中包含 Python 2,但我模糊地记得它在某个时候被废弃并移除了。我唯一的 Mac 目前在另一个州,所以我无法亲自检查。

    • Python 2 在 Monterey 12.3 版本中被移除,这简直是愚蠢且破坏性的,因为它让所有人都措手不及。我们都知道苹果公司曾表示会移除它,但所有人都以为他们会明智地选择在新的重大操作系统版本中进行,就像他们对 PHP 所做的那样,而不是在版本周期中途。

      https://developer.apple.com/documentation/macos-release-note…

      我猜想,或许正是这场风波导致他们至今仍未移除Ruby和Perl,尽管他们曾做出过同样的承诺。macOS上的Ruby版本大约是2.6。至于Perl,我怀疑他们可能不会移除,因为它作为Unix系统管理的重要组成部分,我敢肯定他们自己也在某个地方使用它。

      仍然存在一个 /usr/bin/python3,这是一个兼容层。当你调用它时,如果你没有安装 Xcode 开发工具,系统会提示你安装(这是一个简单的图形界面对话框,只需点击两次即可完成)。安装完成后即可使用。虽然这个版本比最新版本落后几个版本,但它偶尔也会进行更新。

      • > [截至 2020 年 1 月 1 日,该软件已正式达到生命周期终止(EOL)且不再由开发者提供支持 ——尽管他们在4月通过紧急补丁撤销了这一决定]在[2021年10月25日发布的操作系统]中被移除,这极其愚蠢且破坏性极强,因为它让所有人猝不及防[尽管该软件的EOL意图已提前多年宣布,且该截止日期本身已比常规发布周期延长了数年]]。

        我永远无法理解这一点。

        不过,我从Python 3.2开始使用Python 3,我对这一变化的最初反应是松了一口气,等到我升级到3.4时,我已经在想为什么其他人还在拖延切换。

        • > 我永远无法理解这一点。

          也许是因为你对我的评论理解有误。

          > 在 [2021 年 10 月 25 日发布的操作系统] 中被移除

          不,不是!那本来是可以接受的。甚至如果他们在前一年移除它也无妨。两年。三年。我不在乎。问题是它在一次小版本更新(不是 10 月)中被移除,且没有提前警告,而此前他们已经在一次重大版本更新中移除了另一种语言。

          > 但我从Python 3.2开始使用Python 3,我对这一点的最初反应是松了一口气

          我甚至不在乎Python。但我仍然不得不处理那些我没有编写的东西带来的后果。

          • > 问题在于,它是在一个小版本更新(而非十月)中被移除的,且未提前通知,此前已设定过在重大版本更新中移除另一种语言的先例。

            我的观点是,问题在于人们在2020年1月1日之后仍在使用Python 2。我早在数年之前就已放弃它。

      • 我认为现在我们不应将.0版本的重大更新视为macOS中真正存在或不存在功能的标志。操作系统通常在.4版本左右趋于稳定,在此期间一些已废弃的功能也可能被移除。

        他们还有一个习惯,即有时会在版本周期中添加或修改一些API,因此你可能会看到对最新Xcode版本的要求与操作系统的中期版本相关。或者像Mac App Store那样,不仅它本身,而且其中分发的程序的相关API在10.6.4中就出现了?

      • 我的Sequoia 15.5上/usr/bin/python3显示的是3.9.6。这似乎……太老了。我所有的工作和个人项目都使用3.13。

    • 我认为现在 macOS 已经不再预装 Python 了。(uv 已经取代了它)

      编辑:与较早版本的 macOS 不同,较新版本的 macOS(Catalina 及以后)不再预装 Python。

      • 从技术上讲,macOS 并未预装 Python,但它确实提供了一个简单的安装路径。

        https://news.ycombinator.com/item?id=44580198

        移除操作发生在 Monterey 版本中。Catalina 及其后续版本 Big Sur 仍保留了该功能。Catalina 是第一个移除 32 位支持的版本。

        • 我认为他们意识到了安全隐患,

          你可以随意拿一台 MacBook,打开终端并运行 python3 -m http.server 3000 –directory ~

          然后在本地网络上你可以下载他所有的文件

          • 如果你有权访问他们的MacBook并能打开终端,即使是预装的rsync也能完成任务。

            我认为更可能的情况是,他们只是厌倦了管理这些语言(并不断被批评落后),因此选择直接移除它们。

          • 这说不通。如果有物理访问权限,我完全可以直接下载Python并做同样的事情。或者任何其他软件。

      • macOS最近也移除了PHP——这简直是在毁掉苹果公司多年来辛苦积累的开发者市场份额。

        • 为什么?使用brew安装Python和PHP只需2秒,而且你可以控制安装的内容,而不是使用系统中已过时的版本等。实际上现在要好得多。系统工具应该由系统来管理。

        • 我认识的几乎没有专业开发者会使用内置的Python或PHP。不过,对于偶尔的脚本编写用途来说,可能已经足够了。

        • 我认为开发者不应使用冲突的系统发行版。

          不过,有一段时间内,内置解释器对初学者和学习者来说确实很方便。

        • 我更喜欢自己安装,使用项目所需的版本,并且安装在已知且常见的位置。

          • 这倒也说得过去。对于有经验的开发者来说可能问题不大,但对于那些想学习编程的人来说,这又多了一道障碍。

        • 如果你安装了命令行工具,那么

          /Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/

  14. 我一直避免使用Python,但现在我自己也被卷入了,主要是因为某些任务似乎比Java或Perl需要少得多的代码。

    不过,尽管如此,叫我老派吧,但我真的对“curl $URL | bash”这种安装方法有意见。如果你要使用安装脚本,最好先检查一下。

    • 如果你打算执行代码,你必须检查一切,或者信任提供代码的人。bash 并没有什么特别之处,使其比 Python 更危险。

      • 我的问题是 $URL 可能被劫持,或者像 PuTTY SSH 客户端不在 putty.org 上的那种混乱。

    • 你也会检查二进制安装程序吗?

  15. 我喜欢 Python 作为编程语言,对于许多任务来说,线程模型限制并不重要。它是一门完成任务的优秀语言。我认为包管理是个挑战,但下次我会尝试 uv!

  16. 不错的文章,但这个命令并不会激活由 uv 管理虚拟环境。

        uv venv activate
    

    该命令实际上会在名为 “activate” 的目录中创建一个新虚拟环境。正确激活虚拟环境的方式如下:

        source .venv/bin/activate
    
    • uv 的核心原则之一就是不激活 venv。每次运行时都通过 uv 执行,例如 `uv run pytest` 或其他命令。

  17. 写得很好,选择也很稳妥。作为过去几年主要使用 Python 开发的人,我的技术栈与之非常相似。

    两个额外建议:

    * 使用 mise 管理系统依赖项,包括 uv 版本和 Python 本身

    * 直接使用 makefile 语法太过繁琐,建议改用其他方式。

    Mise实际上还提供了一个命令运行器,我尚未尝试过,但可能更适合在当前环境中运行命令。当你的GitHub Actions工作流只需:

    * 安装mise

    * mise安装其他所有依赖

  18. 如果你像文章中那样将 Make 用作任务运行器,请确保你将任务声明为 .PHONY 目标:https://www.gnu.org/software/make/manual/html_node/Phony-Tar…

    否则,如果存在与任务名称相同的文件或文件夹(例如“test”),该任务将无法执行,这在使用 Makefile 作为脚本或 CI 的一部分时可能会非常烦人,因为你可能不会注意到“Nothing to be done for…”的提示。

    • 哦,这就是它的实际用途吗?

      据我所知,信息页面只是说它是用于缺少文件的目标。这样更容易记住。

  19. > 没错,Python 非常强大,而且与现在无处不在的 VSCode 编辑器配合得很好。

    我一直觉得VSCode在Python和C语言方面不如PyCharm和Clion。后者无需调整配置文件就能直接使用。

    • 有趣的是,我对JetBrains IDE的看法恰恰相反。它们*看起来*非常强大,但如果我从事应用、基础设施和管道工作,总需要花很多时间配置。(编辑:我不是说它们不够强大,只是作为新手,我总是不知道如何在开箱即用时最好地利用它们)

      根据我的经验(这并非普适真理),喜欢 JetBrains IDE 的用户大多来自 Java/IntelliJ 背景,听说在那里它们确实表现出色。

      这可能只是技能问题,因为我几乎所有专业项目都是基于VSCode的,但由于我只在小型公司工作过,我不能排除这是因为设置起来更容易,而不是为了让Fin为我们所有人争取许可证而斗争。

      在你看来,是什么让PyCharm(或如果你想加上CLion)“just work”?你认为这是因为你使用它太久,已经熟悉了它的方方面面?还是说你看到它有一些VSCode没有的功能?

      作为一个对JetBrains世界没有太多专业接触的人,我一直对这个问题感到好奇。

      • 我所关注的“直接可用”功能包括:将鼠标悬停在标识符(本地或外部)上获取信息、右键点击跳转到定义位置,以及通过单击在调试器中运行代码。也许我只是不知道如何在VSCode中设置这些功能;我是一家一人公司,没有同事可以询问如何操作。我的 Python 工作只是提出请求、分析数据并将结果输出到 csv 文件中。这被 Rust 然后 C 取代了。C 代码有 60k 行,因为它能做更多的事情。在 Clion 中,当您打开一个 cmake 项目时,它会自动从 cmake 文件中理解您的项目结构,并提供我列出的那些必备功能。

      • “Just work”当然是主观的,但JetBrains产品中的内省功能才是它们强大的原因。如果你从未听说过所有18万亿个lint工具,你可以在PyCharm中打开一个文件,它会立即指出它将如何失败,包括我最喜欢的技巧,这是VSCode+lint垃圾从未想过要尝试的:

          import re
          import sys
          if re.match(r“[A-Za-z}”, sys.stdin):
              print(“ok”)
        

        PyCharm会立即发现这个错误,无需任何复杂配置

        PyCharm Professional还涉及SQL方面:

            with connect(“:memory:”) as conn:
                c = conn.cursor()
                c.execute(“SELECT * FROM oops WHERE NAME IN (‘alpha, 'beta’)”)
        

        立即检测到错误,无需任何配置

        我原本想用 json.loads 作为示例,但似乎在某个环节他们搞砸了——json.loads 的第一个参数本应是 JSON 字符串。不过,这确实展示了你可以通过 PyCharm 的语言注入注释,让其语法检查任何字符串字面量的“内部”语言:

          import json
        
          # language=json
          bogus = “”“{”this is subtly wrong“; true}”“”
        
          json.loads(bogus)
        
          # 同样适用于 tomllib:
        
          import tomllib
          # language=toml
          bogus = “”"
          [alpha]
          beta = []onoz
          “”"
          tomllib.loads(bogus)
        
          # 或者,如果你有一些复杂的内部逻辑
        
          def do_the_thing(
            # language=sql
            s: str
          ):
              with connect(something) as conn:
                  c = conn.cursor()
                  c.execute (s)
        
          do_the_thing(“SELECT * FROM oops WHERE NAME IN (‘alpha, 'beta’)”)
          #            ^^^ 也会进行语法检查,因为它知道第一个参数是 SQL
        
      • JetBrains 在我的机器上运行得非常缓慢,以至于我体验到了物理电传打字机的速度,尽管这种设备早已过时。

  20. 从哪里?最好在帖子开头提及,这样我们就能了解你使用 Python 的背景。

    我主要从 Perl 切换到 Python 是在 2010 年代初(我认为我第一次“认真”使用的版本是 2.6)。这主要是用于系统管理、监控以及数据转换/可视化。当时在工作环境中并没有涉及像人工智能这样的高级应用。

    我发现最大的影响并非在于编写代码本身,而在于代码能够在很长一段时间内保持可读性,即使是匆忙编写“先让它运行起来”的代码也是如此。尤其是在团队协作中。

    Python仍然是我最喜欢的语言之一,如果 Bash 无法满足我的需求,它就是我首先会使用的工具。

  21. 我基本上同意这些观点。我唯一想补充的建议是将“make”替换为“just”

    • 我时不时会听到关于它的讨论,但始终无法理解其优势。与make相比,它的杀手锏功能是什么?

      • 首先,我理解 make。我这么说是因为我熟悉它,而不是因为我无知和恐惧。

        Make 非常适合编译没有定制构建系统的语言的代码。如果你想编译一堆 C 代码,那就太棒了。但要构建 Rust 或 JavaScript 项目,那就不可能了。这些语言有更适合自己的工具。

        因此,在过去大约15年里,我一直将make用作任务运行器(比如“make test”调用“cargo test”,或“make build”调用“cargo build”等)。作为任务运行器……它有点糟糕。当然,它完全能够运行任何 shell 脚本可以运行的内容,但它最初是为编译大型软件项目而设计的,因此在实现这一目标时带有许多隐含的结构。

        它并不试图成为一个构建系统。它专为运行任务而优化。在这里,这意味着它提供了大量方便的路径操作和其他常见脚本功能。它还添加了数十个开发者可能甚至没有意识到自己需要的便利功能。

        例如,考虑这个简单的 justfile:

          # 删除旧文档
          clean:
              rm -rf public
        
          # 该命令接受参数
          hello name:
              @echo Hello, {{name}}
        

        如果你在包含该文件的目录中运行 `just –list`,它会显示该文件中的目标列表:

          $ just --list
          可用配方:
              clean      # 删除旧文档
              hello name # 该命令接受参数
        

        第二个配方需要一个必填的命令行参数:

          $ just hello
          错误:配方 `hello` 只收到了 0 个参数,但需要 1 个
          用法:
              just hello name
        
          $ just hello underdeserver
          你好,underdeserver
        

        你可以在 make 中做这些事情!我亲眼见过!Just 只是没有添加之前不可能实现的功能。但我保证,在 make 中实现这些功能比在 just 中难得多,因为在 just 中它们是原生功能。

        还有无数类似的小巧思。just 并不试图做 make 做的所有事情。它只专注于你会在 .PHONY 目标中放置的较小子集,并让它们的使用变得非常、非常便捷。

        你不会在大型、复杂的构建中用它来取代 make。我会毫不犹豫地推荐它来包装新语言存储库中的常见目标,这样无论你是在 Python、TS、Rust 还是其他语言中,`just clean build test` 都能做同样的事情,而你也不必为了构建几个简单的入口点而费力地应对 make 的各种怪癖。

        • 我并不喜欢 Make 作为构建系统,尤其是因为今天我接触过的每种语言都有更好的构建系统——cargo、uv、cmake、bazel 等。

          我的观点是,Make 非常普遍,如果保持简单,其语法完全可读;如果你要做复杂的事情,我认为你根本不希望将逻辑放在 Makefile 中,而是应该放在 shell 脚本或 Python 脚本中。

          我明白,如果你想做非常复杂的事情,Just 可能更符合人体工程学。但我还没有看到任何真正的论据表明它更符合人体工程学,或更易于理解,或值得学习曲线,当你的 Makefile 简单或有实际脚本处理复杂任务时。

          `just –list` 确实缺失(尽管我听说 make 正在添加一个 –print-targets 标志),但我通常需要运行相同的命令 – make run、make test、make typecheck、make lint、make format。注意这些命令都不需要参数 – 这也是我从未觉得需要的东西。

          • 关键在于:just 对于简单任务也更加简洁和干净。例如,它不需要 .PHONY。如果你已经需要安装 cargo/uv/cmake,只需将 just 添加到依赖列表中。

            当你意识到不再受限于无法轻松访问参数时,参数功能就显得很有用了。我用just来构建我的博客,并添加了一个目标,每次更新到某些软件的新版本时,都会创建一个模板条目。我可以写“just newversion 1.2.3”来发布公告,使用与其他操作相同的机制。没有这个功能,我本可以编写一个脚本来实现相同的功能。有了它,我无需这样做。我不会尝试用make来做这件事。

      • 如果你是 make 的专家并且已经有一个 Makefile,我不会建议你为了那个项目切换到 just。

        just 的优势在于它设计为命令执行器,而 make 设计为构建工具。justfile 语法简单且更符合人体工学。它还具备许多实用功能:私有配方、子模块、可指定仅在特定操作系统上运行的配方(我们为Windows和Linux使用相同的justfile)、使用与shell语言不同的语言编写配方,以及许多其他便利功能。

        新用户只需几个小时就能在just中开始进行“高级”操作。如果通过make尝试相同操作,则需要花费更多时间。

    • Just 对任何项目都非常有用——包括非代码项目!

  22. 有趣的是,我恰恰相反:大语言模型(LLMs) 使我能够轻松地用 Python 编写草稿,然后将其翻译成更适合目标问题领域的语言,例如 Go。

    • 同意。我们接受了关于如何开发大语言模型(LLM)应用程序的培训,显然是用Python语言。我从来就不喜欢Python,但依赖性问题(演示项目使用了uv,但作者却忘记声明一些她可能已经全局安装的依赖项)浪费了大家数小时的时间,还有持续出现的魔术(管道运算符与unix管道完全不同,wtf!仅仅为了声明一个带有元数据的数据类型就得搞一堆花里胡哨的东西,还有很多类似的情况),几乎所有人都立即切换到了像Kotlin这样的类型化语言(他们有一个不错的AI框架),一旦我们掌握了基本的工作原理。

    • Go语言是否不包含用于mmap的模块,也不包含用于将二进制数据放入结构体的模块?

      如果你想读取二进制文件,你可以使用C或Python,但不能使用Go(当然,除非你使用那些容易导致段错误且质量堪忧的第三方库)。

  23. > 我更喜欢使用单仓库结构

    曾在一家公司工作,这种方法导致了庞大且难以管理的结构,没有人敢触碰,以免破坏其他团队正在工作的内容。问题不在于仓库本身,而是依赖结构(如整个仓库共用一个 requirements.txt 文件)和构建脚本。

    理论上这应该很完美——只需更新一次依赖项,就能确保所有代码都应用了最新的安全补丁。但实际上, everyone 都害怕触碰它,因为这可能会破坏他人的代码。单仓库架构只有在患有严重的 NIH 综合症(如谷歌)时才能发挥作用。

    我开始欣赏那些该死的微服务,只要每个服务都反映出团队的组织结构。

    https://en.wikipedia.org/wiki/Conway's_law

    • 听起来更像是一个单体应用。你可以在单仓库中使用具有独立需求的微服务。我们在工作中使用pantsbuild(嗯,这个工具有点古怪)进行了一次设置,但它比不断创建新的Git仓库和CI管道来处理小型服务/工具要好得多。

      不过,我认为“每个团队一个服务”是一个不错的模式

  24. “猜猜人工智能的默认编程语言是什么?没错,就是那个狡猾的家伙。”

    这是否在指PyTorch?如果不是,你觉得作者指的是什么?

    “不仅因为语法更人性化,还因为Python解释器在所有Unix发行版中都原生集成。”

    这是否指的是 GNU/Linux?

    UNIX(UNIX 类系统)不仅包括 Linux;有些 UNIX 发行版在基础系统中不包含 Python

    是否安装 Python 由用户自行选择

    我知道这一点,因为我使用这样的发行版,除非某些软件需要它,否则我不会安装 Python

    在这种情况下,当我完成使用该软件后,我会卸载它^1

    例如,他提到提取YouTube频道元数据

    我不用Python做这个;我用一个19行的shell脚本(ash而非bash),其启动速度更快

    与Python不同,它包含在我使用的UNIX发行版(包括Linux和BSD)的基础系统中

    但如果我需要用yt-dlp测试某些内容,可能会临时安装Python

    1. 我从源代码编译Python,该项目的一个令人烦恼的方面,除了启动速度慢外,就是其Makefile中未包含卸载目标
    • “猜猜人工智能的实际编程语言是什么?没错,就是那个狡猾的家伙。”

      他指的是Python语言本身

      “不仅因为语法更人性化,还因为Python解释器在所有Unix发行版中都原生集成。”

      我认为他的意思是它在许多主要Linux发行版(如Ubuntu、Fedora、NixOS等)中可用或易于获取。我认为“原生”这个词不太准确。

      我也使用bash,但Python真的很棒。你说得对,确实存在与打包和速度相关的问题,但它仍然经常是完成任务的正确工具。它强大、易于使用且开源。

      • “我也用bash,但Python太棒了。”

        我不使用bash。我使用ash。bash对我来说太慢了,就像Python一样。

    • >我不用 Python 來做這個;我用一個 19 行 shell 腳本(ash 而不是 bash),它的啟動速度更快

      Ash 不會做網頁請求,除非你在 ash 中實現了 HTTP。你又得依賴第三方依賴項,而這些依賴項並非所有系統都安裝

      • 对于这个脚本,我使用 ash 内置的 printf 函数“实现了 HTTP”。

        TCP 网络通信通过原生 netcat 从管道读取 HTTP 数据实现。

        TLS 由监听回环地址的 TLS 转发代理处理。

        我使用的原始 netcat 及其他 TCP 客户端,如 djb 的 ucspi 中的 tcpclient 或 haproxy 中的 tcploop,并非 NetBSD 基础系统的一部分,但可在编译操作系统时轻松添加。对于 Linux,我使用自己制作的自定义发行版,不包含 LFS。Busybox 将 ash 和 nc 整合在同一个二进制文件中。

        这些TCP客户端程序是静态目标,它们将年复一年地可靠运行,且体积足够小,即使在资源有限的计算机上也能快速存储和编译。Python则不断演进,是一个动态目标,且体积庞大。

        我用 C89 编写了一个名为 yy025 的工具,它“实现 HTTP”。这个工具使用 GCC 编译,具体来说是使用 flex,而 flex 安装在所有我使用的系统上。flex 是 NetBSD 工具链的一部分。它是编译操作系统的必要条件。它是构建许多 GNU 用户空间工具的必要条件。甚至在构建 Linux 内核时,它也被列为必要条件。

        yy025是我在需要生成HTTP时通常在shell脚本中使用的工具。它从标准输入读取URL,并输出定制的HTTP到标准输出。HTTP没有“第三方依赖”。这是一个“第一方”程序。我编写了它。

        但这个用于获取YouTube元数据的脚本没有使用yy025。它只是些printf语句。

        • 近年来,NetBSD 已将 OpenBSD 版本的 nc 添加到基础系统中。

          因此可以认为每个系统都“预装了”一个 TCP 客户端。

          对我来说,更重要的是系统中已安装的 GCC 版本。我尚未遇到过缺乏创建基本 TCP 客户端所需网络功能的类 UNIX 系统。

    • 你为何不直接让 Python 程序闲置?出于安全考虑?

      • 通常是存储空间问题。即使我有剩余空间,我仍然喜欢只安装我实际使用的工具,保持用户空间的最小化。

  25. YAML、Python、Make……——我是否看到了选择这些工具集的问题和模式?

    • 它们占据了一个大多数时候都能正常工作的空间,但卡顿和陷阱是家常便饭。不幸的是,这似乎是大多数人所在的领域。

  26. Python什么时候过时了?这是我看到的第二篇将它视为某种异端的的文章。

    我明白它不再是最新潮的语言,但我不明白为什么有人会讨厌它。这是因为一些从未学过Python的初级开发者,还是因为有我错过的某种新语言?(请不要告诉我JavaScript……)

    • 过去十年左右,我对Python一直有一种莫名的反感。

      我主要是对它的一些设计决策不满意。我不喜欢lambda表达式不能跨多行,我不喜欢循环运行得那么慢,我不喜欢有些函数似乎会修改列表而另一些不会,我肯定还有其他我没注意到的缺点。

      但归根结底,是因为我的职业生涯中很少用到它。我偶尔会因为脚本编写而不得不使用它,甚至在大学里教过编程 101 课程,但除此之外,我并没有太多接触过它。

      对于自己的脚本编写,我通常会使用 Clojure 和 GraalVM(是的,我知道 babashka),或者现在甚至只是使用像 Rust 这样的静态链接编译语言。

      老实说,我不太理解为什么人们认为编译语言不能用于脚本编写(或至少是任务自动化)。是的,你必须添加一个编译步骤。这可能需要在开发过程中多做一步,但实际上甚至不需要。使用 Rust 时,我只需在开发过程中运行 cargo run,我认为这并不比输入 Python 更难。

      • Python最疯狂的特性是for循环会保留中间变量。

        for x in [1,2,3]: print(x)

        x会一直存在!因此你总会有这些随机变量在系统中游荡,希望你不会用错。

        更糟糕的是,如果你使用 mypy 进行类型检查,当你尝试用不同类型重新使用 x 时,你会得到一个漂亮的错误:

        for x in [‘a’, ‘b’, ‘c’]: print(x) << 赋值时类型不兼容(表达式类型为 “str”,变量类型为 “int”) [assignment]

        而这些类型我永远无法信任。我已经使用了所有工具,但运行时仍然会出现类型错误。它也慢得离谱。

        我还希望拥有可选链(例如 foo?.bar?.baz)以及其他高级编程语言拥有的无数其他功能。

        • > Python 最疯狂的功能是 for 循环会保留中间变量。

          “疯狂功能”是对这种行为的委婉描述。我认为这只是一个愚蠢的 bug,却一直存在至今。可能现在无法修复,因为 https://xkcd.com/1172/

          类型检查器和代码检查器应该如何处理这个问题是一个棘手的问题。语言应该如何运作,以及语言实际上如何运作,这两个问题并不相同,而且遗憾的是,它们并不相同。

        • 这样的评论基本上是在说“我希望得到尽可能多的帮助”。

          幸运的是,如今的大语言模型(LLMs)在这方面做得相当不错。

          >而我永远无法信任的类型。我已经使用了所有工具,但运行时仍然会出现类型错误。而且速度慢得离谱。

          IDE集成的mypy检查会在你输入时在后台进行。至于错误,这都与你实际使用的类型数量有关。你可以设置IDE在任何类型或缺少类型注释时抛出警告。

          再次强调,手把手指导。

      •     msedit main.py && ./main.py
            !!
            !!
        

        但事实上,按 F5 键就可以解决 Rust 和 Python 的这个问题。

      • 为什么你要让 lambda 跨越多行?这比函数好在哪里?

        • 因为有时我希望逻辑跨越多行,且不想为其命名。一个简单的例子可能是涉及mapfilter的场景。例如,在 JavaScript 中

              [1,2,3,4].filter(x => {
                  let z = x * 2;
                  let y = x * 3; 
                  let a = x / 2; 
                  return (z + x * a) % 27 == 2;
              });
          

          显然我知道可以给这个函数命名并传入,但对于这种一次性逻辑,我认为lambda表达式已经足够描述性,而且我喜欢它可以直接在原地完成。

          你可以不同意,但我认为大多数语言现在都允许多行lambda表达式是有原因的,甚至包括Java。

          • > 显然我知道我可以给这个函数起个名字并传入,但对于这种一次性逻辑,我认为lambda表达式已经足够描述性,而且我喜欢它可以直接在原地实现。

            顺便说一句,你还可以享受能够对逻辑进行单元测试的优势。

            • 我的意思是,也许这就是为什么你的lambda表达式不应该太长。

              我做过很多Haskell和F#,对“提升”的概念非常熟悉,是的,能够单独测试组件是很好的,但在Haskell中,如果逻辑不需要重复使用或只有几行,使用lambda表达式并不罕见。

              如果你有一个巨大的函数,或者你认为逻辑有任何可能被复用,当然不要使用lambda,而应该使用命名函数。我只是说,有时候只有2到4行的代码,还不值得给它起个名字。

          • 离题:你没有使用y。

            • 你说得对!显然这只是我为了说明一个观点而临时写的一个例子,但我确实不应该使用多余的变量。

          • 说实话,我真的看不出来无名函数有什么吸引力。我几乎不使用lambda,所以如果它们消失了,我也不会特别在意。偶尔作为排序键,或者在列表推导中使用。

            我在JavaScript中经常看到有人这样做,但我一直以为这背后有什么我不知道的性能优势。

            仔细想想,如果将函数传递给另一个函数时,确实需要简洁性,这种做法是有道理的。我能想象在需要时使用类似的写法,但一旦需要排查问题,就会立即拆分并给它起个名字。这与我目前使用嵌套理解表达式的方式类似,即在需要时直接写出来,但一旦出现问题就立即重构。

            我可能只是对一些大括号之类的东西不太适应,如果给它们命名,对我来说更容易理解。我想这就是为什么我们有32种变体。

            感谢你坦率地回答我,我真的很感激,即使我的语气听起来有点轻率。有时候我直到读回来才意识到自己听起来是什么样子。

            • 显然,对于这种事情,有不同意见是完全可以接受的。

              > 我在JavaScript中经常看到有人这样做,但我一直以为这背后有什么我不知道的性能优势。

              我不这么认为,至少我没听说过有这样的优势。

              我有一个经验法则:“如果超过6-7行,就给它起个名字”。这不是严格的规则,但我尽量逼自己遵守。

              就像在Python中,大多数lambda表达式可以写成一行,但这也涉及到另一种糟糕的逻辑,因为你可能会试图在表达式中塞入尽可能多的内容。

              比如,在我的例子中,它本可以这样写:

                  [1,2,3,4].filter(x =>((x * 2) + x * (x/2)) % 27 == 2);
              

              但现在我得到一个巨大的表达式,因为我把它全部放在了一行中。之前我有多余的两个变量名,现在我把临时逻辑塞了进去,因为我想把它挤进一个lambda表达式中。

              • 我认为这就是只允许使用单行lambda表达式的理由所在。我记得很久以前看过一个帖子,其中GVR甚至不希望包含lambda表达式,如果有时间的话,我可能会去找找并添加一个链接。

                从根本上说,可以说Python的目的是迫使用户以可读的方式格式化代码。随着时间的推移,出于实用性考虑,Python逐渐偏离了这一原则,以吸引那些对可读性标准存在分歧的用户。

                有时我希望他们能取消嵌套列表推导式,因为在紧急情况下我懒得避免使用它们,而且我乐于让它们正常工作,尽管我知道它们很糟糕。

            • 命名是计算机科学中的一个难题。不被强制要求命名某物确实很不错。

        • 如果你习惯于进行大量函数式编程,多行lambda表达式是非常自然的选择。其他时候,你可能希望使用一个或多个变量,而无需将其作为参数传递。这尤其有意义,如果你已经习惯在Java/C++/JavaScript/Go中这样做。

          它比命名函数“更好”吗?当然不是,它们基本上工作方式相同。但我们不是在讨论好坏。我们只是在讨论语法本身,因为有些人更喜欢以一种你可能并不在意的的方式编写代码。

          • 这很有道理,我在另一条回复中也得出了类似的结论。

            我一直认为函数式编程的吸引力更多在于可测试性和并发性,我从未想过有人会更喜欢语法本身。

            • 当然,我不能代表其他人发言,但对我来说,我选择函数式编程的一个重要原因正是因为我发现其语法非常表达力强。我觉得我可以直接表达我的意图,而不是描述一系列步骤并希望我的意图能够实现。

              不同的人有不同的喜好,当然不是每个人都喜欢函数式编程,而且对它也有一些合理的批评,但我一直欣赏它与命令式编程相比更加简洁和简单。

              即使在可测试性方面,如果你的函数是纯函数且不涉及IO操作,那么即使使用多行lambda表达式也不会影响太多。你只需测试函数的调用即可。

              需要注意的是,Haskell并没有像Python那样的“循环”;在Python中,你可能不需要lambda表达式,因为你可以将一次性逻辑放在for循环中实现。在 Haskell 中,你拥有 map、filter、reduce 和递归,基本上就是这些。

              • > Haskell 实际上没有“循环”

                嗯,你仍然可以在 Haskell 中做类似的事情:

                     import Data.Vector.Mutable
                     ...
                     let n = length vec
                     forM_ [0 .. n-1] $ i -> do
                       next <- if i < n-1 then read vec (i+1) else pure 0
                       modify vec (+ next) i -- 将该值增加 next 值
                

                只是在许多情况下,函数式编程的工具如此易于使用,以至于你不需要使用 for 循环。

    • 显然,作者以前是Java开发者。我有一种感觉——或者偏见——认为仍处于Java世界的人与科技行业有些脱节,他们通常在大型零售或美国银行业务中处理庞大的遗留项目。这些人倾向于保守且抵制变革,因此这类文章可能源自于此。

      • Java团队确实是我见过最看不起Python的地方。根据我的经验,最强烈的情感往往来自那些实际上没有多少Python经验的人,甚至可能对除Java以外的任何语言都没有多少实际经验。因此,他们倾向于认为Python客观上 inferior,仅仅因为它是解释型且动态类型的。

        在之前的一份工作中,我确实成功地改变了这种看法,当我演示用Python实现替换我们其中一个Java服务时。代码量只是原来的很小一部分,却实现了更好的延迟和吞吐量。显然,并非每个Python程序都能做到这一点。但我的观点不是Python更好,而是这类事情从来都不是非黑即白的。许多非平凡的 Python 程序只是围绕着一个经过优化和实战检验的 C/C++/Rust 代码核心的薄壳。而 Java 方面,如果你在使用泛型时不够小心,也会因指针追踪和运行时类型查找(以及 GC 翻转)而积累大量动态语言风格的性能损失。一如既往,魔鬼藏在细节中。它也更难玩“其实我是C++”的把戏,因为使用紧凑型垃圾回收器会让与原生代码互操作时面临高昂的序列化开销。

        • 我对 Python 的经验远多于 Java。在至少 4 家公司(包括我不会称之为 Java 公司的 Google)中,我们主要使用 Python。

          尽管如此,我认为 Java 更适合应用程序开发。Python 更适合脚本语言(替代 Bash)。小型应用程序在 Python 上更容易实现,但大型应用程序在 Java 上要容易得多,无论是语法(类型)还是生态系统(库)。

          • 基本上同意,不过我认为对于规模适中且没有大量(不适合 NumPy)计算负载的服务,Python作为默认选择会让我更满意,因为这类服务通常没有迫切的多线程需求。这在我们现在都陷入云计算环境时尤为常见。正如你所说,小型应用程序在 Python 中更容易实现。

          • 这也是普遍看法。许多小型应用程序/模拟器使用Python。运维脚本通常使用Python。Java用于核心/数据部分。在我看来,当处理10万行代码库时,Java在重构/工具化方面更容易。TypeScript始终是首选。

            在两个生态系统中都见过不少编码灾难……

            • 算法用Python编写时更容易理解。我目前正在用伪代码文档化一个中等规模的Java代码库,伪代码看起来像Python。仅仅通过这种方式,我已经发现了多个在查看Java代码时未察觉的 bug。

              • 试试 Kotlin 吧。

                我不会称它为新语言,对我来说它只是 Java 的语法糖,但对于任何问题,你都会搜索“如何在 Java 中实现 X”,而不是“如何在 Kotlin 中实现 X”。

                但在 Kotlin 中你可以使用更简洁的语法,比如:

                    0..100 米,-45..45 度,3 秒内
                

                因为“0..100 米…”等同于“(0..100).meters(…)”

                (0..100) 是内置的 IntRange 类型,你可以扩展它:

                    data class MyDistanceRange(val meters: ClosedRange<Double>)
                
                    val IntRange.meters: MyDistanceRange
                        get() = MyDistanceRange(first.toDouble()..last.toDouble())
                

                以及

                • 我玩了一会儿,但它不够吸引我,让我花时间好好学习它。我对 Python 的兴趣也仅限于学习算法和可能的一些较小的 Pyside 界面。然而,与 Mojo 结合使用时,它可能会再次变得有趣。

                • 我认为Kotlin有一些特性使其远不止是Java的语法糖。比如,我知道你可以用一些令人眼花缭乱的Gang of Four模式来实现与扩展方法相同的效果。但维护起来会如此麻烦,以至于称Kotlin的特性是相同事物的语法糖,就像说函数定义只是汇编语言调用约定上的语法糖一样有用。

              • 这可能是许多讨论中被忽视的关键点。我同意我不希望维护 100k 行 Python 代码。但我不认为这在实际业务应用中是一个现实的假设。符合 Python 风格的代码通常只需 Java 风格代码的一小部分就能完成相同任务。唯一接近这种情况的是当代码由刻意模仿老派企业级Java风格的人编写时。因此最终会积累大量类似以下内容:

                  class IWantToBeABean:
                    def init(self, arg1: int, arg2: str, arg4: str) -> None:
                      self._field1: int = arg1
                      self._field2: str = arg2
                      self._field3: str = arg3
                
                    def get_field1(self) -> int:
                      return self._field1
                
                    def set_field1(self, value: int) -> None:
                      self._field1 = value
                
                    def get_field2(self) -> str:
                      return self._field2
                
                    def set_field2(self, value: str) -> None:
                      self._field2 = value
                
                    def get_field3(self) -> str:
                      return self._field3
                
                    def set_field3(self, value: str) -> None:
                      self._field3 = value
                

                而实际上可以简化为:

                  @dataclass
                  class IDontWantToBeABean:
                    field1: int
                    field2: str
                    field3: str
                

                Python最糟糕的情况是,有人用老派Python的方式,认为动态类型和鸭子类型意味着可以随意混用类型。Scikit-learn 就是一个典型的例子,其各种函数的返回值类型和结构,甚至它们能够处理的数据类型和结构,都会根据函数的参数而有所不同。而且往往以一种未明确记录的方式变化。有时规则甚至会在次要版本升级时悄然改变。

                大型 Python 代码库之所以特别令人望而生畏,并不一定是因为代码库本身的规模。而是因为一个代码库要达到如此庞大的规模,很可能已经存在了很长时间,因此它至少有一个主要贡献者喜欢做这种花哨的小把戏的可能性接近 1。我宁愿选择像上面 Java 示例中那样过于冗长的代码,也不愿看到这种情况。

                • 嘿,你忘了为每个方法添加无用的注释:

                      get_field1(self) -> int:
                        “”"
                           获取 Field1。
                  
                           @rtype: int
                           @return: field1
                        “”"
                        self._field1
                  

                  并且使用双下划线表示私有字段,因为你知道,这是封装。

                  说真的,在Java中你也不需要get*()。通常你会有以下选择:

                  1. 使用`record`类型(自Java14,2020年起),或

                  2. 使用Lombok自动生成它们[1],或

                  1. 使用 Kotlin 的 data class,或

                  2. 直接访问字段。如果我的代码不是供互联网广泛使用的库,我不会在意封装性。

                  [1] https://projectlombok.org/features/GetterSetter

        • > Java 方面,如果不注意使用泛型的方式,也可能积累大量动态语言风格的性能损失,如指针追踪、运行时类型查找(以及垃圾回收的频繁发生)。

          Java 最糟糕的地方在于 Java 程序员的平均水平。Python 可能也存在类似问题。不过我认为尝试编写 AbstractFactoryFactory 的 Python 程序员比 Java 少。Java 存在一种糟糕的文化,即过度冗长、层级过深的继承树,这会恶化调试和开发体验,并导致性能下降。

          • 我确实这么认为,但不确定这是否只是因为我对Java不熟悉或缺乏高级编程知识。我尝试对一个Java项目(Guacamole)进行一个看似非常简单的补丁,结果发现必须将新功能添加到基类、抽象类和接口等中,这简直令人发狂。所有这些冗余代码也让人头疼。

      • 我有一种相关的感受或偏见,认为那些不在Java世界中的人会将自己的领域视为“科技行业”,却没有意识到与Java世界这个庞大的都市相比,他们所处的只是一个小村庄。

        Java程序员可能不常写博客,Java也不常出现在Hacker News上,但不“极度在线”并不意味着它不被真实用户广泛使用,而这些用户的经验同样具有价值。

        • 你可以对其他十几种语言说同样的话,但确实存在一种刻板印象中的古怪Java开发者,他们对编写本应极快但实际却不尽如人意的企业级应用感到沮丧。他们沉迷于冗长的代码,并看不起那些他们认为走捷径的人。如果你从未遇到过这样的人,那你很幸运,但在我工作过的每家公司都存在这种人。

          • > 你也可以用同样的话来形容其他十几种语言

            不,你不能。你只能用同样的话来形容另外四种语言:PHP、C、C++ 和 C#。

            没有其他语言在用户基数上能与之相提并论,同时在在线技术讨论中又如此低调。

            我同意确实存在老派的Java开发者(以及老派的C、C++、PHP等开发者)。再过十年或二十年,也会出现老派的TypeScript开发者。这只是技术生命周期和职业道路的本质。有些人随着年龄增长会对学习感到相对疲倦,只想利用已掌握的知识来度过剩余的职业生涯。

            • 我认为我在 30 多岁时曾刻意努力,以免成为老顽固的 Python 开发人员。但我预测,再过一两个十年,我(现在 40 多岁)就会成为老顽固的 Rust 开发人员。

            • 好吧,我可能有点夸张,但确实有不少人默默地在Fortran、Lisp、COBOL、Perl、Pascal、R、汇编语言、各种BASIC语言、Lua、TCL、Erlang等语言上编写和维护关键任务代码,列表还可以继续下去。而且这些只是我过去十年中亲眼见过的人使用的语言。我总是对以下事实感到震惊:

              这些语言很少能登上HN(Reddit热门新闻)或其它通用论坛的榜首。相反,Java和Python占据了热门席位,而那些默默支撑系统运行的无名英雄却被遗忘。

        • 我曾从事.Net开发,后来转投Python阵营。这与Java的繁荣景象如出一辙,只是缺乏大量计算机科学毕业生。

        • 如果TIOBE指数是一个稍微有点用的指标,那么Python就是一个更大的巨型都市。

          • 是的,但这里的区别在于,Python被不断讨论,所以在线技术世界知道Python是巨大的。

            我回复的评论似乎认为Java是一个偏僻的小地方,这完全不是事实。

            • 我更倾向于将这句话理解为“Java开发者相较于其他社区,往往显得格外封闭和保守”,而非单纯认为“Java很小”。

              考虑到就在四年前,我还在参与一个项目,该项目仍需强制支持在Java 7环境下运行,而这种情况当时并不被视为异常,因此我无法对此观点提出强烈反对。是的,那时候Java 7仍处于扩展支持周期内,因此从Java开发者的角度来看,这确实没什么不寻常或令人惊讶的。但这正是关键所在。

              考虑到运行在Java上的各种应用,这其实并非坏事。大型机开发者也有类似情况,原因同样合理且有益。

          • 我不确定他们指的是语言覆盖范围的广度,还是说在忽略语法臃肿后Java的先进性。JVM能够实现其他环境难以实现的功能。

            我记得比如在运行时更新包/代码,快速重新编译关键路径。也许在Borg/Kubernetes环境中这并不必要,因为你可以直接重启服务让负载均衡基础设施处理,或者运行较慢的代码因为加速CPU密集型库后计算成本并不高,但无论如何这都很酷。

        • 作为在多家Java公司工作过的人,这绝对正确。不过,这也是Java的缺陷,因为对于不参与Java生态系统的人来说,几乎不可能在没有工作相关接触的情况下了解其内部机制。

        • 别忘了Python比Java早了大约4年。

        • 你可能已经知道这些,只是在针对Hacker News的群体。但我读了你的评论,心想“他难道认为Java比Python大得多?”我甚至不确定Python是否更小。

          编辑得更简洁

        • 人们仍然使用Java(或其衍生语言)编程的原因是,他们正在处理的遗留代码是用Java编写的,而没有人具备将这些代码翻译成其他语言的技能或时间。这意味着使用Java的岗位基本上都是大型企业中那些存在已久、技术含量较低的软件项目。

          Log4shell事件完美地展示了Java世界中的人群类型。

          • > 没有人具备将这些代码迁移到其他语言的技能或时间。

            迁移到哪里?Java仍是一种高效、生产力高的语言。将遗留代码库更新为使用Java的新特性可能是明智之举,但迁移到其他语言 unlikely to significantly move the needle in terms of runtime performance or developer velocity.

            • Java的大多数应用场景都存在网络延迟占主导地位的情况。如果Python对Uber和YouTube来说足够快,那么对你的服务来说也足够快。

              当你处理不需要编译的代码库时,开发速度非常快。你不需要编译,可以随时原型化功能,甚至可以在Web服务器运行时编写实时更新。

              而Java的标准编译过程在Groovy中非常低效——Groovy会被编译为字节码,然后通过JIT编译为本地代码,接着实际执行源代码到字节码的编译,最后在首次启动时因JIT编译为本地代码而产生延迟。

              要编写任何现代应用程序,你真正需要的只是 Python + C。任何需要快速运行的功能,只需将其封装在一个小型 C 程序中,然后从 Python 中调用该程序。

      • 我感觉情况恰恰相反。人们将 Java 视为 2008 年的企业 Java;一种笨拙过时的语言,用于编写冗长混乱的代码。

        事实上,许多最有趣的编译器和语言研究成果正应用于实际场景(如Project Loom、Graal等),而现代Java的特性(如模式匹配、记录类型等)使其成为许多非传统企业应用的理想选择。

      • 我最近在一次会议上发表演讲,讲述现代Java其实并不糟糕,而Java最糟糕的部分是Java开发者,他们似乎完全缺乏进取心。

        我认为Java本身不会让人缺乏野心,而是因为Java在学校被教授,缺乏野心的人觉得没有必要学习其他东西,他们最终在缺乏野心的公司找到工作。这种选择偏向于那些不想学习更多知识的人。

        相比之下,像Clojure或Haskell这样的语言,通常不在学校教授,且就业前景不佳。学习这些语言的人通常是主动寻求这些知识,因为他们对此感兴趣。这种选择性偏向于那些在智力上充满野心的人。

        因此,对于像我这样的人来说,Java开发者可能会让人难以忍受。

        那些开发Java的人在过去二十年里确实让这个平台和语言变得相当不错,但我之前在一家公司工作时,不得不为使用NIO而斗争,我认为NIO是在Java 4中引入的,但我的同事们很少有人听说过它或使用过它,因为传统的Java阻塞IO已经“足够好”了。

        • >现代Java其实并不糟糕,

          考虑到Lombok仍然被广泛使用,其底层功能本质上是篡改抽象语法树(AST),或者注解处理器将Java代码写入文件的事实,或者你可能在使用Log4j这样的标准库时,系统中存在重大漏洞,因为有人认为日志语句能够执行代码是个好主意,而没有人提出异议, 或者 Kotlin 和 Groovy 实际上就是为了解决 Java 的低效问题而创建的,等等……

          嗯,我不太确定你是如何得出这个结论的。

          • 这正是我的观点。例如,现在很多情况下 Lombok 其实并不必要,因为 Records 已经提供了你使用 Lombok 时会用到的很多功能。

            Kotlin 和 Groovy 确实解决了 Java 的问题,如果你雇主允许的话应该使用它们。我只是说 Java 21 其实挺有趣的。

            是的,有些库确实不安全,但这只是 Java 30 年历史中的一个例子。

            我只是觉得 Java 在过去二十年里确实有所改进。是工程师们没有进步。

        • 如果录音有的话,请提供链接。

          我认为我在HN上看到的一条评论说,如果以Pythonic的方式编写,Java会更好。我完全同意这种观点。

          但是,在Java中,确实有一些“复杂性商人”,他们更喜欢以最抽象的方式做事,而不是简单的方式。

          顺便说一句,Java可以像Go一样简单。

          • 我认为这场演讲尚未在公共YouTube上发布(他们明确表示不要公开分享未列出的链接),但这里是帖子链接:https://www.lambdadays.org/lambdadays2025/thomas-gebert

            幻灯片在此处可供查看:https://github.com/Tombert/lambda_days_2025/blob/main/slides…

            我担心我的幽默感在幻灯片中没有得到充分体现,但请想象这里的所有内容都是以一种略带讽刺的语气表达的。

            Java 在某些方面可以与 Go 媲美,阻塞队列和虚拟线程可以让你走得很远,尽管它们不如 Go 频道那么优雅,因为你无法像使用 Go 频道那样在多个阻塞队列之间进行选择。

            总体而言,我认为Java 21其实并不算太差。令人惊讶的是,我甚至有时会享受编写Java代码的过程。

      • > 我有一种感觉——或者偏见——认为仍然留在 Java 世界的人与科技行业有些脱节

        除了年龄歧视,这是错误的;或者错误地归因于 Java。我每天都使用 Python,而缺失的是静态类型和利用它来大大减少我需要记住的代码和上下文的 IDE。Python(显然是一种动态类型语言)维护起来令人筋疲力尽。但编写起来很简单。相比之下,Java/C#/任何静态类型语言搭配优秀的IDE,编写和维护都更轻松。

        当然也有Python和动态类型语言的IDE,但我尝试过的所有IDE都无法与最佳的Java/C# IDE相媲美。

        静态类型与动态类型曾是互联网上一场激烈的争论,但过去几年我遇到过从未听说过这一话题的人。这就是现状。

      • 我认为亚马逊(amazon.com)和AWS至少有一半是基于Java运行的。

      • 我一直觉得X远胜于Y,更别提Z或W了。

    • 我个人不喜欢Python,但令我惊讶的是,有人会认为它普遍不受欢迎,因为这绝对不是我从古至今的印象。我怀疑它已经过时了。更可能的是,认为某件事其实并不糟糕已经成为当前的点击诱饵框架。

    • 十年前,Python在生产环境中还相当粗糙。当然你可以让它工作,但你必须在以下方面做出很多妥协:

      – 环境/依赖管理

      – 类型安全

      – 性能

      正如作者指出的,这些问题现在大多已通过uv、类型提示、pydantic、FastAPI等技术得到解决。

      • >十年前,Python在生产环境中还相当粗糙。

        其实不然。

        环境/依赖管理从来都不是一个真正的问题。人们总是认为只要更新一个版本,系统就会出问题。即使如此,venv 就是为此而存在的,你可以在 setup.py 或 requirements.txt 中指定版本。

        类型安全在理论上应由单元测试覆盖。如果你在动态环境中意外地将一个变量赋值给另一个变量,比如将字符串赋值给字典,那么你的功能就会出现问题。但实际上,使用不同的变量名并不难。根据我的经验,唯一会让人感到困惑的情况是,当你开始在 Python 中使用类似工厂模式、大量面向对象编程和继承等 Java 概念时。这些东西其实并不必要,你用字典就能处理大约 90% 的数据传输。而在非常注重类型安全的语言中,你花大量时间设计类型系统,而不是直接编写实现功能的代码。

        性能仍然较慢,但语言性能其实并不重要——你可以轻松从 Python 调用原生编译的代码。Numpy 就是基于这个概念开发的,而且已经有了 10 年历史。你永远无法超越 C 语言(通过显式处理器指令实现向量运算等功能)或 CUDA 代码,但大多数代码并不需要这种级别的性能。

      • 输入体验仍然非常糟糕。性能方面也一样,除非你处理的是与 NumPy 或 JAX 兼容的数值数据。

    • 我还没试过uv,但Python让我最头疼的是env的工作方式。我总是觉得它既痛苦又奇怪。

      向后兼容性,我猜这与必须使用env密切相关,也让人头疼。根据我的经验,在许多情况下你无法向前或向后兼容。这在那些很少改变的项目中尤其痛苦。我确信活跃的代码库可能能够在版本之间保持更新,但如果你已经等待了多个版本,这似乎很痛苦。

      但是,我不确定我是否给它一个公平的尝试,因为我没有需要。它在人工智能中的应用使其成为一个有吸引力的选择,现在比以往任何时候都更具吸引力。

    • 我认为是以下因素的结合:

      – 静态类型语言得到了改进,减少了动态类型的相对优势。人们意识到他们并不真的讨厌静态类型,他们讨厌的是[插入90年代至00年代的企业语言]实现静态类型的方式

      – 随着计算机核心数量的增加,GIL(全局解释器锁)变得越来越令人痛苦

      – 它曾经是热情的黑客的语言,就像现代的Lisp(参见那篇旧的pg文章)。“用Python编写”曾经是质量和工艺的标志。现在它是最常见的入门语言;数百万的编程训练营开发者将其列入简历。平均技能水平急剧下降。

      – PSF 多年来对打包/工具问题视而不见。与 cargo、nix 甚至 npm 相比,pip/venv 简直是恐龙。

    • > 这是从未学过它的初级开发人员吗?

      看起来更像是它已经失宠于转用 Go/Rust 的高级开发人员。

      • 我对 Go 一无所知。但 Rust 更像是 C 和 C++ 的竞争对手,对吗?如果这些语言与 Python 这样的脚本语言发生冲突,那就有些奇怪了。

        Python 与 Bash 或 JavaScript 等语言相比表现相当不错,对吗?(也许 JavaScript 更好,但我对此一无所知)。

        • Rust 具有一些语言特性(通常受函数式编程语言启发),允许你编写相当高级的代码。

      • 我认为这是自找的。Python 的异步功能……很遗憾。

        JavaScript 具有更直观的异步语法,实际上是从 Python 框架中借来的。

        出于某种原因,Python 开发者决定不基于现有成果进行扩展,而是从头开始重新发明轮子。

      • 无论资深与否,不使用 Go 进行 Go 相关工作、不使用 Python 进行 Python 相关工作都是疯狂的。

        • 我同时使用两者,更倾向于 Go,但觉得应该多做些 Python 项目以保持技能新鲜。

          似乎在“Python相关工作”中,有两个主要场景是“与只懂Python的人合作”和“AI/ML,因为有现成的包”。

          还有其他场景吗?

          • 我的意思是……稍微简化一下,Python用于脚本编写,Go用于服务器开发。

    • 我认为对我们中的许多人来说,当它被推广时,没有其他语言中类似的实践,这非常令人沮丧。Poetry曾经被推广为如何进行依赖管理,但并不明显它不一定是你的构建管理器。即使今天,我也不完全清楚管理项目的标准方法是什么。 🙁

      • 如果我再尝试一次,我会选择uv,它似乎好得多

        • 我一直使用Poetry有一段时间了。什么会让我想要/需要切换到uv?

          • – 性能:uv 的运行速度快到某些操作几乎无感。poetry 的依赖解析器以速度慢著称。uv 作为原生二进制文件,启动速度也更快。

            – 解释器版本管理:uv 可自动为每个项目管理独立的 Python 版本,无需使用 pyenv/asdf/mise 等工具。

            – 启动:只需 uv 二进制文件,即可处理任何 Python 安装,因此无需安装 Python 即可安装管理程序。

            – 环境管理:uv 会透明地创建和更新环境。无需加载 venv,而是使用 `uv run…` 代替直接使用 Python。

            总体而言,它使常见的 Python 项目管理任务变得简单透明。它还遵循标准(如项目定义元数据和锁定文件),而 Poetry 通常不遵循这些标准。

            • 我很快会关注 Poetry。目前,我记得我的构建从未因 Poetry 而变慢。我认为它对速度没有任何影响。

              我刚刚更新了部分项目的依赖项。我能理解这样做可能更快。不过我更新依赖项的频率不够高,因此并不特别在意。poetry run pytest是目前最慢的操作,而我确信其中大部分延迟已在我直接控制范围内。

              我对锁文件机制感到好奇。我以为这正是最初使用诗歌(poetry)这类工具的主要原因之一?uv是否有更好的锁文件机制?

              • uv支持标准化的锁文件格式。这意味着你无需依赖uv,因为它使用的绝大多数项目设置(如元数据、依赖项、额外配置等)均基于PEP规范。而诗歌(poetry)则为许多功能定义了自有格式。

                https://peps.python.org/pep-0751/

                • 啊,我看到这是比我之前使用 poetry 的项目更新的格式。迁移到这个格式有什么优势吗?

          • 也许你会对这系列文章感兴趣:https://www.loopwerk.io/articles/tag/uv/

            • 谢谢!我并不完全确定有必要改变,这有点奇怪。

              我很幸运,我的Python项目都相对较小,所以可能这在一定程度上影响了我的判断。当然,这看起来像是会让我在初期选择UV的因素,但目前的情况是,我主要希望有一个更标准/被接受的构建和发布工作流程。

              • 有道理。我已经不再使用Python进行开发,但经常遇到想使用的Python工具。uv/uvx对我来说是完美的。

          • 是的,Poetry 很好。

    • 我用 python 编程了很多年,但后来不再喜欢它了。

      它没有自带第一方包管理器,所以社区试图填补这个空白。使用其他具有良好工具的语言,比如 golang 或 rust,会让人感到耳目一新。

      Python 作为实际的 PL 是一种自毁式武器,因为它是动态脚本语言。(别跟我说要使用工具 X、Y、Z、mypy 等)你基本上成了检查类型、在执行不常见路径时解决基本语法错误的编译器。

      一种只适合 < 100 行脚本的编程语言并不是一个好的选择。

      我真诚地希望 python 能处于更好的状态。我转用 rust 了。

      • > 别跟我提使用工具X、Y、Z、mypy等。

        使用能解决常见问题的工具有什么问题?我认为没有这些工具我不会使用Python,但ruff和pyright能让Python成为一种非常高效且可靠的语言,只要你愿意完全接受静态分析。

      • > 仅适用于<100行脚本的编程语言并非明智选择。

        这简直是胡说八道。要展示用Python编写的远超这个字数且广受欢迎的实用程序,简直易如反掌,我甚至懒得去证明。

        这真是懒惰的批评。

        • 你好rsyring,我基于经验发表此评论。

          随着Python项目不断扩展,你需要进行大量测试和语法正确性检查等支持工作。而在编译型语言中,这类问题会在编译时作为错误被捕获,而非运行时错误。

          我个人更倾向于尽可能将更多错误转移到编译时。动态语言在运行时确实非常强大,但这种运行时灵活性是以编译时验证为代价的。

          当然,每个项目都可以用任何语言编写,只要付出足够的努力。大型且成功的 Python 项目并不意味着开发体验、开发效率或代码的健壮性。

          • 所有观点都完全合理,我同意你所写的大部分内容。但上面的评论与我之前有异议的评论在语气和努力程度上差异很大。:)

            事后看来,我本该置之不理,不做回应,这本是我通常的做法。但Python的流行并非偶然。它的权衡取舍对许多人和项目来说都合理。来自HN社区部分群体的低水平恶意攻击让我今天有些恼火,我觉得有必要说点什么。对于对您评论的不够建设性的批评,我深表歉意。

            祝好。

            • 感谢你的评论。我确实可以花更多时间和上下文来更好地表达我的观点。

        • 来自社区指南:

          > 在评论中

          > 保持友好。不要刻薄。以好奇的态度交流,不要进行审讯。删除攻击性言论。

          > 随着话题变得更加争议性,评论应更加深入和有 substance,而不是更少。

          > 当意见相左时,请针对论点回复,而不是人身攻击。“那太愚蠢了;1+1等于2,不是3”可以简化为“1+1等于2,不是3”。

          > 请不要大发雷霆。请不要嘲讽,包括对社区其他成员的嘲讽。

          > 请针对他人言论中最强有力的合理解释进行回应,而非选择更容易批评的弱化版本。假设对方出于善意。

          https://news.ycombinator.com/newsguidelines.html

    • 有大量开发者甚至从未想过尝试使用动态类型语言编程,尽管大型公司都采用了渐进式类型系统。他们不知道自己错过了什么。

      • 我对动态类型语言没有意见,但我对Python的主要问题是显著的空白。我真的不喜欢它。我能接受Python或其他任何编程语言的其他方面,但显著的空白是我无法接受的。

        • 即使写了大量Python代码,空格问题仍然是我不会默认选择它的原因。

          如今IDE已经好多了,但我仍然无法将显著空格视为任何优点。使用括号我可以缩进,但自动缩进每次都能成功。

        • 在一段时间后重新开始使用 Python 进行项目开发时,我发现对我来说,每次运行代码时自动修复空格是解决办法。

          我在 Justfile 中添加了一行代码来实现这一点。虽然在保存时格式化可能更好,但我使用不同的编辑器,还没来得及设置。

          这并不能消除对一种由制定空格规则的人设计的语言中“一切”的疑虑,但它消除了特定的痛点。

        • 你是指空格的要求还是空格占用的空间大小?因为你可以调整Python所需的缩进空格数量来减少空格占用。

          • 我写代码已有40多年。我知道如何设置我的编辑器,谢谢。

            如果你复制粘贴一些Python代码,而它没有正确缩进,它会破坏Python程序。这是我见过最愚蠢的事情,包括JavaScript(它并没有像许多人声称的那样愚蠢)。

    • 是的,我认为Python作为轻量级脚本和网页开发语言的地位,大多已被JS取代。

      它仍是数据科学的王者,当然它仍有大量用途和用户,但它在这些圈子之外并不酷或流行。

      • Python从未是唯一的网页开发语言,这一地位始终属于JavaScript。Python是服务器端语言中较为流行的选择,而JavaScript在过去十年间也从前端扩展到了后端。不确定这是否真的夺走了任何后端语言的桂冠。

        什么是轻量级脚本?难道脚本本身不就是轻量级的?

        但 Python 整体上在数据科学圈之外仍然非常流行。不确定这个说法是从哪里来的。

      • > 它仍然是数据科学的王者,当然它仍然有大量用途和用户,但它在数据科学圈之外并不酷或流行。

        这是一种极端的观点。你永远无法获得完全准确的测量结果,但所有我看到的来源[0][1][2]都将Python列为最常见的编程语言,遥遥领先。

        如果它看起来不“酷”或不“流行”,那是因为每个人都在使用它。

        [0] https://www.tiobe.com/tiobe-index/ [1] https://pypl.github.io/PYPL.html#google_vignette [2] https://www.pluralsight.com/resources/blog/upskilling/top-pr…

      • 人们真的会选择 JavaScript(而非 Python)进行轻量级脚本编写吗?我们讨论的是“更好的 Bash”用例吗?

        • JavaScript 开发者确实会这样做,而且人数众多

        • 我经常使用“tsx script.ts”,它非常不错,因为可以使用渐进式类型系统(而且我对这门语言很熟悉)

        • 我的意思是,如果它真的轻量级,而且我想避免使用Bash,那么JavaScript也不是太糟糕。缺点是与Python相比,内置函数并不丰富。

          著名的答案……_这要看情况_。

        • 那将非常令人惊讶

    • 当它变得足够大,以至于公司迫使人们学习它时。人们会自动讨厌那些他们没有自己选择的东西。

      此外,在过去十年左右的时间里,对动态类型的反对情绪一直在上升,这主要是由于一些自虐者。

      •     > 受虐狂
        

        嘿!受虐狂会带来回报。我用鸭子类型做的事,用 `dyn Trait` 滥用也能做到 🙂

    • 使用过 Rust 的工具后,Python 的工具就显得非常痛苦了。简直难以忍受。这让我们中的一些人对这门语言本身感到非常沮丧。因为 Python 的工具,我浪费了大量时间……这些时间永远都回不来了。

      但随后 uv 出现了。它不仅仅是另一个 poetry 或 pipenv 之类的工具。它运行良好,并且拥有 uvx 和 `uv tool install` 以及其他一些不错的功能。

      以前,当我看到某个东西是用 Python 编写时,我会在心里咒骂,因为我没有精力去创建一个虚拟环境,运行它的 shell 脚本,并记住把它放在哪里,只是为了尝试一下。在 arch Linux 上,我不能直接用 pip 安装东西,必须先进行一些疯狂的设置,以前我还有耐心做这些,但随着年龄的增长,我更愿意直接咒骂这种蛇语言,把时间花在更轻松的事情上,比如写 Rust。

      但现在我只需输入“uvx”就能运行。我可能不会很快重新开始写Python,但至少现在我对那些选择写Python的人少了一些失望。

    • 从v2到v3的过渡让很多人措手不及。

      • 我们能停止重复那些陈词滥调吗?那已经是几年前的事了。如今大多数Python开发者从未写过一行Python 2代码。我16年前开始学习Python,尽管当时像Django这样的框架正处于过渡期,但我几乎立即开始学习并编写Python 3。

        • 这可能是真的,但我偶尔还是会输入:

          print “xyz”

          这没什么大不了的,但它让我想起多年前与 pip->pip3 以及其他许多事情斗争的经历。

        • 哈哈,这让我想起当年我得意洋洋地告诉我的大一室友,用Python 3教程学习编程完全是在浪费时间,他应该像我们这些l33t hax0rs一样,一辈子都用2.7。

          说实话,当时 Django 在 Python 3 上还很不稳定,几乎没有第三方 Django 库支持它,而且我当时使用的是 Google AppEngine,而它当时与 Python 2.7 运行时紧密耦合。但说实话,我只是在重复Slashdot上的集体观点,当时大家100%确信这次迁移就像新的Perl 6一样会杀死Python,而且Python.org在更改默认设置时,正在欺骗那些不懂行的新手,教他们学习一种死语言和无用的技能。

          幸运的是,他忽略了我的建议,而大多数大型 Django 库大约在一年后完成了迁移,到那时我不得不切换以获取更新。完全同意,到 2025 年这几乎无关紧要,而且坦率地说,尽管存在一些合法的痛苦,这次迁移比当时的怀疑论者预期的要成功得多。

    • Python可能太过出色和灵活而不会过时,但作为长期用户,生态系统令人沮丧。依赖项和打包问题在经历了数千年的发展后,仍然是一场噩梦。问题当然可以解决,但关键是它们永无止境。在你积累了足够的经验来解决所有相关问题后,你将不得不继续为自己、技术水平较低的团队成员以及第三方代码解决这些问题,几乎永远如此。这在最初的100年里是值得的,因为你可以使用“import antigravity”,但最终会让人感到沮丧。

      每个人都会提到 uv/pyenv/poetry/conda/virtualenvs,所以好吧,假设你已经尝试过这些工具,而且它们都是家喻户晓的名字。假设打包战争已经结束,每个人都使用你使用的工具,而无需你告诉他们,并且假设每个 pypa 问题都不是在责怪 Debian 维护者导致明显的退化。PyPI 仍然会从源头撤销[0] 包,可能随机破坏确定性行为,波及范围内的任何内容,而不仅仅是使用 –strict 标志的客户端。你可以固定自己的依赖项,但谁知道他们会固定什么(可能什么都不固定,或者固定错误的内容,或者以错误的方式固定!)或者他们会撤销什么。为了实现可重复性,你需要为所有使用的软件包托管镜像——这对企业来说没问题,但对新手来说是不可行的,对开源项目来说也令人烦恼。

      如果你没有依赖项,或者你的环境变化缓慢且始终得到持续维护,你可能永远不会注意到问题有多严重。但如果你将大多数项目搁置一个月后再重新拾起,或许在不同的环境、机器或略微不同的 Python 版本下,项目已损坏、功能退化,需要大量工作才能恢复正常。

      有人可能会辩解……这只是软件开发的一部分。不,我以对社区努力的深深感激之情说出这一点……但大多数编程语言/生态系统绝不会容忍这种不稳定性。最终,我们不得不承认,要实现可工作、可移植且真正可重复的环境,Python基本上只能依赖Docker。我们来聊聊“–break-system-packages”这个选项有多荒谬吧?当你退回到Docker环境后,每天只需输入几次命令,将容器推送到仓库,数月或数年后再拉取下来,最终会发现,要获得一个稳定的工作环境,唯一的方法就是请求一个已损坏的版本。证毕

      [0]: https://docs.pypi.org/project-management/yanking/

    • 这并非过时。我们只是看到长尾效应终于开始显现。它正逐渐成为编程领域的通用语言。

    • 我认为在Python 2到3的过渡期(几乎持续了十年),很多人开始寻找替代方案,因为坦白说,那团乱麻是最后的导火索。在科学领域,这就是Julia开始崛起的时机。

      当然,随后加密货币热潮席卷而来,其余的便是历史了。

    • [删除]

      • 如果你能证明你的“设计糟糕”的论点,那将非常棒。有什么线索吗? 🙂

  27. 我喜欢编写Python。然而,有几点需要注意:

    1: 我不喜欢处理跨语言边界的问题——这很痛苦,尤其是涉及编译语言时——摩擦太大。编写起来容易,但维护起来困难。

    2: 调试 Python 可能……很痛苦。如果你有一个专用于纯 Python 的完美环境,并且所有工具都已设置好,那会很轻松。但如果你有像 C++ 这样更复杂的语言,它调用 Python 或反之,并且使用了一个不适合 Python 的编辑器,比如 QTCreator,那么突然间就变得像地狱一样。

    谁拥有这些数据!?这些数据在哪里初始化!?什么调用了这个函数!?这个数据是什么类型!?!?!?!这些问题在C++中对我来说轻而易举,但在Python中却异常痛苦。这让工作效率降至爬行速度。

    不得不回到打印语句、grep和手绘一个显示哪些函数调用了哪些函数的图表,只为弄清楚到底出了什么问题,这让人筋疲力尽。这种痛苦程度让我尽可能避免它。

    …更不用说部署时处理依赖问题了…

    • 我一直渴望有一个流畅的调试工作流程,可以在Cython中无缝地在Python和C/C++之间进行调试。我认为有一些技巧可以实现,但没有真正集成得很好的解决方案。

    • 当你严格调试 Python 代码时,Python 调试非常出色。设置一个 `断点` 或通过 `import IPython; IPython.embed()` 启动一个 IPython 实例来调查问题,让事情变得轻松。对于 neovim,你可以通过 vim-slime 轻松将代码发送到 ipython REPL。

      我从未需要调试底层 C++ 代码,除非在开发 C++ 扩展时。但这真的很难吗?只需将 lldb 指向 Python 进程,并在 lldb 中运行你的脚本即可。

      如果 C++ 代码不是你编写的,且假设它是一个成熟的库(如 PyTorch):这很可能是你在 Python 端引入的错误。

      • C++和Python都是我自己的。我遇到过C++调用Python和Python调用C++的情况。问题可能出现在任何一方。

        问题是如何从一方进入另一方,据我所知,这确实无法实现。

        Python 部分不够独立,无法单独运行。在运行之前需要进行大量设置,因此它实际上只能从顶级用户控制界面运行。

  28. 我认为 Python 语言一点也不优雅。我更喜欢 Ruby 的语法。

    但 Python 的工具链,尤其是 Astral 提供的工具(如 uv、ruff、ty),非常出色,我几乎总是使用 Python 而很少使用 Ruby。当然,丰富的库也是原因之一。

    • Jupyter 在 Python 生态系统中也是个很棒的工具,加上 Colab 等。

  29. > 猜猜人工智能的实际编程语言是什么?

    我以为 nodejs/typescript 似乎是大多数大语言模型(LLMs) 的默认选择?还是只是 v0/lovable/replit?(尽管 replit 有时似乎更适合非 js)

  30. 我理解“实际上喜欢它”的部分。

    我从20世纪70年代末就开始享受Lisp语言,今天使用Common Lisp和Racket让我感到快乐,就像今天清晨站在森林里喝咖啡让我感到快乐一样。

    但Python也是一门有趣的语言,像“足够好”和“实际上喜欢它”这样的说法是成立的。

  31. 作为一门语言,Python很不错。但Python的版本和包管理简直是一场噩梦。

    • 这已经有一段时间不成立了。uv极大地改善了使用体验。

      • 过去20年,这几乎成了口头禅。某种X能“解决”所有问题。

        但事实并非如此。它只是创造了另一个在一段时间内流行的 X,却无法追溯性地“修复”所有安装和升级都令人头疼的混乱项目。是的,我理解人们喜欢 Python。是的,我理解大语言模型(LLM) 兄弟们喜欢它。但在真正的生产环境中,对于真正的应用程序,你仍然希望避免使用它,因为创建用于工业用途的强大系统并不是一件容易的事。如果你能将这种混乱局限在某个数据中心并有人专门维护,或许还能勉强应付。

        • 我认为这种情况会持续到不再成立,而人们正在积极支持uv。

          希望它能真正解决Python包管理问题(许多人表示它已在其用例中实现这一目标)!

          • 解决这个问题至少需要Python维护者做出选择,将其集成到Python二进制文件中并坚持下去。至少。但这可能需要让一些人感到不满,直到a) 无论什么解决方案成熟,b) 人们接受它。

        • 我忽略了这些趋势,一直使用标准的 requirements.txt 文件配合 pip 和虚拟环境,过去 10 多年从未遇到问题。无论如何,你总是希望在生产环境部署时能及时发现问题。

        • >但事实并非如此。

          这只在你从未重新审视自己陈述的普适性时成立。我保证,解决Python生态系统混乱是可能的,uv已基本实现这一点,而你的先入之见正阻碍你利用这一成果。

          • 问题是:在Python开始进入我的职业生涯的20年左右时间里,从未缺乏过宣称这个问题已解决的人。(而在那之前的12-13年里,我乐得忽略它,因为没人在这方面做太多事情。)人们从黑莓时代就开始这么说了。

            多次有人解释为什么他们认为自己现在疯狂热爱的东西是最终解决方案。而在过去的几十年里,这些说法从未被证明是正确的。

            我理解你对某些事物充满热情。我明白。但或许你能理解,有些人需要看到事情真正稳定下来,才会宣布胜者?我对空想并不感冒。

        • 世界上最大的代码库中,有相当一部分是用Python编写的,这是一个充满hn优越感怪异声明。

          • 每个语言爱好者都会说世界上最大的代码库都是他们最喜欢的语言。关键在于:代码库的大小完全无关紧要。重要的是使用和维护程序的体验。

            Python并不是唯一一个工具链糟糕的语言。C/C++的代码库规模甚至比Python更大,而它的工具链简直糟糕透顶。

            有帮助的是,人们意识到工具和生产准备应该达到什么水平。他们可以从 Rust 和 Go 中学习很多东西。

            “它很大,所以它一定是对的”这种说法是无稽之谈。更糟糕的是:这种说法是为缺乏真正改进找借口的无稽之谈。

        • > 但在实际的生产环境中,对于真正的应用程序,你仍然希望避免使用它,因为创建适用于工业用途的健壮系统并不特别容易。

          这似乎有些荒谬,因为它忽视了科技行业及更广泛领域中“真实生产环境”中大量存在的 Python 代码库,其中一些代码库甚至支撑着价值超过 $10 亿美元的项目,而我敢打赌,其中许多代码库本身就是“健壮”且适合“工业用途”的,无需过多维护,仅仅因为它们是 Python 代码库。

          (我理解不喜欢某种语言或其生态系统,但我怀疑我几乎可以为当今最常用的前10种语言中的任何一种重写相同的回复。)

        • 我明白Python并非适合所有人,它确实有其缺点,也许uv只是另一个转瞬即逝的解决方案,和其他许多解决方案一样会来来去去。我可能不同意,但我可以接受这一点。但我无法接受的是,认为它不适合真实的生产环境,这坦率地说有点荒谬,考虑到所有在Python上运行的真实应用程序和真实生产环境。

      • 仍有20年的项目使用uv出现前的各种技术。它们并未在uv出现时立即升级。数据科学领域仍在使用其他垃圾技术。

        • > 它们并未在uv出现时立即升级。

          还有一些项目无法使用`uv`,因为它不喜欢当前的`requirements.txt`[0],而我没有精力去研究如何绕过这个问题。

          [0] 其中有一个从`git+https`安装的包,它出于某种原因强烈反对。互联网搜索没有发现有用的信息。

          • 与uv无关,但将git引用放在requirements.txt中的问题在于,pip会将其视为固定版本,从而严格限制可解析的其他依赖项,一旦这些包也从另一个git引用加载包,这种情况就变得极其难以理解。将所有内容放入codeartifact(或其他云平台的等效服务)是长期更好的解决方案。

          • 如果你打开一个简短的问题并标记我@zanieb,我很乐意看看!

    • 我估计,每两个知道什么是`pip`的Python用户中,就有三个对环境配置完全不感兴趣。考虑到这一点,专注于语言本身而非打包是正确的选择。打包问题通常是别人的麻烦。

      只是HN用户更可能成为那个“别人”。我们可能不得不处理非Python依赖项,所以我们转向了更大的工具,如Docker或Nix。如果没有一堆Python包管理器就好了,但无论我使用哪个,它最终都只是一个不太重要的中间层。

    • Python的包管理还行。主要问题在于.so库

      想想JNI或cgo管理。

      • 是的,我从未真正理解对Python包管理的抱怨——在多个发行版和操作系统上保证原生代码的构建从来都不是一件容易的事。

        这些原生包可以使用任何语言编写,并且可能需要任何奇特的工具组合来构建。谁真正解决了这个问题?

        • 如果你不需要链接 C 库,你可以为 Go 程序构建任何架构和操作系统的组合。默认工具集允许你轻松做到这一点。

          如果你需要链接 C 库,可以设置编译其他操作系统(可能还有其他架构)。

          • 以 numpy 为例,它是 GFortran 与 C 的混合。cgo 是如何处理的?

            还有 ffmpeg….

            • 如果你有对象文件,只要拥有编译器工具集,就可以编译任何内容。我曾编写过一个 Go 程序,该程序链接了 ffmpeg,并从 Linux 编译到 Windows 和 macOS。虽然过程并不轻松,但确实可行。

        • wheels、conda、docker、firecracker、unikernel、flatpak

          • flatpak、docker?也就是说,为了让一个包正常工作,你需要包含一个几乎完整的发行版?但如果你想让这个包在当前的发行版中工作呢?如果你需要两个不同的包,它们分别在不同的flatpak或不同的docker镜像中呢?

            • 将它们分别部署在各自的容器中,并使用 GRPC 进行通信…

              (说真的,我见过有人这么做)

              • 天啊!

                • 嗯,既然你已经在使用 GRPC,而且已经有了协议缓冲区… 在安装了几个小时的破包版本后,很难 resist 这种诱惑。

                  顺便说一句,我怀疑这是微服务的精神起源…

            • 显然不要首先尝试这些,我只是说,无论你想要多高的隔离级别,都有很多选项可以选择。

              如果你只需要预编译的二进制文件,可以从轮子开始;如果你需要与系统包相关的架构和操作系统特定的C库,可以升级到conda;如果你不想让这些库影响主机系统,可以使用flatpack或docker;如果你需要内核模块或硬件虚拟化,可以选择unikernel/firecracker/VMs。

      • 我再加个BLAS和LAPACK。天啊,让SciPy运行起来一直是个噩梦。我总是会得到一堆混杂的conda和pip安装的软件,就是无法正常工作。

        • 真的吗?在新的环境中运行pip install scipy对我来说一直都正常。你遇到了哪些具体问题?

      • 这不行(也许uv能解决这个问题?)

    • 说实话:有了 uv,情况其实没那么糟糕了。当然,整个打包生态系统仍然很糟糕,但 uv 抽象了其中大部分。除非你在做一些非常冷门的事情,否则你会没事的。

    • 使用 Nix 开发环境对我来说运行脚本效果很好。但我还没弄清楚这种工作流程在大项目中是什么样子的。我对学习UV不感兴趣

      • 作为Nix用户,你几乎不需要了解UV,但你可能需要知道如何与使用UV的非Nix用户合作。这很简单。

        会有一个文件:uv.lock你可以使用 uv2nix 获取项目所有 Python 依赖项的单个 Nix 包,然后将其添加到你的开发环境中,就像在 nixpkgs 中找到的其他包一样(例如与 uv 本身并列)。实际上,获取该软件包只需两三行代码,但您只需将大语言模型(LLM)指向 uv2nix 文档和您的 flake.nix,它就会为您搞定。

        然后,您的开发环境将跟踪其他开发人员对该项目的依赖项所做的更改。如果您想修改它们…

           编辑 pyproject.toml # 包名称和版本
           uv lock             # 将名称和版本映射到哈希值(Nix 需要哈希值,会在 uv.lock 中查找)
           nix flake lock      # 根据 uv.lock 更新 flake.lock
           nix develop         # 现在你的开发环境也拥有这些依赖
        

        这样您就无需维护项目依赖关系的多个数据源,普通用户也无需关心您的 Nix 魔法。

        • 我不是说我没兴趣吗?

          • 你创建了一个Nix开发环境与UV交互相关的场所,我放了一些东西在那里,以帮助对这些主题感兴趣的路人。这其实不是专门为你准备的。

      • 没什么好学的。让Claude代码将UV安装到你的开发环境中,并切换你的依赖项。5分钟搞定。

    • 它类似于C/C++的(通常)解释型等价物。有许多“标准”的包管理选项。

      而且似乎包解析终于默认是本地化的,尽管这需要一个“虚拟环境”,这似乎是全球包管理系统的遗留问题。

  32. > 我大约6个月前开始更多地用Python编程。为什么?显然是因为AI。对我来说,很明显,如今AI领域到处都是大钱的机会。

    我觉得这很令人沮丧。大语言模型(LLMs)不仅在暗中削弱我们的思考和决策能力,现在还让人们自愿地顺从一些较低的共同标准。

    这就像人类决定在这个时间点停滞不前(这是一个多么糟糕的选择),不再探索其他方向。只有大语言模型吐出来的东西才是有效的。

    • 嗯,我第一次通过人工智能编写了一个Chrome扩展程序。如果它能自动化处理那些枯燥的工作,我对此的接受度会更高。

    • 人类试图节省时间并将其分配给其他追求,别担心

  33. 我当时想,从什么切换过来,后来发现一个脚注“我以前主要是Java/JavaScript/R这类人”

  34. 如果你在做机器学习,最经济的选择是Python。

    但如果你用过C#并发编程,用Python编写处理管道会很令人沮丧。

    我认为最好的选择是Celery,但你无法在没有外部代理的情况下使用它。Celery一团糟。我真的很讨厌它。

    • 同意。我认为它有所改进,但Celery作为事实上的任务/队列解决方案仍然令人沮丧。很多默认设置使其不可靠(如果工作者崩溃或未干净关闭,它可能会丢失任务)

      我希望自由线程的存在能推动更多原生并发原语的出现。并发未来对象(Concurrent Futures)不错,但当你需要除队列之外的并发安全数据结构时就行不通了

    • 同意Celery一团糟,且与异步(Asyncio)Python配合不好。我认为版本6可能将来会支持它。

      我也遇到了很多与 SQLAlchemy 中的异步原语相关的问题——asyncio.gather 与 TaskGroup 之间存在一些复杂的交互,以及 SQLAlchemy 会话如何与之配合以实现代码的轻松组合。

  35. 我看不出来 OP 在 Python 上层添加的工具如何解决“该语言不适合生产环境”的论点。显然,Python 完全适合生产环境。

  36. >> 我更倾向于使用单仓库结构

    没有什么比拥有大量小型仓库更令人烦恼,每个仓库都包含仅有几百行代码的微型项目,但(当然)你需要其中大部分/全部才能完成任何工作。在我看来,除非有明显理由需要拆分,否则应一直使用单仓库结构。

    • 根据我的经验,按“领域”划分仓库效果最佳。这通常意味着按团队划分,但你不想因为团队重组导致代码迁移问题。而且,只有在组织压力迫使的情况下才这样做。

      另一方面,我们有一个拥有50多个团队的组织,我们的运维团队正迫切希望采用单仓库。他们完全接受一个团队的代码推送迫使N个团队进行意外部署并回收缓存、连接等资源。更不用说当团队A因其他组织压力无法处理团队B的合并时会发生什么。

      • 他们为什么想要单一代码库?当前情况存在哪些问题?

        • 一致的CI/CD、代码发现与共享,以及(我认为是错误的)防止向后兼容性问题。

    • 我认为TFA所说的单仓库是指不将后端和前端拆分为两个不同的仓库。

      对于个人项目,我确实能理解小型项目采用单一仓库的价值。

      • 我指的不是个人项目。如果团队规模少于100人,我认为单一仓库是合适的。

  37. 当然,世界上最常用的编程语言使用起来不会令人头疼。如果它使用起来很麻烦,怎么可能成为第一名呢?

  38. 在你使用Python之前,你选择的编程语言是什么?

  39. 我一直想写点类似的内容,但从“如何简化它”的角度探索——我的世界很大程度上是Kubernetes(在合适的场景下很棒),但你能从一个为单人开发快速迭代设计的堆栈中削减复杂性吗:

    例如,而不是:

      > 在项目界面中不要进行任何复杂的数据处理步骤……我们保持浏览器应用程序轻量级,同时将繁重的工作和业务逻辑委托给服务器
    

    削减复杂性,直接从后端提供HTML

      > ty
    

    我很好奇 ty 的去向,但对于一个低复杂度的堆栈,我不想在预发布工具上花费复杂度资源

      > pydantic … dataclasses
    

    二者选其一,而且我会永远搞混:是 postinit(dataclasses)还是 postmodel_init(pydantic)——我不得不查证!

      > docker
    

    如果我们已经有了 uv,是否可以不用 docker?在正确配置下,uv 同步可以提供几乎与静态编译二进制文件相媲美的体验。它无法处理卷等功能,因此如果你使用了 docker 功能,这个概念行不通。如果你不依赖 Docker,是否可以在开发和生产环境中仅使用 uv?在企业环境中可能不行,我不会期望在生产环境中下载依赖项。但如果你独自开发……

      > compose
    

    你有一个前端、一个后端和一个数据库。是否可以仅使用 uv 管理后端和本地 SQLite 数据库?

    一个功能上大致相当的堆栈,用于快速迭代且复杂性更低,但仍具备所有高级功能,这样你无需自己从头开始实现一切,可能看起来像:

      - uv
      - fastapi + jinja + htmx + surreal + picocss
      - sqlite
    

    如果你需要,你可能可以规划一条通向超大规模的道路:

      - v1 = 上述技术栈
      - v2 = 将 SQLite 替换为 PostgreSQL,现在您解锁了多写入器和水平扩展功能,可能使用 py-pglite 进行测试环境,以便在下次扩展迭代前推迟采用测试容器。WAL 流式传输会为这一步增加一些复杂性,但值得尝试
      - v3 = 引入容器、容器注册表和测试容器。但我认为这一步在增加复杂性的同时,实际解锁的价值并不高……
      - v4 = RKE2 单节点集群,目前无需负载均衡器
      - v5 = 扩展至三节点集群,此时需要负载均衡器
      - v6 = 在 RKE 集群中添加代理节点
      - v7 = 能否优化成本?或许可以将不变部分重写为更节省资源的架构
      …
    
    • 如果你想从后端提供 HTML,为什么不使用 FastHTML 呢 😉

      • 我尝试过它,我喜欢这个概念。讽刺的是,我原本最期待的部分(用函数或可调用对象的组合替代Jinja的扩展机制)反而让我不太适应。我不知道为什么,纸面上它符合我的预期。但在实际使用中,我感觉速度较慢。

  40. 感觉大多数人都是从其他语言转到 Python 的。不知道迁移率是多少。

  41. Rust 有类似的东西吗?作为一个主要有 NodeJS 经验的人,我想深入研究一下 rust 的“api_starter”!

  42. Python 是所有方面都排名第二的语言。

  43. 我好奇这位可怜的人是从哪个语言转过来的。

    • 根据文章脚注,是Java、JavaScript和R。说实话,这让我对前文的看法有了很大改观。

      “我从全身沾满泥巴到只沾了一半泥巴,这太棒了!我不明白为什么有人会抱怨只沾了一半泥巴。”

  44. 我本以为这个评论区会充满开发者的无谓争论,结果果然如此。

    更严肃地说:看到这篇文章与我自己的 Python 项目布局如此相似,真是令人着迷和有趣。过去十年间,工具和实践已经有了长足的进步,良好的开发标准往往会以一种自然的方式成为事实上的标准。

    我认为 uv 已经赢得了成为 Python 环境管理新标准的比赛(Poetry 确实努力过,但最终在速度上败下阵来)。

  45. 我不明白“切换”到任何东西的意义。工作选择工具,而不是相反。

  46. “任何工作的第二好语言”

    • 意思是最佳语言取决于工作?

    • 有很多工作场景中,.Net/Java/Go这三种语言会位列前三。我将它们归类为主流、高性能、带有垃圾回收机制和丰富库的类型化语言。

      • 我在一家Java公司工作。所有面向客户的应用程序都使用Java开发。我们的工具和胶水代码主要使用Python。数据处理工作流也是类似情况

        • 是的,这可能说明了问题。Python会是您为面向客户的应用程序选择的第二语言吗?

          • 遗憾的是,实际操作可能并不容易。获得某些认证和通过审计已经花费了数年时间,我认为现在添加另一种语言的成本可能无法收回。

  47. 从什么语言切换到Python的?R?Java?JavaScript?

  48. “我大约六个月前开始更多地用Python编程。为什么?显然是因为人工智能。在我看来,如今人工智能领域到处都是巨大的商业机会。而猜猜人工智能的事实上的编程语言是什么?没错,就是那个狡猾的家伙。”

    为什么是这样?

    为什么选择Python进行人工智能开发?

    • 因为在许多人工智能库中,如PyTorch、TensorFlow等,Python都得到了第一流的支持……虽然这些库也可以从其他编程语言中使用,但对于Python来说,更容易找到良好的文档和示例。

    • 可能是因为许多可用的 LLMs 在沙盒中运行你的 Python 代码,这意味着对 vibe 编码者来说摩擦更少,或者至少这是一个促成因素。

      • > 因为许多可用的 LLMs 在沙盒中运行你的 Python 代码,这意味着对 vibe 编码者来说摩擦更少。

        这是错误的,许多非“vibe 开发者”使用 Python 进行 AI 开发,是因为 PyTorch 以及许多其他 AI 库都提供了对 Python 的原生支持。

        • 你怎么知道这不是原因或不是原因之一?对我来说,使用 ChatGPT、Claude 或任何支持它的工具似乎相当合理。

          我敢肯定有些人(可能包括这位作者)使用Python是因为他们的脚本可以在这些网站的沙箱中执行。

          天啊,如果它在Factor或Forth上的表现能像在Python上一样好,我也会写更多这样的脚本。

          无论如何,你不能断言这不是原因之一。你能吗?

          • 我读了你最初的消息,现在已编辑,这是主要原因。当然,这可能是作者选择Python的原因之一。

            此外,vibe coding部分让我觉得你暗示使用/选择Python进行AI的人都是vibe coders,这显然是错误的。如果我误解了你,请原谅,但这是我从你最初的消息中得到的印象。

            • 没关系。我认为存在误解,因为即使在我的原始消息中,我也没有打算暗示使用或选择 Python 进行 AI 的人都是 vibe 编码者,或者至少我认为我没有暗示这一点。

  49. pydantic 基类模型使数据类变得多余。

    • 数据类有一个巨大的优势,那就是它不是额外的依赖项。

      • 没错。除非你需要从外部来源序列化或反序列化数据,否则你其实不需要 Pydantic。如果你只是在内部传递数据,数据类完全足够。

  50. 无处不在的 VSCode——说实话,这真是一场悲剧。

  51. 我的 Python 应该使用哪个软件包管理器?我刚刚听说过 UV。

  52. 不讨人喜欢的观点:我想我会等待 4 版本的到来 /jk。但老实说,我已经被 Rust、Go 甚至 TypeScript 等现代语言宠坏了,它们拥有现代工具、强类型、稳定性和开箱即用的性能。目前,我只是与大语言模型(LLMs)进行交互,而不是构建它们。

    话虽如此,我记得几年前我给自己写过一条笔记,要避免 Python 项目。我不得不清理整个公司的代码,并使其准备就绪以投入生产。每个人都有自己的 Python 版本,requirements.txt 中缺少依赖项,两个依赖项与 Python 版本之间存在三方冲突,风格差异巨大,而且有拉入尽可能多库的习惯 [1])。即使回想起这些经历,我的胃都翻腾起来。

    我相信限制能让项目脱颖而出并易于维护。我更希望你给我一个真正的 Python 而不是一个 Python 项目。

    [1] 是的,我了解容器,我就是那个不幸写容器的人。

    • 我对Python没有特别的喜爱,但作者提到的所有工具实际上让Python现在使用起来相当不错。

      虽然还可以更好,但我认为Python现在真的找到了自己的节奏。

    • > 不受欢迎的观点:我打算等待 Python 4 版本

      在我个人时间线上,那些放弃等待 Perl 6 的人是早期 Python 开发者的重要来源。

    • 为什么 TypeScript 比 Python 更好?我不明白……

      • 更强大且可靠的类型系统,搭配相对合理的包管理机制。Python世界在这方面正在改善(感谢Astral),但仍远不及TypeScript。

        • 不确定是否值得为微软出卖灵魂。

          当我需要更强大的类型系统时,我会选择其他语言。

  53. 从什么切换过来?

  54. 祝你好运!

  55. 所以,我还是不明白,现在我仍然需要使用uv虚拟环境吗?

    • 据我所知,uv的原则就是不要去修改虚拟环境。使用 uv run、uv sync 等命令时,你无需担心虚拟环境是否激活等问题。如果你在项目中,它会自动选择正确的 Python 版本。然而,如果你想做一些无法通过这些命令实现的事情,你仍然可以深入虚拟环境。此外,为了在 IDE 中实现自动完成,你通常需要选择 venv

      • 我使用了一个名为“python envy”的VSCode扩展,它会根据你在单仓库中的哪个文件夹自动切换虚拟环境,非常方便!希望VSCode能内置这个功能

    • 你安装的是 uv 而不是 Python,使用 “uv run <file>” 而不是 “python <file>”,其他事情不用担心。

    • Python 虚拟环境只有一种类型,uv 和标准库的 venv 模块都可以创建它(第三方工具如 virtualenv 也能做到)。区别在于虚拟环境中包含的内容(除了你显式安装的包之外),以及配置接口的不同。特别是,标准库默认会初始化 pip,但你可以轻松跳过这一步。而 uv 不会初始化 pip,因为它本身就承担了 pip 的功能(并且功能更强大)。

    • 如果你从事数据科学/机器学习领域,与其他工具相比,uv 是处理项目依赖项的最佳选择。它的使用量激增,尤其是因为你可以通过 ‘uv pip 任意命令’ 来使用 pip。

  56. 带着一丝惊讶地说?

    这让我觉得这可能是一个被灌输反 Python 观念的 Ruby 开发者。文章似乎没有说明他们来自哪里。

    • > 被灌输反 Python 观念的 Ruby 开发者

      Ruby 开发者对代码的思考方式不同。就像 Perl 一样,他们接受“TIMTOWTDI”(有多种方式实现同一目标)。

      https://perl.fandom.com/wiki/TIMTOWTDI

      此外,他们以编写优雅的代码为荣,而非遵循“Pythonic”规范。卓越并非 conformity。

      我现在使用 Python 的频率更高,因为它被视为更简单、更易于上手且更容易强制遵守规范。我怀念 Ruby。

      • 依我之见,“Pythonic”主要在于利用语言本身的功能,而非试图将其视为 Java 或 C++。

        不过,在代码审查中,你确实会遇到一些懒惰的评论,比如“不Pythonic”或“不符合最佳实践”,这些评论往往只是审查者不喜欢某件事,却又懒得解释原因。这种评论被视为绝对的否定,无法反驳,可能会让你对“Pythonic”这个术语产生终身抵触情绪。

        “应该有一种——而且最好只有一种——显而易见的方式来实现它。”

        这可能是你可能会遇到的问题的核心,但我认为这并不是关于你如何编写自己的代码,而是关于你所创建的东西是否易于他人使用。

        • 我喜欢这种观点:

          > 应该有一种——而且最好只有一种——显而易见的方式来实现它。

          但我经常觉得,Python式的做法并不是一个很好的实现方式。这让你有点进退两难!

      • 我曾经工作过的唯一一家Ruby公司会大量使用Rubocop来消除TIMTOWTDI。我想你可以写任何你想要的代码,但Rubocop会非常积极地重写它。

      • > 就像 Perl 一样,他们接受 TIMTOWTDI。

        是的,这是错误的方法。当然,你可以有任何偏好,但在工程上,这是明显错误的。

    • 这在脚注中提到,他们是 Java/JavaScript/R 开发者。

    • 有一个脚注:

      > 如果你了解我,就知道我过去主要是Java/JavaScript/R类型的开发者。↩

    • 所以,如果你不喜欢Python,就必须被贴上Ruby开发者的标签?这种观点真奇怪。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

链接收藏


京ICP备12002735号