mirror of
https://github.com/python/cpython.git
synced 2025-08-04 00:48:58 +00:00
Merge branch 'main' into doctest-displayhook-fix-70280
This commit is contained in:
commit
42b7031e52
1328 changed files with 71008 additions and 28578 deletions
|
@ -1,4 +1,4 @@
|
|||
FROM docker.io/library/fedora:37
|
||||
FROM docker.io/library/fedora:40
|
||||
|
||||
ENV CC=clang
|
||||
|
||||
|
|
12
.github/CODEOWNERS
vendored
12
.github/CODEOWNERS
vendored
|
@ -243,6 +243,18 @@ Lib/test/support/interpreters/ @ericsnowcurrently
|
|||
Modules/_xx*interp*module.c @ericsnowcurrently
|
||||
Lib/test/test_interpreters/ @ericsnowcurrently
|
||||
|
||||
# Android
|
||||
**/*Android* @mhsmith
|
||||
**/*android* @mhsmith
|
||||
|
||||
# iOS (but not termios)
|
||||
**/iOS* @freakboy3742
|
||||
**/ios* @freakboy3742
|
||||
**/*_iOS* @freakboy3742
|
||||
**/*_ios* @freakboy3742
|
||||
**/*-iOS* @freakboy3742
|
||||
**/*-ios* @freakboy3742
|
||||
|
||||
# WebAssembly
|
||||
/Tools/wasm/ @brettcannon
|
||||
|
||||
|
|
28
.github/workflows/build.yml
vendored
28
.github/workflows/build.yml
vendored
|
@ -8,19 +8,11 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
- '3.12'
|
||||
- '3.11'
|
||||
- '3.10'
|
||||
- '3.9'
|
||||
- '3.8'
|
||||
- '3.*'
|
||||
pull_request:
|
||||
branches:
|
||||
- 'main'
|
||||
- '3.12'
|
||||
- '3.11'
|
||||
- '3.10'
|
||||
- '3.9'
|
||||
- '3.8'
|
||||
- '3.*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
@ -137,6 +129,7 @@ jobs:
|
|||
uses: actions/cache@v4
|
||||
with:
|
||||
path: config.cache
|
||||
# Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ needs.check_source.outputs.config_hash }}-${{ env.pythonLocation }}
|
||||
- name: Install Dependencies
|
||||
run: sudo ./.github/workflows/posix-deps-apt.sh
|
||||
|
@ -217,7 +210,7 @@ jobs:
|
|||
with:
|
||||
config_hash: ${{ needs.check_source.outputs.config_hash }}
|
||||
free-threading: true
|
||||
# macos-14 is M1
|
||||
# macos-14-large is Intel with 12 cores (most parallelism)
|
||||
os-matrix: '["macos-14"]'
|
||||
|
||||
build_ubuntu:
|
||||
|
@ -249,7 +242,7 @@ jobs:
|
|||
|
||||
build_ubuntu_ssltests:
|
||||
name: 'Ubuntu SSL tests with OpenSSL'
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
needs: check_source
|
||||
if: needs.check_source.outputs.run_tests == 'true'
|
||||
|
@ -315,7 +308,7 @@ jobs:
|
|||
|
||||
test_hypothesis:
|
||||
name: "Hypothesis tests on Ubuntu"
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
needs: check_source
|
||||
if: needs.check_source.outputs.run_tests == 'true' && needs.check_source.outputs.run_hypothesis == 'true'
|
||||
|
@ -428,7 +421,7 @@ jobs:
|
|||
|
||||
build_asan:
|
||||
name: 'Address sanitizer'
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 60
|
||||
needs: check_source
|
||||
if: needs.check_source.outputs.run_tests == 'true'
|
||||
|
@ -492,6 +485,8 @@ jobs:
|
|||
with:
|
||||
config_hash: ${{ needs.check_source.outputs.config_hash }}
|
||||
options: ./configure --config-cache --with-thread-sanitizer --with-pydebug
|
||||
suppressions_path: Tools/tsan/supressions.txt
|
||||
tsan_logs_artifact_name: tsan-logs-default
|
||||
|
||||
build_tsan_free_threading:
|
||||
name: 'Thread sanitizer (free-threading)'
|
||||
|
@ -501,6 +496,8 @@ jobs:
|
|||
with:
|
||||
config_hash: ${{ needs.check_source.outputs.config_hash }}
|
||||
options: ./configure --config-cache --disable-gil --with-thread-sanitizer --with-pydebug
|
||||
suppressions_path: Tools/tsan/suppressions_free_threading.txt
|
||||
tsan_logs_artifact_name: tsan-logs-free-threading
|
||||
|
||||
# CIFuzz job based on https://google.github.io/oss-fuzz/getting-started/continuous-integration/
|
||||
cifuzz:
|
||||
|
@ -514,8 +511,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# sanitizer: [address, undefined, memory] -- memory skipped temporarily until GH-116886 is solved.
|
||||
sanitizer: [address, undefined]
|
||||
sanitizer: [address, undefined, memory]
|
||||
steps:
|
||||
- name: Build fuzzers (${{ matrix.sanitizer }})
|
||||
id: build
|
||||
|
|
31
.github/workflows/jit.yml
vendored
31
.github/workflows/jit.yml
vendored
|
@ -5,15 +5,16 @@ on:
|
|||
- '**jit**'
|
||||
- 'Python/bytecodes.c'
|
||||
- 'Python/optimizer*.c'
|
||||
- 'Python/optimizer_bytecodes.c'
|
||||
push:
|
||||
paths:
|
||||
- '**jit**'
|
||||
- 'Python/bytecodes.c'
|
||||
- 'Python/optimizer*.c'
|
||||
- 'Python/optimizer_bytecodes.c'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
@ -22,7 +23,7 @@ jobs:
|
|||
jit:
|
||||
name: ${{ matrix.target }} (${{ matrix.debug && 'Debug' || 'Release' }})
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 90
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -40,7 +41,7 @@ jobs:
|
|||
- true
|
||||
- false
|
||||
llvm:
|
||||
- 16
|
||||
- 18
|
||||
include:
|
||||
- target: i686-pc-windows-msvc/msvc
|
||||
architecture: Win32
|
||||
|
@ -74,14 +75,10 @@ jobs:
|
|||
architecture: aarch64
|
||||
runner: ubuntu-latest
|
||||
compiler: gcc
|
||||
# These fail because of emulation, not because of the JIT:
|
||||
exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv test_external_inspection
|
||||
- target: aarch64-unknown-linux-gnu/clang
|
||||
architecture: aarch64
|
||||
runner: ubuntu-latest
|
||||
compiler: clang
|
||||
# These fail because of emulation, not because of the JIT:
|
||||
exclude: test_unix_events test_init test_process_pool test_shutdown test_multiprocessing_fork test_cmd_line test_faulthandler test_os test_perf_profiler test_posix test_signal test_socket test_subprocess test_threading test_venv test_external_inspection
|
||||
env:
|
||||
CC: ${{ matrix.compiler }}
|
||||
steps:
|
||||
|
@ -93,37 +90,43 @@ jobs:
|
|||
- name: Native Windows
|
||||
if: runner.os == 'Windows' && matrix.architecture != 'ARM64'
|
||||
run: |
|
||||
choco upgrade llvm -y
|
||||
choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }}
|
||||
./PCbuild/build.bat --experimental-jit ${{ matrix.debug && '-d' || '--pgo' }} -p ${{ matrix.architecture }}
|
||||
./PCbuild/rt.bat ${{ matrix.debug && '-d' }} -p ${{ matrix.architecture }} -q --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3
|
||||
./PCbuild/rt.bat ${{ matrix.debug && '-d' || '' }} -p ${{ matrix.architecture }} -q --multiprocess 0 --timeout 4500 --verbose2 --verbose3
|
||||
|
||||
# No PGO or tests (yet):
|
||||
- name: Emulated Windows
|
||||
if: runner.os == 'Windows' && matrix.architecture == 'ARM64'
|
||||
run: |
|
||||
choco upgrade llvm -y
|
||||
choco install llvm --allow-downgrade --no-progress --version ${{ matrix.llvm }}
|
||||
./PCbuild/build.bat --experimental-jit ${{ matrix.debug && '-d' || '' }} -p ${{ matrix.architecture }}
|
||||
|
||||
- name: Native macOS
|
||||
if: runner.os == 'macOS'
|
||||
run: |
|
||||
brew update
|
||||
brew install llvm@${{ matrix.llvm }}
|
||||
SDKROOT="$(xcrun --show-sdk-path)" \
|
||||
./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }}
|
||||
make all --jobs 4
|
||||
./python.exe -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3
|
||||
./python.exe -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
|
||||
|
||||
# --with-lto has been removed temporarily as a result of an open issue in LLVM 18 (see https://github.com/llvm/llvm-project/issues/87553)
|
||||
- name: Native Linux
|
||||
if: runner.os == 'Linux' && matrix.architecture == 'x86_64'
|
||||
run: |
|
||||
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
|
||||
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
|
||||
./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }}
|
||||
./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations' }}
|
||||
make all --jobs 4
|
||||
./python -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3
|
||||
./python -m test --multiprocess 0 --timeout 4500 --verbose2 --verbose3
|
||||
|
||||
# --with-lto has been removed temporarily as a result of an open issue in LLVM 18 (see https://github.com/llvm/llvm-project/issues/87553)
|
||||
- name: Emulated Linux
|
||||
if: runner.os == 'Linux' && matrix.architecture != 'x86_64'
|
||||
# The --ignorefile on ./python -m test is used to exclude tests known to fail when running on an emulated Linux.
|
||||
run: |
|
||||
sudo bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)" ./llvm.sh ${{ matrix.llvm }}
|
||||
export PATH="$(llvm-config-${{ matrix.llvm }} --bindir):$PATH"
|
||||
|
@ -138,6 +141,6 @@ jobs:
|
|||
CC="${{ matrix.compiler == 'clang' && 'clang --target=$HOST' || '$HOST-gcc' }}" \
|
||||
CPP="$CC --preprocess" \
|
||||
HOSTRUNNER=qemu-${{ matrix.architecture }} \
|
||||
./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations --with-lto' }} --build=x86_64-linux-gnu --host="$HOST" --with-build-python=../build/bin/python3 --with-pkg-config=no ac_cv_buggy_getaddrinfo=no ac_cv_file__dev_ptc=no ac_cv_file__dev_ptmx=yes
|
||||
./configure --enable-experimental-jit ${{ matrix.debug && '--with-pydebug' || '--enable-optimizations ' }} --build=x86_64-linux-gnu --host="$HOST" --with-build-python=../build/bin/python3 --with-pkg-config=no ac_cv_buggy_getaddrinfo=no ac_cv_file__dev_ptc=no ac_cv_file__dev_ptmx=yes
|
||||
make all --jobs 4
|
||||
./python -m test --exclude ${{ matrix.exclude }} --multiprocess 0 --timeout 3600 --verbose2 --verbose3
|
||||
./python -m test --ignorefile=Tools/jit/ignore-tests-emulated-linux.txt --multiprocess 0 --timeout 4500 --verbose2 --verbose3
|
||||
|
|
4
.github/workflows/mypy.yml
vendored
4
.github/workflows/mypy.yml
vendored
|
@ -8,6 +8,7 @@ on:
|
|||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/mypy.yml"
|
||||
- "Lib/_pyrepl/**"
|
||||
- "Lib/test/libregrtest/**"
|
||||
- "Tools/build/generate_sbom.py"
|
||||
- "Tools/cases_generator/**"
|
||||
|
@ -35,8 +36,9 @@ jobs:
|
|||
strategy:
|
||||
matrix:
|
||||
target: [
|
||||
"Lib/_pyrepl",
|
||||
"Lib/test/libregrtest",
|
||||
"Tools/build/",
|
||||
"Tools/build",
|
||||
"Tools/cases_generator",
|
||||
"Tools/clinic",
|
||||
"Tools/jit",
|
||||
|
|
1
.github/workflows/posix-deps-apt.sh
vendored
1
.github/workflows/posix-deps-apt.sh
vendored
|
@ -15,6 +15,7 @@ apt-get -yq install \
|
|||
libgdbm-dev \
|
||||
libgdbm-compat-dev \
|
||||
liblzma-dev \
|
||||
libmpdec-dev \
|
||||
libncurses5-dev \
|
||||
libreadline6-dev \
|
||||
libsqlite3-dev \
|
||||
|
|
2
.github/workflows/project-updater.yml
vendored
2
.github/workflows/project-updater.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
|||
- { project: 32, label: sprint }
|
||||
|
||||
steps:
|
||||
- uses: actions/add-to-project@v0.6.0
|
||||
- uses: actions/add-to-project@v1.0.0
|
||||
with:
|
||||
project-url: https://github.com/orgs/python/projects/${{ matrix.project }}
|
||||
github-token: ${{ secrets.ADD_TO_PROJECT_PAT }}
|
||||
|
|
2
.github/workflows/reusable-docs.yml
vendored
2
.github/workflows/reusable-docs.yml
vendored
|
@ -74,7 +74,7 @@ jobs:
|
|||
- name: 'Set up Python'
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11' # known to work with Sphinx 4.2
|
||||
python-version: '3.12' # known to work with Sphinx 6.2.1
|
||||
cache: 'pip'
|
||||
cache-dependency-path: 'Doc/requirements-oldest-sphinx.txt'
|
||||
- name: 'Install build dependencies'
|
||||
|
|
3
.github/workflows/reusable-macos.yml
vendored
3
.github/workflows/reusable-macos.yml
vendored
|
@ -22,6 +22,7 @@ jobs:
|
|||
HOMEBREW_NO_INSTALL_CLEANUP: 1
|
||||
HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK: 1
|
||||
PYTHONSTRICTEXTENSIONBUILD: 1
|
||||
TERM: linux
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -49,7 +50,7 @@ jobs:
|
|||
--prefix=/opt/python-dev \
|
||||
--with-openssl="$(brew --prefix openssl@3.0)"
|
||||
- name: Build CPython
|
||||
run: make -j4
|
||||
run: make -j8
|
||||
- name: Display build info
|
||||
run: make pythoninfo
|
||||
- name: Tests
|
||||
|
|
29
.github/workflows/reusable-tsan.yml
vendored
29
.github/workflows/reusable-tsan.yml
vendored
|
@ -7,6 +7,14 @@ on:
|
|||
options:
|
||||
required: true
|
||||
type: string
|
||||
suppressions_path:
|
||||
description: 'A repo relative path to the suppressions file'
|
||||
required: true
|
||||
type: string
|
||||
tsan_logs_artifact_name:
|
||||
description: 'Name of the TSAN logs artifact. Must be unique for each job.'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build_tsan_reusable:
|
||||
|
@ -25,12 +33,19 @@ jobs:
|
|||
- name: Install Dependencies
|
||||
run: |
|
||||
sudo ./.github/workflows/posix-deps-apt.sh
|
||||
sudo apt install -y clang
|
||||
# Install clang-18
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh 18
|
||||
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-18 100
|
||||
sudo update-alternatives --set clang /usr/bin/clang-18
|
||||
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-18 100
|
||||
sudo update-alternatives --set clang++ /usr/bin/clang++-18
|
||||
# Reduce ASLR to avoid TSAN crashing
|
||||
sudo sysctl -w vm.mmap_rnd_bits=28
|
||||
- name: TSAN Option Setup
|
||||
run: |
|
||||
echo "TSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/Tools/tsan/supressions.txt" >> $GITHUB_ENV
|
||||
echo "TSAN_OPTIONS=log_path=${GITHUB_WORKSPACE}/tsan_log suppressions=${GITHUB_WORKSPACE}/${{ inputs.suppressions_path }} handle_segv=0" >> $GITHUB_ENV
|
||||
echo "CC=clang" >> $GITHUB_ENV
|
||||
echo "CXX=clang++" >> $GITHUB_ENV
|
||||
- name: Add ccache to PATH
|
||||
|
@ -49,3 +64,13 @@ jobs:
|
|||
run: make pythoninfo
|
||||
- name: Tests
|
||||
run: ./python -m test --tsan -j4
|
||||
- name: Display TSAN logs
|
||||
if: always()
|
||||
run: find ${GITHUB_WORKSPACE} -name 'tsan_log.*' | xargs head -n 1000
|
||||
- name: Archive TSAN logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ inputs.tsan_logs_artifact_name }}
|
||||
path: tsan_log.*
|
||||
if-no-files-found: ignore
|
||||
|
|
4
.github/workflows/reusable-ubuntu.yml
vendored
4
.github/workflows/reusable-ubuntu.yml
vendored
|
@ -12,10 +12,12 @@ jobs:
|
|||
build_ubuntu_reusable:
|
||||
name: 'build and test'
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
OPENSSL_VER: 3.0.13
|
||||
PYTHONSTRICTEXTENSIONBUILD: 1
|
||||
TERM: linux
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Register gcc problem matcher
|
||||
|
|
8
.github/workflows/reusable-wasi.yml
vendored
8
.github/workflows/reusable-wasi.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
|||
build_wasi_reusable:
|
||||
name: 'build and test'
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
env:
|
||||
WASMTIME_VERSION: 18.0.3
|
||||
WASI_SDK_VERSION: 21
|
||||
|
@ -50,7 +50,8 @@ jobs:
|
|||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CROSS_BUILD_PYTHON }}/config.cache
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ inputs.config_hash }}
|
||||
# Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-${{ inputs.config_hash }}-${{ env.pythonLocation }}
|
||||
- name: "Configure build Python"
|
||||
run: python3 Tools/wasm/wasi.py configure-build-python -- --config-cache --with-pydebug
|
||||
- name: "Make build Python"
|
||||
|
@ -59,7 +60,8 @@ jobs:
|
|||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CROSS_BUILD_WASI }}/config.cache
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-wasi-sdk-${{ env.WASI_SDK_VERSION }}-${{ inputs.config_hash }}
|
||||
# Include env.pythonLocation in key to avoid changes in environment when setup-python updates Python
|
||||
key: ${{ github.job }}-${{ runner.os }}-${{ env.IMAGE_VERSION }}-wasi-sdk-${{ env.WASI_SDK_VERSION }}-${{ inputs.config_hash }}-${{ env.pythonLocation }}
|
||||
- name: "Configure host"
|
||||
# `--with-pydebug` inferred from configure-build-python
|
||||
run: python3 Tools/wasm/wasi.py configure-host -- --config-cache
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.2.0
|
||||
rev: v0.3.4
|
||||
hooks:
|
||||
- id: ruff
|
||||
name: Run Ruff on Lib/test/
|
||||
|
@ -11,9 +11,19 @@ repos:
|
|||
args: [--exit-non-zero-on-fix, --config=Tools/clinic/.ruff.toml]
|
||||
files: ^Tools/clinic/|Lib/test/test_clinic.py
|
||||
|
||||
- repo: https://github.com/psf/black-pre-commit-mirror
|
||||
rev: 24.4.2
|
||||
hooks:
|
||||
- id: black
|
||||
name: Run Black on Tools/jit/
|
||||
files: ^Tools/jit/
|
||||
language_version: python3.12
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: check-case-conflict
|
||||
- id: check-merge-conflict
|
||||
- id: check-toml
|
||||
exclude: ^Lib/test/test_tomllib/
|
||||
- id: check-yaml
|
||||
|
|
|
@ -22,12 +22,25 @@ you don't already have the SDK, here's how to install it:
|
|||
`android-sdk/cmdline-tools/latest`.
|
||||
* `export ANDROID_HOME=/path/to/android-sdk`
|
||||
|
||||
The `android.py` script also requires the following commands to be on the `PATH`:
|
||||
|
||||
* `curl`
|
||||
* `java`
|
||||
* `tar`
|
||||
* `unzip`
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
Building for Android requires doing a cross-build where you have a "build"
|
||||
Python to help produce an Android build of CPython. This procedure has been
|
||||
tested on Linux and macOS.
|
||||
Python can be built for Android on any POSIX platform supported by the Android
|
||||
development tools, which currently means Linux or macOS. This involves doing a
|
||||
cross-build where you use a "build" Python (for your development machine) to
|
||||
help produce a "host" Python for Android.
|
||||
|
||||
First, make sure you have all the usual tools and libraries needed to build
|
||||
Python for your development machine. The only Android tool you need to install
|
||||
is the command line tools package above: the build script will download the
|
||||
rest.
|
||||
|
||||
The easiest way to do a build is to use the `android.py` script. You can either
|
||||
have it perform the entire build process from start to finish in one step, or
|
||||
|
@ -43,9 +56,10 @@ The discrete steps for building via `android.py` are:
|
|||
./android.py make-host HOST
|
||||
```
|
||||
|
||||
To see the possible values of HOST, run `./android.py configure-host --help`.
|
||||
`HOST` identifies which architecture to build. To see the possible values, run
|
||||
`./android.py configure-host --help`.
|
||||
|
||||
Or to do it all in a single command, run:
|
||||
To do all steps in a single command, run:
|
||||
|
||||
```sh
|
||||
./android.py build HOST
|
||||
|
@ -62,3 +76,22 @@ call. For example, if you want a pydebug build that also caches the results from
|
|||
```sh
|
||||
./android.py build HOST -- -C --with-pydebug
|
||||
```
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
To run the Python test suite on Android:
|
||||
|
||||
* Install Android Studio, if you don't already have it.
|
||||
* Follow the instructions in the previous section to build all supported
|
||||
architectures.
|
||||
* Run `./android.py setup-testbed` to download the Gradle wrapper.
|
||||
* Open the `testbed` directory in Android Studio.
|
||||
* In the *Device Manager* dock, connect a device or start an emulator.
|
||||
Then select it from the drop-down list in the toolbar.
|
||||
* Click the "Run" button in the toolbar.
|
||||
* The testbed app displays nothing on screen while running. To see its output,
|
||||
open the [Logcat window](https://developer.android.com/studio/debug/logcat).
|
||||
|
||||
To run specific tests, or pass any other arguments to the test suite, edit the
|
||||
command line in testbed/app/src/main/python/main.py.
|
||||
|
|
|
@ -61,6 +61,12 @@ done
|
|||
export CFLAGS=""
|
||||
export LDFLAGS="-Wl,--build-id=sha1 -Wl,--no-rosegment"
|
||||
|
||||
# Unlike Linux, Android does not implicitly use a dlopened library to resolve
|
||||
# relocations in subsequently-loaded libraries, even if RTLD_GLOBAL is used
|
||||
# (https://github.com/android/ndk/issues/1244). So any library that fails to
|
||||
# build with this flag, would also fail to load at runtime.
|
||||
LDFLAGS="$LDFLAGS -Wl,--no-undefined"
|
||||
|
||||
# Many packages get away with omitting -lm on Linux, but Android is stricter.
|
||||
LDFLAGS="$LDFLAGS -lm"
|
||||
|
||||
|
|
|
@ -7,8 +7,9 @@ import shutil
|
|||
import subprocess
|
||||
import sys
|
||||
import sysconfig
|
||||
from os.path import relpath
|
||||
from os.path import basename, relpath
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
SCRIPT_NAME = Path(__file__).name
|
||||
CHECKOUT = Path(__file__).resolve().parent.parent
|
||||
|
@ -102,11 +103,17 @@ def unpack_deps(host):
|
|||
for name_ver in ["bzip2-1.0.8-1", "libffi-3.4.4-2", "openssl-3.0.13-1",
|
||||
"sqlite-3.45.1-0", "xz-5.4.6-0"]:
|
||||
filename = f"{name_ver}-{host}.tar.gz"
|
||||
run(["wget", f"{deps_url}/{name_ver}/{filename}"])
|
||||
download(f"{deps_url}/{name_ver}/{filename}")
|
||||
run(["tar", "-xf", filename])
|
||||
os.remove(filename)
|
||||
|
||||
|
||||
def download(url, target_dir="."):
|
||||
out_path = f"{target_dir}/{basename(url)}"
|
||||
run(["curl", "-Lf", "-o", out_path, url])
|
||||
return out_path
|
||||
|
||||
|
||||
def configure_host_python(context):
|
||||
host_dir = subdir(context.host, clean=context.clean)
|
||||
|
||||
|
@ -160,6 +167,30 @@ def clean_all(context):
|
|||
delete_if_exists(CROSS_BUILD_DIR)
|
||||
|
||||
|
||||
# To avoid distributing compiled artifacts without corresponding source code,
|
||||
# the Gradle wrapper is not included in the CPython repository. Instead, we
|
||||
# extract it from the Gradle release.
|
||||
def setup_testbed(context):
|
||||
ver_long = "8.7.0"
|
||||
ver_short = ver_long.removesuffix(".0")
|
||||
testbed_dir = CHECKOUT / "Android/testbed"
|
||||
|
||||
for filename in ["gradlew", "gradlew.bat"]:
|
||||
out_path = download(
|
||||
f"https://raw.githubusercontent.com/gradle/gradle/v{ver_long}/{filename}",
|
||||
testbed_dir)
|
||||
os.chmod(out_path, 0o755)
|
||||
|
||||
with TemporaryDirectory(prefix=SCRIPT_NAME) as temp_dir:
|
||||
os.chdir(temp_dir)
|
||||
bin_zip = download(
|
||||
f"https://services.gradle.org/distributions/gradle-{ver_short}-bin.zip")
|
||||
outer_jar = f"gradle-{ver_short}/lib/plugins/gradle-wrapper-{ver_short}.jar"
|
||||
run(["unzip", bin_zip, outer_jar])
|
||||
run(["unzip", "-o", "-d", f"{testbed_dir}/gradle/wrapper", outer_jar,
|
||||
"gradle-wrapper.jar"])
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
subcommands = parser.add_subparsers(dest="subcommand")
|
||||
|
@ -173,8 +204,11 @@ def main():
|
|||
help="Run `configure` for Android")
|
||||
make_host = subcommands.add_parser("make-host",
|
||||
help="Run `make` for Android")
|
||||
clean = subcommands.add_parser("clean", help="Delete files and directories "
|
||||
"created by this script")
|
||||
subcommands.add_parser(
|
||||
"clean", help="Delete the cross-build directory")
|
||||
subcommands.add_parser(
|
||||
"setup-testbed", help="Download the testbed Gradle wrapper")
|
||||
|
||||
for subcommand in build, configure_build, configure_host:
|
||||
subcommand.add_argument(
|
||||
"--clean", action="store_true", default=False, dest="clean",
|
||||
|
@ -194,7 +228,8 @@ def main():
|
|||
"configure-host": configure_host_python,
|
||||
"make-host": make_host_python,
|
||||
"build": build_all,
|
||||
"clean": clean_all}
|
||||
"clean": clean_all,
|
||||
"setup-testbed": setup_testbed}
|
||||
dispatch[context.subcommand](context)
|
||||
|
||||
|
||||
|
|
21
Android/testbed/.gitignore
vendored
Normal file
21
Android/testbed/.gitignore
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
# The Gradle wrapper should be downloaded by running `../android.py setup-testbed`.
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/deploymentTargetDropdown.xml
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
1
Android/testbed/app/.gitignore
vendored
Normal file
1
Android/testbed/app/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/build
|
129
Android/testbed/app/build.gradle.kts
Normal file
129
Android/testbed/app/build.gradle.kts
Normal file
|
@ -0,0 +1,129 @@
|
|||
import com.android.build.api.variant.*
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
val PYTHON_DIR = File(projectDir, "../../..").canonicalPath
|
||||
val PYTHON_CROSS_DIR = "$PYTHON_DIR/cross-build"
|
||||
val ABIS = mapOf(
|
||||
"arm64-v8a" to "aarch64-linux-android",
|
||||
"x86_64" to "x86_64-linux-android",
|
||||
)
|
||||
|
||||
val PYTHON_VERSION = File("$PYTHON_DIR/Include/patchlevel.h").useLines {
|
||||
for (line in it) {
|
||||
val match = """#define PY_VERSION\s+"(\d+\.\d+)""".toRegex().find(line)
|
||||
if (match != null) {
|
||||
return@useLines match.groupValues[1]
|
||||
}
|
||||
}
|
||||
throw GradleException("Failed to find Python version")
|
||||
}
|
||||
|
||||
|
||||
android {
|
||||
namespace = "org.python.testbed"
|
||||
compileSdk = 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "org.python.testbed"
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "1.0"
|
||||
|
||||
ndk.abiFilters.addAll(ABIS.keys)
|
||||
externalNativeBuild.cmake.arguments(
|
||||
"-DPYTHON_CROSS_DIR=$PYTHON_CROSS_DIR",
|
||||
"-DPYTHON_VERSION=$PYTHON_VERSION")
|
||||
}
|
||||
|
||||
externalNativeBuild.cmake {
|
||||
path("src/main/c/CMakeLists.txt")
|
||||
}
|
||||
|
||||
// Set this property to something non-empty, otherwise it'll use the default
|
||||
// list, which ignores asset directories beginning with an underscore.
|
||||
aaptOptions.ignoreAssetsPattern = ".git"
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("com.google.android.material:material:1.11.0")
|
||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||
}
|
||||
|
||||
|
||||
// Create some custom tasks to copy Python and its standard library from
|
||||
// elsewhere in the repository.
|
||||
androidComponents.onVariants { variant ->
|
||||
generateTask(variant, variant.sources.assets!!) {
|
||||
into("python") {
|
||||
for (triplet in ABIS.values) {
|
||||
for (subDir in listOf("include", "lib")) {
|
||||
into(subDir) {
|
||||
from("$PYTHON_CROSS_DIR/$triplet/prefix/$subDir")
|
||||
include("python$PYTHON_VERSION/**")
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
}
|
||||
}
|
||||
}
|
||||
into("lib/python$PYTHON_VERSION") {
|
||||
// Uncomment this to pick up edits from the source directory
|
||||
// without having to rerun `make install`.
|
||||
// from("$PYTHON_DIR/Lib")
|
||||
// duplicatesStrategy = DuplicatesStrategy.INCLUDE
|
||||
|
||||
into("site-packages") {
|
||||
from("$projectDir/src/main/python")
|
||||
}
|
||||
}
|
||||
}
|
||||
exclude("**/__pycache__")
|
||||
}
|
||||
|
||||
generateTask(variant, variant.sources.jniLibs!!) {
|
||||
for ((abi, triplet) in ABIS.entries) {
|
||||
into(abi) {
|
||||
from("$PYTHON_CROSS_DIR/$triplet/prefix/lib")
|
||||
include("libpython*.*.so")
|
||||
include("lib*_python.so")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun generateTask(
|
||||
variant: ApplicationVariant, directories: SourceDirectories,
|
||||
configure: GenerateTask.() -> Unit
|
||||
) {
|
||||
val taskName = "generate" +
|
||||
listOf(variant.name, "Python", directories.name)
|
||||
.map { it.replaceFirstChar(Char::uppercase) }
|
||||
.joinToString("")
|
||||
|
||||
directories.addGeneratedSourceDirectory(
|
||||
tasks.register<GenerateTask>(taskName) {
|
||||
into(outputDir)
|
||||
configure()
|
||||
},
|
||||
GenerateTask::outputDir)
|
||||
}
|
||||
|
||||
|
||||
// addGeneratedSourceDirectory requires the task to have a DirectoryProperty.
|
||||
abstract class GenerateTask: Sync() {
|
||||
@get:OutputDirectory
|
||||
abstract val outputDir: DirectoryProperty
|
||||
}
|
20
Android/testbed/app/src/main/AndroidManifest.xml
Normal file
20
Android/testbed/app/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<application
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.Material3.Light.NoActionBar">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
9
Android/testbed/app/src/main/c/CMakeLists.txt
Normal file
9
Android/testbed/app/src/main/c/CMakeLists.txt
Normal file
|
@ -0,0 +1,9 @@
|
|||
cmake_minimum_required(VERSION 3.4.1)
|
||||
project(testbed)
|
||||
|
||||
set(PREFIX_DIR ${PYTHON_CROSS_DIR}/${CMAKE_LIBRARY_ARCHITECTURE}/prefix)
|
||||
include_directories(${PREFIX_DIR}/include/python${PYTHON_VERSION})
|
||||
link_directories(${PREFIX_DIR}/lib)
|
||||
link_libraries(log python${PYTHON_VERSION})
|
||||
|
||||
add_library(main_activity SHARED main_activity.c)
|
147
Android/testbed/app/src/main/c/main_activity.c
Normal file
147
Android/testbed/app/src/main/c/main_activity.c
Normal file
|
@ -0,0 +1,147 @@
|
|||
#include <android/log.h>
|
||||
#include <errno.h>
|
||||
#include <jni.h>
|
||||
#include <pthread.h>
|
||||
#include <Python.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
|
||||
static void throw_runtime_exception(JNIEnv *env, const char *message) {
|
||||
(*env)->ThrowNew(
|
||||
env,
|
||||
(*env)->FindClass(env, "java/lang/RuntimeException"),
|
||||
message);
|
||||
}
|
||||
|
||||
|
||||
// --- Stdio redirection ------------------------------------------------------
|
||||
|
||||
// Most apps won't need this, because the Python-level sys.stdout and sys.stderr
|
||||
// are redirected to the Android logcat by Python itself. However, in the
|
||||
// testbed it's useful to redirect the native streams as well, to debug problems
|
||||
// in the Python startup or redirection process.
|
||||
//
|
||||
// Based on
|
||||
// https://github.com/beeware/briefcase-android-gradle-template/blob/v0.3.11/%7B%7B%20cookiecutter.safe_formal_name%20%7D%7D/app/src/main/cpp/native-lib.cpp
|
||||
|
||||
typedef struct {
|
||||
FILE *file;
|
||||
int fd;
|
||||
android_LogPriority priority;
|
||||
char *tag;
|
||||
int pipe[2];
|
||||
} StreamInfo;
|
||||
|
||||
static StreamInfo STREAMS[] = {
|
||||
{stdout, STDOUT_FILENO, ANDROID_LOG_INFO, "native.stdout", {-1, -1}},
|
||||
{stderr, STDERR_FILENO, ANDROID_LOG_WARN, "native.stderr", {-1, -1}},
|
||||
{NULL, -1, ANDROID_LOG_UNKNOWN, NULL, {-1, -1}},
|
||||
};
|
||||
|
||||
// The maximum length of a log message in bytes, including the level marker and
|
||||
// tag, is defined as LOGGER_ENTRY_MAX_PAYLOAD in
|
||||
// platform/system/logging/liblog/include/log/log.h. As of API level 30, messages
|
||||
// longer than this will be be truncated by logcat. This limit has already been
|
||||
// reduced at least once in the history of Android (from 4076 to 4068 between API
|
||||
// level 23 and 26), so leave some headroom.
|
||||
static const int MAX_BYTES_PER_WRITE = 4000;
|
||||
|
||||
static void *redirection_thread(void *arg) {
|
||||
StreamInfo *si = (StreamInfo*)arg;
|
||||
ssize_t read_size;
|
||||
char buf[MAX_BYTES_PER_WRITE];
|
||||
while ((read_size = read(si->pipe[0], buf, sizeof buf - 1)) > 0) {
|
||||
buf[read_size] = '\0'; /* add null-terminator */
|
||||
__android_log_write(si->priority, si->tag, buf);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *redirect_stream(StreamInfo *si) {
|
||||
/* make the FILE unbuffered, to ensure messages are never lost */
|
||||
if (setvbuf(si->file, 0, _IONBF, 0)) {
|
||||
return "setvbuf";
|
||||
}
|
||||
|
||||
/* create the pipe and redirect the file descriptor */
|
||||
if (pipe(si->pipe)) {
|
||||
return "pipe";
|
||||
}
|
||||
if (dup2(si->pipe[1], si->fd) == -1) {
|
||||
return "dup2";
|
||||
}
|
||||
|
||||
/* start the logging thread */
|
||||
pthread_t thr;
|
||||
if ((errno = pthread_create(&thr, 0, redirection_thread, si))) {
|
||||
return "pthread_create";
|
||||
}
|
||||
if ((errno = pthread_detach(thr))) {
|
||||
return "pthread_detach";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_python_testbed_MainActivity_redirectStdioToLogcat(
|
||||
JNIEnv *env, jobject obj
|
||||
) {
|
||||
for (StreamInfo *si = STREAMS; si->file; si++) {
|
||||
char *error_prefix;
|
||||
if ((error_prefix = redirect_stream(si))) {
|
||||
char error_message[1024];
|
||||
snprintf(error_message, sizeof(error_message),
|
||||
"%s: %s", error_prefix, strerror(errno));
|
||||
throw_runtime_exception(env, error_message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Python intialization ----------------------------------------------------
|
||||
|
||||
static PyStatus set_config_string(
|
||||
JNIEnv *env, PyConfig *config, wchar_t **config_str, jstring value
|
||||
) {
|
||||
const char *value_utf8 = (*env)->GetStringUTFChars(env, value, NULL);
|
||||
PyStatus status = PyConfig_SetBytesString(config, config_str, value_utf8);
|
||||
(*env)->ReleaseStringUTFChars(env, value, value_utf8);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void throw_status(JNIEnv *env, PyStatus status) {
|
||||
throw_runtime_exception(env, status.err_msg ? status.err_msg : "");
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL Java_org_python_testbed_MainActivity_runPython(
|
||||
JNIEnv *env, jobject obj, jstring home, jstring runModule
|
||||
) {
|
||||
PyConfig config;
|
||||
PyStatus status;
|
||||
PyConfig_InitIsolatedConfig(&config);
|
||||
|
||||
status = set_config_string(env, &config, &config.home, home);
|
||||
if (PyStatus_Exception(status)) {
|
||||
throw_status(env, status);
|
||||
return;
|
||||
}
|
||||
|
||||
status = set_config_string(env, &config, &config.run_module, runModule);
|
||||
if (PyStatus_Exception(status)) {
|
||||
throw_status(env, status);
|
||||
return;
|
||||
}
|
||||
|
||||
// Some tests generate SIGPIPE and SIGXFSZ, which should be ignored.
|
||||
config.install_signal_handlers = 1;
|
||||
|
||||
status = Py_InitializeFromConfig(&config);
|
||||
if (PyStatus_Exception(status)) {
|
||||
throw_status(env, status);
|
||||
return;
|
||||
}
|
||||
|
||||
Py_RunMain();
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package org.python.testbed
|
||||
|
||||
import android.os.*
|
||||
import android.system.Os
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.*
|
||||
import java.io.*
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
// Python needs this variable to help it find the temporary directory,
|
||||
// but Android only sets it on API level 33 and later.
|
||||
Os.setenv("TMPDIR", cacheDir.toString(), false)
|
||||
|
||||
val pythonHome = extractAssets()
|
||||
System.loadLibrary("main_activity")
|
||||
redirectStdioToLogcat()
|
||||
runPython(pythonHome.toString(), "main")
|
||||
findViewById<TextView>(R.id.tvHello).text = "Python complete"
|
||||
}
|
||||
|
||||
private fun extractAssets() : File {
|
||||
val pythonHome = File(filesDir, "python")
|
||||
if (pythonHome.exists() && !pythonHome.deleteRecursively()) {
|
||||
throw RuntimeException("Failed to delete $pythonHome")
|
||||
}
|
||||
extractAssetDir("python", filesDir)
|
||||
return pythonHome
|
||||
}
|
||||
|
||||
private fun extractAssetDir(path: String, targetDir: File) {
|
||||
val names = assets.list(path)
|
||||
?: throw RuntimeException("Failed to list $path")
|
||||
val targetSubdir = File(targetDir, path)
|
||||
if (!targetSubdir.mkdirs()) {
|
||||
throw RuntimeException("Failed to create $targetSubdir")
|
||||
}
|
||||
|
||||
for (name in names) {
|
||||
val subPath = "$path/$name"
|
||||
val input: InputStream
|
||||
try {
|
||||
input = assets.open(subPath)
|
||||
} catch (e: FileNotFoundException) {
|
||||
extractAssetDir(subPath, targetDir)
|
||||
continue
|
||||
}
|
||||
input.use {
|
||||
File(targetSubdir, name).outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private external fun redirectStdioToLogcat()
|
||||
private external fun runPython(home: String, runModule: String)
|
||||
}
|
17
Android/testbed/app/src/main/python/main.py
Normal file
17
Android/testbed/app/src/main/python/main.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
import runpy
|
||||
import signal
|
||||
import sys
|
||||
|
||||
# Some tests use SIGUSR1, but that's blocked by default in an Android app in
|
||||
# order to make it available to `sigwait` in the "Signal Catcher" thread. That
|
||||
# thread's functionality is only relevant to the JVM ("forcing GC (no HPROF) and
|
||||
# profile save"), so disabling it should not weaken the tests.
|
||||
signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGUSR1])
|
||||
|
||||
# To run specific tests, or pass any other arguments to the test suite, edit
|
||||
# this command line.
|
||||
sys.argv[1:] = [
|
||||
"--use", "all,-cpu",
|
||||
"--verbose3",
|
||||
]
|
||||
runpy.run_module("test")
|
BIN
Android/testbed/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Normal file
BIN
Android/testbed/app/src/main/res/drawable-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3 KiB |
19
Android/testbed/app/src/main/res/layout/activity_main.xml
Normal file
19
Android/testbed/app/src/main/res/layout/activity_main.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tvHello"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Hello World!"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
3
Android/testbed/app/src/main/res/values/strings.xml
Normal file
3
Android/testbed/app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">Python testbed</string>
|
||||
</resources>
|
5
Android/testbed/build.gradle.kts
Normal file
5
Android/testbed/build.gradle.kts
Normal file
|
@ -0,0 +1,5 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.2.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||
}
|
23
Android/testbed/gradle.properties
Normal file
23
Android/testbed/gradle.properties
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
||||
# Enables namespacing of each library's R class so that its R class includes only the
|
||||
# resources declared in the library itself and none from the library's dependencies,
|
||||
# thereby reducing the size of the R class for that library
|
||||
android.nonTransitiveRClass=true
|
6
Android/testbed/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
Android/testbed/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
#Mon Feb 19 20:29:06 GMT 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
18
Android/testbed/settings.gradle.kts
Normal file
18
Android/testbed/settings.gradle.kts
Normal file
|
@ -0,0 +1,18 @@
|
|||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "Python testbed"
|
||||
include(":app")
|
||||
|
|
@ -22,6 +22,10 @@ have a suggestion on how to fix it, include that as well.
|
|||
You can also open a discussion item on our
|
||||
`Documentation Discourse forum <https://discuss.python.org/c/documentation/26>`_.
|
||||
|
||||
If you find a bug in the theme (HTML / CSS / JavaScript) of the
|
||||
documentation, please submit a bug report on the `python-doc-theme bug
|
||||
tracker <https://github.com/python/python-docs-theme>`_.
|
||||
|
||||
If you're short on time, you can also email documentation bug reports to
|
||||
docs@python.org (behavioral bugs can be sent to python-list@python.org).
|
||||
'docs@' is a mailing list run by volunteers; your request will be noticed,
|
||||
|
|
|
@ -191,10 +191,10 @@ called with a non-bytes parameter.
|
|||
|
||||
.. c:function:: int _PyBytes_Resize(PyObject **bytes, Py_ssize_t newsize)
|
||||
|
||||
A way to resize a bytes object even though it is "immutable". Only use this
|
||||
to build up a brand new bytes object; don't use this if the bytes may already
|
||||
be known in other parts of the code. It is an error to call this function if
|
||||
the refcount on the input bytes object is not one. Pass the address of an
|
||||
Resize a bytes object. *newsize* will be the new length of the bytes object.
|
||||
You can think of it as creating a new bytes object and destroying the old
|
||||
one, only more efficiently.
|
||||
Pass the address of an
|
||||
existing bytes object as an lvalue (it may be written into), and the new size
|
||||
desired. On success, *\*bytes* holds the resized bytes object and ``0`` is
|
||||
returned; the address in *\*bytes* may differ from its input value. If the
|
||||
|
|
|
@ -104,7 +104,7 @@ Printing and clearing
|
|||
Similar to :c:func:`PyErr_WriteUnraisable`, but the *format* and subsequent
|
||||
parameters help format the warning message; they have the same meaning and
|
||||
values as in :c:func:`PyUnicode_FromFormat`.
|
||||
``PyErr_WriteUnraisable(obj)`` is roughtly equivalent to
|
||||
``PyErr_WriteUnraisable(obj)`` is roughly equivalent to
|
||||
``PyErr_FormatUnraisable("Exception ignored in: %R", obj)``.
|
||||
If *format* is ``NULL``, only the traceback is printed.
|
||||
|
||||
|
@ -221,13 +221,14 @@ For convenience, some of these functions will always return a
|
|||
|
||||
.. c:function:: PyObject* PyErr_SetFromWindowsErr(int ierr)
|
||||
|
||||
This is a convenience function to raise :exc:`WindowsError`. If called with
|
||||
This is a convenience function to raise :exc:`OSError`. If called with
|
||||
*ierr* of ``0``, the error code returned by a call to :c:func:`!GetLastError`
|
||||
is used instead. It calls the Win32 function :c:func:`!FormatMessage` to retrieve
|
||||
the Windows description of error code given by *ierr* or :c:func:`!GetLastError`,
|
||||
then it constructs a tuple object whose first item is the *ierr* value and whose
|
||||
second item is the corresponding error message (gotten from
|
||||
:c:func:`!FormatMessage`), and then calls ``PyErr_SetObject(PyExc_WindowsError,
|
||||
then it constructs a :exc:`OSError` object with the :attr:`~OSError.winerror`
|
||||
attribute set to the error code, the :attr:`~OSError.strerror` attribute
|
||||
set to the corresponding error message (gotten from
|
||||
:c:func:`!FormatMessage`), and then calls ``PyErr_SetObject(PyExc_OSError,
|
||||
object)``. This function always returns ``NULL``.
|
||||
|
||||
.. availability:: Windows.
|
||||
|
|
|
@ -120,12 +120,19 @@ See also :ref:`Reflection <reflection>`.
|
|||
|
||||
.. c:function:: PyObject* PyFrame_GetLocals(PyFrameObject *frame)
|
||||
|
||||
Get the *frame*'s :attr:`~frame.f_locals` attribute (:class:`dict`).
|
||||
Get the *frame*'s :attr:`~frame.f_locals` attribute.
|
||||
If the frame refers to a function or comprehension, this returns
|
||||
a write-through proxy object that allows modifying the locals.
|
||||
In all other cases (classes, modules) it returns the :class:`dict`
|
||||
representing the frame locals directly.
|
||||
|
||||
Return a :term:`strong reference`.
|
||||
|
||||
.. versionadded:: 3.11
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
Return a proxy object for functions and comprehensions.
|
||||
|
||||
|
||||
.. c:function:: int PyFrame_GetLineNumber(PyFrameObject *frame)
|
||||
|
||||
|
|
|
@ -82,3 +82,14 @@ See also the :c:member:`PyTypeObject.tp_hash` member and :ref:`numeric-hash`.
|
|||
The function cannot fail: it cannot return ``-1``.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
.. c:function:: Py_hash_t PyObject_GenericHash(PyObject *obj)
|
||||
|
||||
Generic hashing function that is meant to be put into a type
|
||||
object's ``tp_hash`` slot.
|
||||
Its result only depends on the object's identity.
|
||||
|
||||
.. impl-detail::
|
||||
In CPython, it is equivalent to :c:func:`Py_HashPointer`.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
|
|
@ -25,3 +25,4 @@ document the API functions in detail.
|
|||
memory.rst
|
||||
objimpl.rst
|
||||
apiabiversion.rst
|
||||
monitoring.rst
|
||||
|
|
|
@ -29,6 +29,8 @@ The following functions can be safely called before Python is initialized:
|
|||
* :c:func:`PyMem_SetAllocator`
|
||||
* :c:func:`PyMem_SetupDebugHooks`
|
||||
* :c:func:`PyObject_SetArenaAllocator`
|
||||
* :c:func:`Py_SetProgramName`
|
||||
* :c:func:`Py_SetPythonHome`
|
||||
* :c:func:`PySys_ResetWarnOptions`
|
||||
|
||||
* Informative functions:
|
||||
|
@ -59,7 +61,7 @@ The following functions can be safely called before Python is initialized:
|
|||
:c:func:`Py_Initialize`: :c:func:`Py_EncodeLocale`, :c:func:`Py_GetPath`,
|
||||
:c:func:`Py_GetPrefix`, :c:func:`Py_GetExecPrefix`,
|
||||
:c:func:`Py_GetProgramFullPath`, :c:func:`Py_GetPythonHome`,
|
||||
and :c:func:`Py_GetProgramName`.
|
||||
:c:func:`Py_GetProgramName` and :c:func:`PyEval_InitThreads`.
|
||||
|
||||
|
||||
.. _global-conf-vars:
|
||||
|
@ -326,6 +328,7 @@ Initializing and finalizing the interpreter
|
|||
.. c:function:: void Py_Initialize()
|
||||
|
||||
.. index::
|
||||
single: PyEval_InitThreads()
|
||||
single: modules (in module sys)
|
||||
single: path (in module sys)
|
||||
pair: module; builtins
|
||||
|
@ -425,6 +428,34 @@ Process-wide parameters
|
|||
=======================
|
||||
|
||||
|
||||
.. c:function:: void Py_SetProgramName(const wchar_t *name)
|
||||
|
||||
.. index::
|
||||
single: Py_Initialize()
|
||||
single: main()
|
||||
single: Py_GetPath()
|
||||
|
||||
This API is kept for backward compatibility: setting
|
||||
:c:member:`PyConfig.program_name` should be used instead, see :ref:`Python
|
||||
Initialization Configuration <init-config>`.
|
||||
|
||||
This function should be called before :c:func:`Py_Initialize` is called for
|
||||
the first time, if it is called at all. It tells the interpreter the value
|
||||
of the ``argv[0]`` argument to the :c:func:`main` function of the program
|
||||
(converted to wide characters).
|
||||
This is used by :c:func:`Py_GetPath` and some other functions below to find
|
||||
the Python run-time libraries relative to the interpreter executable. The
|
||||
default value is ``'python'``. The argument should point to a
|
||||
zero-terminated wide character string in static storage whose contents will not
|
||||
change for the duration of the program's execution. No code in the Python
|
||||
interpreter will change the contents of this storage.
|
||||
|
||||
Use :c:func:`Py_DecodeLocale` to decode a bytes string to get a
|
||||
:c:expr:`wchar_*` string.
|
||||
|
||||
.. deprecated:: 3.11
|
||||
|
||||
|
||||
.. c:function:: wchar_t* Py_GetProgramName()
|
||||
|
||||
Return the program name set with :c:member:`PyConfig.program_name`, or the default.
|
||||
|
@ -626,6 +657,106 @@ Process-wide parameters
|
|||
``sys.version``.
|
||||
|
||||
|
||||
.. c:function:: void PySys_SetArgvEx(int argc, wchar_t **argv, int updatepath)
|
||||
|
||||
.. index::
|
||||
single: main()
|
||||
single: Py_FatalError()
|
||||
single: argv (in module sys)
|
||||
|
||||
This API is kept for backward compatibility: setting
|
||||
:c:member:`PyConfig.argv`, :c:member:`PyConfig.parse_argv` and
|
||||
:c:member:`PyConfig.safe_path` should be used instead, see :ref:`Python
|
||||
Initialization Configuration <init-config>`.
|
||||
|
||||
Set :data:`sys.argv` based on *argc* and *argv*. These parameters are
|
||||
similar to those passed to the program's :c:func:`main` function with the
|
||||
difference that the first entry should refer to the script file to be
|
||||
executed rather than the executable hosting the Python interpreter. If there
|
||||
isn't a script that will be run, the first entry in *argv* can be an empty
|
||||
string. If this function fails to initialize :data:`sys.argv`, a fatal
|
||||
condition is signalled using :c:func:`Py_FatalError`.
|
||||
|
||||
If *updatepath* is zero, this is all the function does. If *updatepath*
|
||||
is non-zero, the function also modifies :data:`sys.path` according to the
|
||||
following algorithm:
|
||||
|
||||
- If the name of an existing script is passed in ``argv[0]``, the absolute
|
||||
path of the directory where the script is located is prepended to
|
||||
:data:`sys.path`.
|
||||
- Otherwise (that is, if *argc* is ``0`` or ``argv[0]`` doesn't point
|
||||
to an existing file name), an empty string is prepended to
|
||||
:data:`sys.path`, which is the same as prepending the current working
|
||||
directory (``"."``).
|
||||
|
||||
Use :c:func:`Py_DecodeLocale` to decode a bytes string to get a
|
||||
:c:expr:`wchar_*` string.
|
||||
|
||||
See also :c:member:`PyConfig.orig_argv` and :c:member:`PyConfig.argv`
|
||||
members of the :ref:`Python Initialization Configuration <init-config>`.
|
||||
|
||||
.. note::
|
||||
It is recommended that applications embedding the Python interpreter
|
||||
for purposes other than executing a single script pass ``0`` as *updatepath*,
|
||||
and update :data:`sys.path` themselves if desired.
|
||||
See :cve:`2008-5983`.
|
||||
|
||||
On versions before 3.1.3, you can achieve the same effect by manually
|
||||
popping the first :data:`sys.path` element after having called
|
||||
:c:func:`PySys_SetArgv`, for example using::
|
||||
|
||||
PyRun_SimpleString("import sys; sys.path.pop(0)\n");
|
||||
|
||||
.. versionadded:: 3.1.3
|
||||
|
||||
.. XXX impl. doesn't seem consistent in allowing ``0``/``NULL`` for the params;
|
||||
check w/ Guido.
|
||||
|
||||
.. deprecated:: 3.11
|
||||
|
||||
|
||||
.. c:function:: void PySys_SetArgv(int argc, wchar_t **argv)
|
||||
|
||||
This API is kept for backward compatibility: setting
|
||||
:c:member:`PyConfig.argv` and :c:member:`PyConfig.parse_argv` should be used
|
||||
instead, see :ref:`Python Initialization Configuration <init-config>`.
|
||||
|
||||
This function works like :c:func:`PySys_SetArgvEx` with *updatepath* set
|
||||
to ``1`` unless the :program:`python` interpreter was started with the
|
||||
:option:`-I`.
|
||||
|
||||
Use :c:func:`Py_DecodeLocale` to decode a bytes string to get a
|
||||
:c:expr:`wchar_*` string.
|
||||
|
||||
See also :c:member:`PyConfig.orig_argv` and :c:member:`PyConfig.argv`
|
||||
members of the :ref:`Python Initialization Configuration <init-config>`.
|
||||
|
||||
.. versionchanged:: 3.4 The *updatepath* value depends on :option:`-I`.
|
||||
|
||||
.. deprecated:: 3.11
|
||||
|
||||
|
||||
.. c:function:: void Py_SetPythonHome(const wchar_t *home)
|
||||
|
||||
This API is kept for backward compatibility: setting
|
||||
:c:member:`PyConfig.home` should be used instead, see :ref:`Python
|
||||
Initialization Configuration <init-config>`.
|
||||
|
||||
Set the default "home" directory, that is, the location of the standard
|
||||
Python libraries. See :envvar:`PYTHONHOME` for the meaning of the
|
||||
argument string.
|
||||
|
||||
The argument should point to a zero-terminated character string in static
|
||||
storage whose contents will not change for the duration of the program's
|
||||
execution. No code in the Python interpreter will change the contents of
|
||||
this storage.
|
||||
|
||||
Use :c:func:`Py_DecodeLocale` to decode a bytes string to get a
|
||||
:c:expr:`wchar_*` string.
|
||||
|
||||
.. deprecated:: 3.11
|
||||
|
||||
|
||||
.. c:function:: wchar_t* Py_GetPythonHome()
|
||||
|
||||
Return the default "home", that is, the value set by
|
||||
|
@ -841,6 +972,33 @@ code, or when embedding the Python interpreter:
|
|||
This thread's interpreter state.
|
||||
|
||||
|
||||
.. c:function:: void PyEval_InitThreads()
|
||||
|
||||
.. index::
|
||||
single: PyEval_AcquireThread()
|
||||
single: PyEval_ReleaseThread()
|
||||
single: PyEval_SaveThread()
|
||||
single: PyEval_RestoreThread()
|
||||
|
||||
Deprecated function which does nothing.
|
||||
|
||||
In Python 3.6 and older, this function created the GIL if it didn't exist.
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
The function now does nothing.
|
||||
|
||||
.. versionchanged:: 3.7
|
||||
This function is now called by :c:func:`Py_Initialize()`, so you don't
|
||||
have to call it yourself anymore.
|
||||
|
||||
.. versionchanged:: 3.2
|
||||
This function cannot be called before :c:func:`Py_Initialize()` anymore.
|
||||
|
||||
.. deprecated:: 3.9
|
||||
|
||||
.. index:: pair: module; _thread
|
||||
|
||||
|
||||
.. c:function:: PyThreadState* PyEval_SaveThread()
|
||||
|
||||
Release the global interpreter lock (if it has been created) and reset the
|
||||
|
@ -1746,6 +1904,58 @@ Python-level trace functions in previous versions.
|
|||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
Reference tracing
|
||||
=================
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
.. c:type:: int (*PyRefTracer)(PyObject *, int event, void* data)
|
||||
|
||||
The type of the trace function registered using :c:func:`PyRefTracer_SetTracer`.
|
||||
The first parameter is a Python object that has been just created (when **event**
|
||||
is set to :c:data:`PyRefTracer_CREATE`) or about to be destroyed (when **event**
|
||||
is set to :c:data:`PyRefTracer_DESTROY`). The **data** argument is the opaque pointer
|
||||
that was provided when :c:func:`PyRefTracer_SetTracer` was called.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
.. c:var:: int PyRefTracer_CREATE
|
||||
|
||||
The value for the *event* parameter to :c:type:`PyRefTracer` functions when a Python
|
||||
object has been created.
|
||||
|
||||
.. c:var:: int PyRefTracer_DESTROY
|
||||
|
||||
The value for the *event* parameter to :c:type:`PyRefTracer` functions when a Python
|
||||
object has been destroyed.
|
||||
|
||||
.. c:function:: int PyRefTracer_SetTracer(PyRefTracer tracer, void *data)
|
||||
|
||||
Register a reference tracer function. The function will be called when a new
|
||||
Python has been created or when an object is going to be destroyed. If
|
||||
**data** is provided it must be an opaque pointer that will be provided when
|
||||
the tracer function is called. Return ``0`` on success. Set an exception and
|
||||
return ``-1`` on error.
|
||||
|
||||
Not that tracer functions **must not** create Python objects inside or
|
||||
otherwise the call will be re-entrant. The tracer also **must not** clear
|
||||
any existing exception or set an exception. The GIL will be held every time
|
||||
the tracer function is called.
|
||||
|
||||
The GIL must be held when calling this function.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
.. c:function:: PyRefTracer PyRefTracer_GetTracer(void** data)
|
||||
|
||||
Get the registered reference tracer function and the value of the opaque data
|
||||
pointer that was registered when :c:func:`PyRefTracer_SetTracer` was called.
|
||||
If no tracer was registered this function will return NULL and will set the
|
||||
**data** pointer to NULL.
|
||||
|
||||
The GIL must be held when calling this function.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
.. _advanced-debugging:
|
||||
|
||||
|
|
|
@ -1250,8 +1250,11 @@ PyConfig
|
|||
If non-zero, initialize the perf trampoline. See :ref:`perf_profiling`
|
||||
for more information.
|
||||
|
||||
Set by :option:`-X perf <-X>` command line option and by the
|
||||
:envvar:`PYTHONPERFSUPPORT` environment variable.
|
||||
Set by :option:`-X perf <-X>` command-line option and by the
|
||||
:envvar:`PYTHON_PERF_JIT_SUPPORT` environment variable for perf support
|
||||
with stack pointers and :option:`-X perf_jit <-X>` command-line option
|
||||
and by the :envvar:`PYTHON_PERF_JIT_SUPPORT` environment variable for perf
|
||||
support with DWARF JIT information.
|
||||
|
||||
Default: ``-1``.
|
||||
|
||||
|
|
|
@ -113,24 +113,28 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
|
|||
retrieved from the resulting value using :c:func:`PyLong_AsVoidPtr`.
|
||||
|
||||
|
||||
.. c:function:: PyObject* PyLong_FromNativeBytes(const void* buffer, size_t n_bytes, int endianness)
|
||||
.. c:function:: PyObject* PyLong_FromNativeBytes(const void* buffer, size_t n_bytes, int flags)
|
||||
|
||||
Create a Python integer from the value contained in the first *n_bytes* of
|
||||
*buffer*, interpreted as a two's-complement signed number.
|
||||
|
||||
*endianness* may be passed ``-1`` for the native endian that CPython was
|
||||
compiled with, or else ``0`` for big endian and ``1`` for little.
|
||||
*flags* are as for :c:func:`PyLong_AsNativeBytes`. Passing ``-1`` will select
|
||||
the native endian that CPython was compiled with and assume that the
|
||||
most-significant bit is a sign bit. Passing
|
||||
``Py_ASNATIVEBYTES_UNSIGNED_BUFFER`` will produce the same result as calling
|
||||
:c:func:`PyLong_FromUnsignedNativeBytes`. Other flags are ignored.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
.. c:function:: PyObject* PyLong_FromUnsignedNativeBytes(const void* buffer, size_t n_bytes, int endianness)
|
||||
.. c:function:: PyObject* PyLong_FromUnsignedNativeBytes(const void* buffer, size_t n_bytes, int flags)
|
||||
|
||||
Create a Python integer from the value contained in the first *n_bytes* of
|
||||
*buffer*, interpreted as an unsigned number.
|
||||
|
||||
*endianness* may be passed ``-1`` for the native endian that CPython was
|
||||
compiled with, or else ``0`` for big endian and ``1`` for little.
|
||||
*flags* are as for :c:func:`PyLong_AsNativeBytes`. Passing ``-1`` will select
|
||||
the native endian that CPython was compiled with and assume that the
|
||||
most-significant bit is not a sign bit. Flags other than endian are ignored.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
@ -354,14 +358,41 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
|
|||
Returns ``NULL`` on error. Use :c:func:`PyErr_Occurred` to disambiguate.
|
||||
|
||||
|
||||
.. c:function:: Py_ssize_t PyLong_AsNativeBytes(PyObject *pylong, void* buffer, Py_ssize_t n_bytes, int endianness)
|
||||
.. c:function:: Py_ssize_t PyLong_AsNativeBytes(PyObject *pylong, void* buffer, Py_ssize_t n_bytes, int flags)
|
||||
|
||||
Copy the Python integer value to a native *buffer* of size *n_bytes*::
|
||||
Copy the Python integer value *pylong* to a native *buffer* of size
|
||||
*n_bytes*. The *flags* can be set to ``-1`` to behave similarly to a C cast,
|
||||
or to values documented below to control the behavior.
|
||||
|
||||
Returns ``-1`` with an exception raised on error. This may happen if
|
||||
*pylong* cannot be interpreted as an integer, or if *pylong* was negative
|
||||
and the ``Py_ASNATIVEBYTES_REJECT_NEGATIVE`` flag was set.
|
||||
|
||||
Otherwise, returns the number of bytes required to store the value.
|
||||
If this is equal to or less than *n_bytes*, the entire value was copied.
|
||||
All *n_bytes* of the buffer are written: large buffers are padded with
|
||||
zeroes.
|
||||
|
||||
If the returned value is greater than than *n_bytes*, the value was
|
||||
truncated: as many of the lowest bits of the value as could fit are written,
|
||||
and the higher bits are ignored. This matches the typical behavior
|
||||
of a C-style downcast.
|
||||
|
||||
.. note::
|
||||
|
||||
Overflow is not considered an error. If the returned value
|
||||
is larger than *n_bytes*, most significant bits were discarded.
|
||||
|
||||
``0`` will never be returned.
|
||||
|
||||
Values are always copied as two's-complement.
|
||||
|
||||
Usage example::
|
||||
|
||||
int32_t value;
|
||||
Py_ssize_t bytes = PyLong_AsNativeBits(pylong, &value, sizeof(value), -1);
|
||||
Py_ssize_t bytes = PyLong_AsNativeBytes(pylong, &value, sizeof(value), -1);
|
||||
if (bytes < 0) {
|
||||
// A Python exception was set with the reason.
|
||||
// Failed. A Python exception was set with the reason.
|
||||
return NULL;
|
||||
}
|
||||
else if (bytes <= (Py_ssize_t)sizeof(value)) {
|
||||
|
@ -372,19 +403,24 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
|
|||
// lowest bits of pylong.
|
||||
}
|
||||
|
||||
The above example may look *similar* to
|
||||
:c:func:`PyLong_As* <PyLong_AsSize_t>`
|
||||
but instead fills in a specific caller defined type and never raises an
|
||||
error about of the :class:`int` *pylong*'s value regardless of *n_bytes*
|
||||
or the returned byte count.
|
||||
Passing zero to *n_bytes* will return the size of a buffer that would
|
||||
be large enough to hold the value. This may be larger than technically
|
||||
necessary, but not unreasonably so.
|
||||
|
||||
To get at the entire potentially big Python value, this can be used to
|
||||
reserve enough space and copy it::
|
||||
.. note::
|
||||
|
||||
Passing *n_bytes=0* to this function is not an accurate way to determine
|
||||
the bit length of a value.
|
||||
|
||||
If *n_bytes=0*, *buffer* may be ``NULL``.
|
||||
|
||||
To get at the entire Python value of an unknown size, the function can be
|
||||
called twice: first to determine the buffer size, then to fill it::
|
||||
|
||||
// Ask how much space we need.
|
||||
Py_ssize_t expected = PyLong_AsNativeBits(pylong, NULL, 0, -1);
|
||||
Py_ssize_t expected = PyLong_AsNativeBytes(pylong, NULL, 0, -1);
|
||||
if (expected < 0) {
|
||||
// A Python exception was set with the reason.
|
||||
// Failed. A Python exception was set with the reason.
|
||||
return NULL;
|
||||
}
|
||||
assert(expected != 0); // Impossible per the API definition.
|
||||
|
@ -394,12 +430,12 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
|
|||
return NULL;
|
||||
}
|
||||
// Safely get the entire value.
|
||||
Py_ssize_t bytes = PyLong_AsNativeBits(pylong, bignum, expected, -1);
|
||||
if (bytes < 0) { // Exception set.
|
||||
Py_ssize_t bytes = PyLong_AsNativeBytes(pylong, bignum, expected, -1);
|
||||
if (bytes < 0) { // Exception has been set.
|
||||
free(bignum);
|
||||
return NULL;
|
||||
}
|
||||
else if (bytes > expected) { // Be safe, should not be possible.
|
||||
else if (bytes > expected) { // This should not be possible.
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"Unexpected bignum truncation after a size check.");
|
||||
free(bignum);
|
||||
|
@ -409,35 +445,51 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
|
|||
// ... use bignum ...
|
||||
free(bignum);
|
||||
|
||||
*endianness* may be passed ``-1`` for the native endian that CPython was
|
||||
compiled with, or ``0`` for big endian and ``1`` for little.
|
||||
*flags* is either ``-1`` (``Py_ASNATIVEBYTES_DEFAULTS``) to select defaults
|
||||
that behave most like a C cast, or a combintation of the other flags in
|
||||
the table below.
|
||||
Note that ``-1`` cannot be combined with other flags.
|
||||
|
||||
Returns ``-1`` with an exception raised if *pylong* cannot be interpreted as
|
||||
an integer. Otherwise, return the size of the buffer required to store the
|
||||
value. If this is equal to or less than *n_bytes*, the entire value was
|
||||
copied. ``0`` will never be returned.
|
||||
Currently, ``-1`` corresponds to
|
||||
``Py_ASNATIVEBYTES_NATIVE_ENDIAN | Py_ASNATIVEBYTES_UNSIGNED_BUFFER``.
|
||||
|
||||
Unless an exception is raised, all *n_bytes* of the buffer will always be
|
||||
written. In the case of truncation, as many of the lowest bits of the value
|
||||
as could fit are written. This allows the caller to ignore all non-negative
|
||||
results if the intent is to match the typical behavior of a C-style
|
||||
downcast. No exception is set on truncation.
|
||||
============================================= ======
|
||||
Flag Value
|
||||
============================================= ======
|
||||
.. c:macro:: Py_ASNATIVEBYTES_DEFAULTS ``-1``
|
||||
.. c:macro:: Py_ASNATIVEBYTES_BIG_ENDIAN ``0``
|
||||
.. c:macro:: Py_ASNATIVEBYTES_LITTLE_ENDIAN ``1``
|
||||
.. c:macro:: Py_ASNATIVEBYTES_NATIVE_ENDIAN ``3``
|
||||
.. c:macro:: Py_ASNATIVEBYTES_UNSIGNED_BUFFER ``4``
|
||||
.. c:macro:: Py_ASNATIVEBYTES_REJECT_NEGATIVE ``8``
|
||||
============================================= ======
|
||||
|
||||
Values are always copied as two's-complement and sufficient buffer will be
|
||||
requested to include a sign bit. For example, this may cause an value that
|
||||
fits into 8 bytes when treated as unsigned to request 9 bytes, even though
|
||||
all eight bytes were copied into the buffer. What has been omitted is the
|
||||
zero sign bit -- redundant if the caller's intention is to treat the value
|
||||
as unsigned.
|
||||
Specifying ``Py_ASNATIVEBYTES_NATIVE_ENDIAN`` will override any other endian
|
||||
flags. Passing ``2`` is reserved.
|
||||
|
||||
Passing zero to *n_bytes* will return the size of a buffer that would
|
||||
be large enough to hold the value. This may be larger than technically
|
||||
necessary, but not unreasonably so.
|
||||
By default, sufficient buffer will be requested to include a sign bit.
|
||||
For example, when converting 128 with *n_bytes=1*, the function will return
|
||||
2 (or more) in order to store a zero sign bit.
|
||||
|
||||
If ``Py_ASNATIVEBYTES_UNSIGNED_BUFFER`` is specified, a zero sign bit
|
||||
will be omitted from size calculations. This allows, for example, 128 to fit
|
||||
in a single-byte buffer. If the destination buffer is later treated as
|
||||
signed, a positive input value may become negative.
|
||||
Note that the flag does not affect handling of negative values: for those,
|
||||
space for a sign bit is always requested.
|
||||
|
||||
Specifying ``Py_ASNATIVEBYTES_REJECT_NEGATIVE`` causes an exception to be set
|
||||
if *pylong* is negative. Without this flag, negative values will be copied
|
||||
provided there is enough space for at least one sign bit, regardless of
|
||||
whether ``Py_ASNATIVEBYTES_UNSIGNED_BUFFER`` was specified.
|
||||
|
||||
.. note::
|
||||
|
||||
Passing *n_bytes=0* to this function is not an accurate way to determine
|
||||
the bit length of a value.
|
||||
With the default *flags* (``-1``, or *UNSIGNED_BUFFER* without
|
||||
*REJECT_NEGATIVE*), multiple Python integers can map to a single value
|
||||
without overflow. For example, both ``255`` and ``-1`` fit a single-byte
|
||||
buffer and set all its bits.
|
||||
This matches typical C cast behavior.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
|
|
@ -411,6 +411,31 @@ The available slot types are:
|
|||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
.. c:macro:: Py_mod_gil
|
||||
|
||||
Specifies one of the following values:
|
||||
|
||||
.. c:macro:: Py_MOD_GIL_USED
|
||||
|
||||
The module depends on the presence of the global interpreter lock (GIL),
|
||||
and may access global state without synchronization.
|
||||
|
||||
.. c:macro:: Py_MOD_GIL_NOT_USED
|
||||
|
||||
The module is safe to run without an active GIL.
|
||||
|
||||
This slot is ignored by Python builds not configured with
|
||||
:option:`--disable-gil`. Otherwise, it determines whether or not importing
|
||||
this module will cause the GIL to be automatically enabled. See
|
||||
:envvar:`PYTHON_GIL` and :option:`-X gil <-X>` for more detail.
|
||||
|
||||
Multiple ``Py_mod_gil`` slots may not be specified in one module definition.
|
||||
|
||||
If ``Py_mod_gil`` is not specified, the import machinery defaults to
|
||||
``Py_MOD_GIL_USED``.
|
||||
|
||||
.. versionadded: 3.13
|
||||
|
||||
See :PEP:`489` for more details on multi-phase initialization.
|
||||
|
||||
Low-level module creation functions
|
||||
|
@ -609,6 +634,19 @@ state:
|
|||
|
||||
.. versionadded:: 3.9
|
||||
|
||||
.. c:function:: int PyUnstable_Module_SetGIL(PyObject *module, void *gil)
|
||||
|
||||
Indicate that *module* does or does not support running without the global
|
||||
interpreter lock (GIL), using one of the values from
|
||||
:c:macro:`Py_mod_gil`. It must be called during *module*'s initialization
|
||||
function. If this function is not called during module initialization, the
|
||||
import machinery assumes the module does not support running without the
|
||||
GIL. This function is only available in Python builds configured with
|
||||
:option:`--disable-gil`.
|
||||
Return ``-1`` on error, ``0`` on success.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
Module lookup
|
||||
^^^^^^^^^^^^^
|
||||
|
|
164
Doc/c-api/monitoring.rst
Normal file
164
Doc/c-api/monitoring.rst
Normal file
|
@ -0,0 +1,164 @@
|
|||
.. highlight:: c
|
||||
|
||||
.. _monitoring:
|
||||
|
||||
Monitorong C API
|
||||
================
|
||||
|
||||
Added in version 3.13.
|
||||
|
||||
An extension may need to interact with the event monitoring system. Subscribing
|
||||
to events and registering callbacks can be done via the Python API exposed in
|
||||
:mod:`sys.monitoring`.
|
||||
|
||||
Generating Execution Events
|
||||
===========================
|
||||
|
||||
The functions below make it possible for an extension to fire monitoring
|
||||
events as it emulates the execution of Python code. Each of these functions
|
||||
accepts a ``PyMonitoringState`` struct which contains concise information
|
||||
about the activation state of events, as well as the event arguments, which
|
||||
include a ``PyObject*`` representing the code object, the instruction offset
|
||||
and sometimes additional, event-specific arguments (see :mod:`sys.monitoring`
|
||||
for details about the signatures of the different event callbacks).
|
||||
The ``codelike`` argument should be an instance of :class:`types.CodeType`
|
||||
or of a type that emulates it.
|
||||
|
||||
The VM disables tracing when firing an event, so there is no need for user
|
||||
code to do that.
|
||||
|
||||
Monitoring functions should not be called with an exception set,
|
||||
except those listed below as working with the current exception.
|
||||
|
||||
.. c:type:: PyMonitoringState
|
||||
|
||||
Representation of the state of an event type. It is allocated by the user
|
||||
while its contents are maintained by the monitoring API functions described below.
|
||||
|
||||
|
||||
All of the functions below return 0 on success and -1 (with an exception set) on error.
|
||||
|
||||
See :mod:`sys.monitoring` for descriptions of the events.
|
||||
|
||||
.. c:function:: int PyMonitoring_FirePyStartEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
|
||||
|
||||
Fire a ``PY_START`` event.
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FirePyResumeEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
|
||||
|
||||
Fire a ``PY_RESUME`` event.
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FirePyReturnEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject* retval)
|
||||
|
||||
Fire a ``PY_RETURN`` event.
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FirePyYieldEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject* retval)
|
||||
|
||||
Fire a ``PY_YIELD`` event.
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FireCallEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject* callable, PyObject *arg0)
|
||||
|
||||
Fire a ``CALL`` event.
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FireLineEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, int lineno)
|
||||
|
||||
Fire a ``LINE`` event.
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FireJumpEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *target_offset)
|
||||
|
||||
Fire a ``JUMP`` event.
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FireBranchEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *target_offset)
|
||||
|
||||
Fire a ``BRANCH`` event.
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FireCReturnEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset, PyObject *retval)
|
||||
|
||||
Fire a ``C_RETURN`` event.
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FirePyThrowEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
|
||||
|
||||
Fire a ``PY_THROW`` event with the current exception (as returned by
|
||||
:c:func:`PyErr_GetRaisedException`).
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FireRaiseEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
|
||||
|
||||
Fire a ``RAISE`` event with the current exception (as returned by
|
||||
:c:func:`PyErr_GetRaisedException`).
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FireCRaiseEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
|
||||
|
||||
Fire a ``C_RAISE`` event with the current exception (as returned by
|
||||
:c:func:`PyErr_GetRaisedException`).
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FireReraiseEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
|
||||
|
||||
Fire a ``RERAISE`` event with the current exception (as returned by
|
||||
:c:func:`PyErr_GetRaisedException`).
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FireExceptionHandledEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
|
||||
|
||||
Fire an ``EXCEPTION_HANDLED`` event with the current exception (as returned by
|
||||
:c:func:`PyErr_GetRaisedException`).
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FirePyUnwindEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
|
||||
|
||||
Fire a ``PY_UNWIND`` event with the current exception (as returned by
|
||||
:c:func:`PyErr_GetRaisedException`).
|
||||
|
||||
|
||||
.. c:function:: int PyMonitoring_FireStopIterationEvent(PyMonitoringState *state, PyObject *codelike, int32_t offset)
|
||||
|
||||
Fire a ``STOP_ITERATION`` event with the current exception (as returned by
|
||||
:c:func:`PyErr_GetRaisedException`).
|
||||
|
||||
|
||||
Managing the Monitoring State
|
||||
-----------------------------
|
||||
|
||||
Monitoring states can be managed with the help of monitoring scopes. A scope
|
||||
would typically correspond to a python function.
|
||||
|
||||
.. :c:function:: int PyMonitoring_EnterScope(PyMonitoringState *state_array, uint64_t *version, const uint8_t *event_types, Py_ssize_t length)
|
||||
|
||||
Enter a monitored scope. ``event_types`` is an array of the event IDs for
|
||||
events that may be fired from the scope. For example, the ID of a ``PY_START``
|
||||
event is the value ``PY_MONITORING_EVENT_PY_START``, which is numerically equal
|
||||
to the base-2 logarithm of ``sys.monitoring.events.PY_START``.
|
||||
``state_array`` is an array with a monitoring state entry for each event in
|
||||
``event_types``, it is allocated by the user but populated by
|
||||
``PyMonitoring_EnterScope`` with information about the activation state of
|
||||
the event. The size of ``event_types`` (and hence also of ``state_array``)
|
||||
is given in ``length``.
|
||||
|
||||
The ``version`` argument is a pointer to a value which should be allocated
|
||||
by the user together with ``state_array`` and initialized to 0,
|
||||
and then set only by ``PyMonitoring_EnterScope`` itelf. It allows this
|
||||
function to determine whether event states have changed since the previous call,
|
||||
and to return quickly if they have not.
|
||||
|
||||
The scopes referred to here are lexical scopes: a function, class or method.
|
||||
``PyMonitoring_EnterScope`` should be called whenever the lexical scope is
|
||||
entered. Scopes can be reentered, reusing the same *state_array* and *version*,
|
||||
in situations like when emulating a recursive Python function. When a code-like's
|
||||
execution is paused, such as when emulating a generator, the scope needs to
|
||||
be exited and re-entered.
|
||||
|
||||
|
||||
.. :c:function:: int PyMonitoring_ExitScope(void)
|
||||
|
||||
Exit the last scope that was entered with ``PyMonitoring_EnterScope``.
|
|
@ -65,7 +65,7 @@ Object Protocol
|
|||
|
||||
Properly handle returning :c:data:`Py_NotImplemented` from within a C
|
||||
function (that is, create a new :term:`strong reference`
|
||||
to NotImplemented and return it).
|
||||
to :const:`NotImplemented` and return it).
|
||||
|
||||
|
||||
.. c:macro:: Py_PRINT_RAW
|
||||
|
|
|
@ -72,6 +72,35 @@ with the :term:`GIL` held.
|
|||
See :func:`time.time` for details important on this clock.
|
||||
|
||||
|
||||
Raw Clock Functions
|
||||
-------------------
|
||||
|
||||
Similar to clock functions, but don't set an exception on error and don't
|
||||
require the caller to hold the GIL.
|
||||
|
||||
On success, the functions return ``0``.
|
||||
|
||||
On failure, they set ``*result`` to ``0`` and return ``-1``, *without* setting
|
||||
an exception. To get the cause of the error, acquire the GIL and call the
|
||||
regular (non-``Raw``) function. Note that the regular function may succeed after
|
||||
the ``Raw`` one failed.
|
||||
|
||||
.. c:function:: int PyTime_MonotonicRaw(PyTime_t *result)
|
||||
|
||||
Similar to :c:func:`PyTime_Monotonic`,
|
||||
but don't set an exception on error and don't require holding the GIL.
|
||||
|
||||
.. c:function:: int PyTime_PerfCounterRaw(PyTime_t *result)
|
||||
|
||||
Similar to :c:func:`PyTime_PerfCounter`,
|
||||
but don't set an exception on error and don't require holding the GIL.
|
||||
|
||||
.. c:function:: int PyTime_TimeRaw(PyTime_t *result)
|
||||
|
||||
Similar to :c:func:`PyTime_Time`,
|
||||
but don't set an exception on error and don't require holding the GIL.
|
||||
|
||||
|
||||
Conversion functions
|
||||
--------------------
|
||||
|
||||
|
|
|
@ -59,6 +59,12 @@ Tuple Objects
|
|||
Return the object at position *pos* in the tuple pointed to by *p*. If *pos* is
|
||||
negative or out of bounds, return ``NULL`` and set an :exc:`IndexError` exception.
|
||||
|
||||
The returned reference is borrowed from the tuple *p*
|
||||
(that is: it is only valid as long as you hold a reference to *p*).
|
||||
To get a :term:`strong reference`, use
|
||||
:c:func:`Py_NewRef(PyTuple_GetItem(...)) <Py_NewRef>`
|
||||
or :c:func:`PySequence_GetItem`.
|
||||
|
||||
|
||||
.. c:function:: PyObject* PyTuple_GET_ITEM(PyObject *p, Py_ssize_t pos)
|
||||
|
||||
|
|
|
@ -883,6 +883,10 @@ and :c:data:`PyType_Type` effectively act as defaults.)
|
|||
:c:member:`~PyTypeObject.tp_richcompare` and :c:member:`~PyTypeObject.tp_hash`, when the subtype's
|
||||
:c:member:`~PyTypeObject.tp_richcompare` and :c:member:`~PyTypeObject.tp_hash` are both ``NULL``.
|
||||
|
||||
**Default:**
|
||||
|
||||
:c:data:`PyBaseObject_Type` uses :c:func:`PyObject_GenericHash`.
|
||||
|
||||
|
||||
.. c:member:: ternaryfunc PyTypeObject.tp_call
|
||||
|
||||
|
@ -1030,7 +1034,8 @@ and :c:data:`PyType_Type` effectively act as defaults.)
|
|||
the type, and the type object is INCREF'ed when a new instance is created, and
|
||||
DECREF'ed when an instance is destroyed (this does not apply to instances of
|
||||
subtypes; only the type referenced by the instance's ob_type gets INCREF'ed or
|
||||
DECREF'ed).
|
||||
DECREF'ed). Heap types should also :ref:`support garbage collection <supporting-cycle-detection>`
|
||||
as they can form a reference cycle with their own module object.
|
||||
|
||||
**Inheritance:**
|
||||
|
||||
|
@ -1376,7 +1381,7 @@ and :c:data:`PyType_Type` effectively act as defaults.)
|
|||
Py_VISIT(Py_TYPE(self));
|
||||
|
||||
It is only needed since Python 3.9. To support Python 3.8 and older, this
|
||||
line must be conditionnal::
|
||||
line must be conditional::
|
||||
|
||||
#if PY_VERSION_HEX >= 0x03090000
|
||||
Py_VISIT(Py_TYPE(self));
|
||||
|
|
|
@ -523,7 +523,7 @@ APIs:
|
|||
- Get the fully qualified name of an object type;
|
||||
call :c:func:`PyType_GetFullyQualifiedName`.
|
||||
|
||||
* - ``T#``
|
||||
* - ``#T``
|
||||
- :c:expr:`PyObject*`
|
||||
- Similar to ``T`` format, but use a colon (``:``) as separator between
|
||||
the module name and the qualified name.
|
||||
|
@ -533,7 +533,7 @@ APIs:
|
|||
- Get the fully qualified name of a type;
|
||||
call :c:func:`PyType_GetFullyQualifiedName`.
|
||||
|
||||
* - ``N#``
|
||||
* - ``#N``
|
||||
- :c:expr:`PyTypeObject*`
|
||||
- Similar to ``N`` format, but use a colon (``:``) as separator between
|
||||
the module name and the qualified name.
|
||||
|
@ -574,7 +574,7 @@ APIs:
|
|||
copied as-is to the result string, and any extra arguments discarded.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
Support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats added.
|
||||
Support for ``%T``, ``%#T``, ``%N`` and ``%#N`` formats added.
|
||||
|
||||
|
||||
.. c:function:: PyObject* PyUnicode_FromFormatV(const char *format, va_list vargs)
|
||||
|
|
25
Doc/conf.py
25
Doc/conf.py
|
@ -12,6 +12,8 @@ import time
|
|||
sys.path.append(os.path.abspath('tools/extensions'))
|
||||
sys.path.append(os.path.abspath('includes'))
|
||||
|
||||
from pyspecific import SOURCE_URI
|
||||
|
||||
# General configuration
|
||||
# ---------------------
|
||||
|
||||
|
@ -24,6 +26,7 @@ extensions = [
|
|||
'pyspecific',
|
||||
'sphinx.ext.coverage',
|
||||
'sphinx.ext.doctest',
|
||||
'sphinx.ext.extlinks',
|
||||
]
|
||||
|
||||
# Skip if downstream redistributors haven't installed them
|
||||
|
@ -128,6 +131,7 @@ nitpick_ignore = [
|
|||
('c:func', 'vsnprintf'),
|
||||
# Standard C types
|
||||
('c:type', 'FILE'),
|
||||
('c:type', 'int32_t'),
|
||||
('c:type', 'int64_t'),
|
||||
('c:type', 'intmax_t'),
|
||||
('c:type', 'off_t'),
|
||||
|
@ -295,8 +299,8 @@ smartquotes_excludes = {
|
|||
'languages': ['ja', 'fr', 'zh_TW', 'zh_CN'], 'builders': ['man', 'text'],
|
||||
}
|
||||
|
||||
# Avoid a warning with Sphinx >= 2.0
|
||||
master_doc = 'contents'
|
||||
# Avoid a warning with Sphinx >= 4.0
|
||||
root_doc = 'contents'
|
||||
|
||||
# Allow translation of index directives
|
||||
gettext_additional_targets = [
|
||||
|
@ -431,6 +435,10 @@ latex_appendices = ['glossary', 'about', 'license', 'copyright']
|
|||
epub_author = 'Python Documentation Authors'
|
||||
epub_publisher = 'Python Software Foundation'
|
||||
|
||||
# index pages are not valid xhtml
|
||||
# https://github.com/sphinx-doc/sphinx/issues/12359
|
||||
epub_use_index = False
|
||||
|
||||
# Options for the coverage checker
|
||||
# --------------------------------
|
||||
|
||||
|
@ -513,6 +521,19 @@ linkcheck_ignore = [
|
|||
r'https://unix.org/version2/whatsnew/lp64_wp.html',
|
||||
]
|
||||
|
||||
# Options for sphinx.ext.extlinks
|
||||
# -------------------------------
|
||||
|
||||
# This config is a dictionary of external sites,
|
||||
# mapping unique short aliases to a base URL and a prefix.
|
||||
# https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html
|
||||
extlinks = {
|
||||
"cve": ("https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-%s", "CVE-%s"),
|
||||
"cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"),
|
||||
"pypi": ("https://pypi.org/project/%s/", "%s"),
|
||||
"source": (SOURCE_URI, "%s"),
|
||||
}
|
||||
extlinks_detect_hardcoded_links = True
|
||||
|
||||
# Options for extensions
|
||||
# ----------------------
|
||||
|
|
9
Doc/data/stable_abi.dat
generated
9
Doc/data/stable_abi.dat
generated
|
@ -188,10 +188,14 @@ function,PyEval_EvalFrame,3.2,,
|
|||
function,PyEval_EvalFrameEx,3.2,,
|
||||
function,PyEval_GetBuiltins,3.2,,
|
||||
function,PyEval_GetFrame,3.2,,
|
||||
function,PyEval_GetFrameBuiltins,3.13,,
|
||||
function,PyEval_GetFrameGlobals,3.13,,
|
||||
function,PyEval_GetFrameLocals,3.13,,
|
||||
function,PyEval_GetFuncDesc,3.2,,
|
||||
function,PyEval_GetFuncName,3.2,,
|
||||
function,PyEval_GetGlobals,3.2,,
|
||||
function,PyEval_GetLocals,3.2,,
|
||||
function,PyEval_InitThreads,3.2,,
|
||||
function,PyEval_ReleaseThread,3.2,,
|
||||
function,PyEval_RestoreThread,3.2,,
|
||||
function,PyEval_SaveThread,3.2,,
|
||||
|
@ -617,6 +621,8 @@ function,PySys_FormatStdout,3.2,,
|
|||
function,PySys_GetObject,3.2,,
|
||||
function,PySys_GetXOptions,3.7,,
|
||||
function,PySys_ResetWarnOptions,3.2,,
|
||||
function,PySys_SetArgv,3.2,,
|
||||
function,PySys_SetArgvEx,3.2,,
|
||||
function,PySys_SetObject,3.2,,
|
||||
function,PySys_WriteStderr,3.2,,
|
||||
function,PySys_WriteStdout,3.2,,
|
||||
|
@ -679,6 +685,7 @@ function,PyType_GenericNew,3.2,,
|
|||
function,PyType_GetFlags,3.2,,
|
||||
function,PyType_GetFullyQualifiedName,3.13,,
|
||||
function,PyType_GetModule,3.10,,
|
||||
function,PyType_GetModuleByDef,3.13,,
|
||||
function,PyType_GetModuleName,3.13,,
|
||||
function,PyType_GetModuleState,3.10,,
|
||||
function,PyType_GetName,3.11,,
|
||||
|
@ -867,6 +874,8 @@ function,Py_NewInterpreter,3.2,,
|
|||
function,Py_NewRef,3.10,,
|
||||
function,Py_ReprEnter,3.2,,
|
||||
function,Py_ReprLeave,3.2,,
|
||||
function,Py_SetProgramName,3.2,,
|
||||
function,Py_SetPythonHome,3.2,,
|
||||
function,Py_SetRecursionLimit,3.2,,
|
||||
type,Py_UCS4,3.2,,
|
||||
macro,Py_UNBLOCK_THREADS,3.2,,
|
||||
|
|
|
@ -616,8 +616,7 @@ use ``p.read(n)``.
|
|||
("ptys") instead of pipes. Or you can use a Python interface to Don Libes'
|
||||
"expect" library. A Python extension that interfaces to expect is called
|
||||
"expy" and available from https://expectpy.sourceforge.net. A pure Python
|
||||
solution that works like expect is `pexpect
|
||||
<https://pypi.org/project/pexpect/>`_.
|
||||
solution that works like expect is :pypi:`pexpect`.
|
||||
|
||||
|
||||
How do I access the serial (RS232) port?
|
||||
|
@ -625,7 +624,7 @@ How do I access the serial (RS232) port?
|
|||
|
||||
For Win32, OSX, Linux, BSD, Jython, IronPython:
|
||||
|
||||
https://pypi.org/project/pyserial/
|
||||
:pypi:`pyserial`
|
||||
|
||||
For Unix, see a Usenet post by Mitch Chapman:
|
||||
|
||||
|
|
|
@ -9,13 +9,14 @@ Glossary
|
|||
.. glossary::
|
||||
|
||||
``>>>``
|
||||
The default Python prompt of the interactive shell. Often seen for code
|
||||
examples which can be executed interactively in the interpreter.
|
||||
The default Python prompt of the :term:`interactive` shell. Often
|
||||
seen for code examples which can be executed interactively in the
|
||||
interpreter.
|
||||
|
||||
``...``
|
||||
Can refer to:
|
||||
|
||||
* The default Python prompt of the interactive shell when entering the
|
||||
* The default Python prompt of the :term:`interactive` shell when entering the
|
||||
code for an indented code block, when within a pair of matching left and
|
||||
right delimiters (parentheses, square brackets, curly braces or triple
|
||||
quotes), or after specifying a decorator.
|
||||
|
@ -547,12 +548,12 @@ Glossary
|
|||
tasks such as compression or hashing. Also, the GIL is always released
|
||||
when doing I/O.
|
||||
|
||||
Past efforts to create a "free-threaded" interpreter (one which locks
|
||||
shared data at a much finer granularity) have not been successful
|
||||
because performance suffered in the common single-processor case. It
|
||||
is believed that overcoming this performance issue would make the
|
||||
implementation much more complicated and therefore costlier to maintain.
|
||||
|
||||
As of Python 3.13, the GIL can be disabled using the :option:`--disable-gil`
|
||||
build configuration. After building Python with this option, code must be
|
||||
run with :option:`-X gil 0 <-X>` or after setting the :envvar:`PYTHON_GIL=0 <PYTHON_GIL>`
|
||||
environment variable. This feature enables improved performance for
|
||||
multi-threaded applications and makes it easier to use multi-core CPUs
|
||||
efficiently. For more details, see :pep:`703`.
|
||||
|
||||
hash-based pyc
|
||||
A bytecode cache file that uses the hash rather than the last-modified
|
||||
|
@ -620,7 +621,8 @@ Glossary
|
|||
execute them and see their results. Just launch ``python`` with no
|
||||
arguments (possibly by selecting it from your computer's main
|
||||
menu). It is a very powerful way to test out new ideas or inspect
|
||||
modules and packages (remember ``help(x)``).
|
||||
modules and packages (remember ``help(x)``). For more on interactive
|
||||
mode, see :ref:`tut-interac`.
|
||||
|
||||
interpreted
|
||||
Python is an interpreted language, as opposed to a compiled one,
|
||||
|
@ -800,8 +802,7 @@ Glossary
|
|||
|
||||
method resolution order
|
||||
Method Resolution Order is the order in which base classes are searched
|
||||
for a member during lookup. See `The Python 2.3 Method Resolution Order
|
||||
<https://www.python.org/download/releases/2.3/mro/>`_ for details of the
|
||||
for a member during lookup. See :ref:`python_2.3_mro` for details of the
|
||||
algorithm used by the Python interpreter since the 2.3 release.
|
||||
|
||||
module
|
||||
|
@ -1085,6 +1086,10 @@ Glossary
|
|||
|
||||
See also :term:`namespace package`.
|
||||
|
||||
REPL
|
||||
An acronym for the "read–eval–print loop", another name for the
|
||||
:term:`interactive` interpreter shell.
|
||||
|
||||
__slots__
|
||||
A declaration inside a class that saves memory by pre-declaring space for
|
||||
instance attributes and eliminating instance dictionaries. Though
|
||||
|
|
|
@ -43,7 +43,7 @@ appearance---and the curses library will figure out what control codes
|
|||
need to be sent to the terminal to produce the right output. curses
|
||||
doesn't provide many user-interface concepts such as buttons, checkboxes,
|
||||
or dialogs; if you need such features, consider a user interface library such as
|
||||
`Urwid <https://pypi.org/project/urwid/>`_.
|
||||
:pypi:`Urwid`.
|
||||
|
||||
The curses library was originally written for BSD Unix; the later System V
|
||||
versions of Unix from AT&T added many enhancements and new functions. BSD curses
|
||||
|
@ -56,8 +56,7 @@ versions of curses carried by some proprietary Unixes may not support
|
|||
everything, though.
|
||||
|
||||
The Windows version of Python doesn't include the :mod:`curses`
|
||||
module. A ported version called `UniCurses
|
||||
<https://pypi.org/project/UniCurses>`_ is available.
|
||||
module. A ported version called :pypi:`UniCurses` is available.
|
||||
|
||||
|
||||
The Python curses module
|
||||
|
@ -429,8 +428,7 @@ User Input
|
|||
|
||||
The C curses library offers only very simple input mechanisms. Python's
|
||||
:mod:`curses` module adds a basic text-input widget. (Other libraries
|
||||
such as `Urwid <https://pypi.org/project/urwid/>`_ have more extensive
|
||||
collections of widgets.)
|
||||
such as :pypi:`Urwid` have more extensive collections of widgets.)
|
||||
|
||||
There are two methods for getting input from a window:
|
||||
|
||||
|
|
|
@ -33,4 +33,5 @@ Currently, the HOWTOs are:
|
|||
annotations.rst
|
||||
isolating-extensions.rst
|
||||
timerfd.rst
|
||||
mro.rst
|
||||
|
||||
|
|
|
@ -1846,8 +1846,11 @@ the use of a :class:`Filter` does not provide the desired result.
|
|||
|
||||
.. _zeromq-handlers:
|
||||
|
||||
Subclassing QueueHandler - a ZeroMQ example
|
||||
-------------------------------------------
|
||||
Subclassing QueueHandler and QueueListener- a ZeroMQ example
|
||||
------------------------------------------------------------
|
||||
|
||||
Subclass ``QueueHandler``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can use a :class:`QueueHandler` subclass to send messages to other kinds
|
||||
of queues, for example a ZeroMQ 'publish' socket. In the example below,the
|
||||
|
@ -1885,8 +1888,8 @@ data needed by the handler to create the socket::
|
|||
self.queue.close()
|
||||
|
||||
|
||||
Subclassing QueueListener - a ZeroMQ example
|
||||
--------------------------------------------
|
||||
Subclass ``QueueListener``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
You can also subclass :class:`QueueListener` to get messages from other kinds
|
||||
of queues, for example a ZeroMQ 'subscribe' socket. Here's an example::
|
||||
|
@ -1903,25 +1906,194 @@ of queues, for example a ZeroMQ 'subscribe' socket. Here's an example::
|
|||
msg = self.queue.recv_json()
|
||||
return logging.makeLogRecord(msg)
|
||||
|
||||
.. _pynng-handlers:
|
||||
|
||||
.. seealso::
|
||||
Subclassing QueueHandler and QueueListener- a ``pynng`` example
|
||||
---------------------------------------------------------------
|
||||
|
||||
Module :mod:`logging`
|
||||
API reference for the logging module.
|
||||
In a similar way to the above section, we can implement a listener and handler
|
||||
using :pypi:`pynng`, which is a Python binding to
|
||||
`NNG <https://nng.nanomsg.org/>`_, billed as a spiritual successor to ZeroMQ.
|
||||
The following snippets illustrate -- you can test them in an environment which has
|
||||
``pynng`` installed. Just for variety, we present the listener first.
|
||||
|
||||
Module :mod:`logging.config`
|
||||
Configuration API for the logging module.
|
||||
|
||||
Module :mod:`logging.handlers`
|
||||
Useful handlers included with the logging module.
|
||||
Subclass ``QueueListener``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
:ref:`A basic logging tutorial <logging-basic-tutorial>`
|
||||
.. code-block:: python
|
||||
|
||||
:ref:`A more advanced logging tutorial <logging-advanced-tutorial>`
|
||||
# listener.py
|
||||
import json
|
||||
import logging
|
||||
import logging.handlers
|
||||
|
||||
import pynng
|
||||
|
||||
DEFAULT_ADDR = "tcp://localhost:13232"
|
||||
|
||||
interrupted = False
|
||||
|
||||
class NNGSocketListener(logging.handlers.QueueListener):
|
||||
|
||||
def __init__(self, uri, /, *handlers, **kwargs):
|
||||
# Have a timeout for interruptability, and open a
|
||||
# subscriber socket
|
||||
socket = pynng.Sub0(listen=uri, recv_timeout=500)
|
||||
# The b'' subscription matches all topics
|
||||
topics = kwargs.pop('topics', None) or b''
|
||||
socket.subscribe(topics)
|
||||
# We treat the socket as a queue
|
||||
super().__init__(socket, *handlers, **kwargs)
|
||||
|
||||
def dequeue(self, block):
|
||||
data = None
|
||||
# Keep looping while not interrupted and no data received over the
|
||||
# socket
|
||||
while not interrupted:
|
||||
try:
|
||||
data = self.queue.recv(block=block)
|
||||
break
|
||||
except pynng.Timeout:
|
||||
pass
|
||||
except pynng.Closed: # sometimes happens when you hit Ctrl-C
|
||||
break
|
||||
if data is None:
|
||||
return None
|
||||
# Get the logging event sent from a publisher
|
||||
event = json.loads(data.decode('utf-8'))
|
||||
return logging.makeLogRecord(event)
|
||||
|
||||
def enqueue_sentinel(self):
|
||||
# Not used in this implementation, as the socket isn't really a
|
||||
# queue
|
||||
pass
|
||||
|
||||
logging.getLogger('pynng').propagate = False
|
||||
listener = NNGSocketListener(DEFAULT_ADDR, logging.StreamHandler(), topics=b'')
|
||||
listener.start()
|
||||
print('Press Ctrl-C to stop.')
|
||||
try:
|
||||
while True:
|
||||
pass
|
||||
except KeyboardInterrupt:
|
||||
interrupted = True
|
||||
finally:
|
||||
listener.stop()
|
||||
|
||||
|
||||
Subclass ``QueueHandler``
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. currentmodule:: logging
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# sender.py
|
||||
import json
|
||||
import logging
|
||||
import logging.handlers
|
||||
import time
|
||||
import random
|
||||
|
||||
import pynng
|
||||
|
||||
DEFAULT_ADDR = "tcp://localhost:13232"
|
||||
|
||||
class NNGSocketHandler(logging.handlers.QueueHandler):
|
||||
|
||||
def __init__(self, uri):
|
||||
socket = pynng.Pub0(dial=uri, send_timeout=500)
|
||||
super().__init__(socket)
|
||||
|
||||
def enqueue(self, record):
|
||||
# Send the record as UTF-8 encoded JSON
|
||||
d = dict(record.__dict__)
|
||||
data = json.dumps(d)
|
||||
self.queue.send(data.encode('utf-8'))
|
||||
|
||||
def close(self):
|
||||
self.queue.close()
|
||||
|
||||
logging.getLogger('pynng').propagate = False
|
||||
handler = NNGSocketHandler(DEFAULT_ADDR)
|
||||
# Make sure the process ID is in the output
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
handlers=[logging.StreamHandler(), handler],
|
||||
format='%(levelname)-8s %(name)10s %(process)6s %(message)s')
|
||||
levels = (logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR,
|
||||
logging.CRITICAL)
|
||||
logger_names = ('myapp', 'myapp.lib1', 'myapp.lib2')
|
||||
msgno = 1
|
||||
while True:
|
||||
# Just randomly select some loggers and levels and log away
|
||||
level = random.choice(levels)
|
||||
logger = logging.getLogger(random.choice(logger_names))
|
||||
logger.log(level, 'Message no. %5d' % msgno)
|
||||
msgno += 1
|
||||
delay = random.random() * 2 + 0.5
|
||||
time.sleep(delay)
|
||||
|
||||
You can run the above two snippets in separate command shells. If we run the
|
||||
listener in one shell and run the sender in two separate shells, we should see
|
||||
something like the following. In the first sender shell:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python sender.py
|
||||
DEBUG myapp 613 Message no. 1
|
||||
WARNING myapp.lib2 613 Message no. 2
|
||||
CRITICAL myapp.lib2 613 Message no. 3
|
||||
WARNING myapp.lib2 613 Message no. 4
|
||||
CRITICAL myapp.lib1 613 Message no. 5
|
||||
DEBUG myapp 613 Message no. 6
|
||||
CRITICAL myapp.lib1 613 Message no. 7
|
||||
INFO myapp.lib1 613 Message no. 8
|
||||
(and so on)
|
||||
|
||||
In the second sender shell:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python sender.py
|
||||
INFO myapp.lib2 657 Message no. 1
|
||||
CRITICAL myapp.lib2 657 Message no. 2
|
||||
CRITICAL myapp 657 Message no. 3
|
||||
CRITICAL myapp.lib1 657 Message no. 4
|
||||
INFO myapp.lib1 657 Message no. 5
|
||||
WARNING myapp.lib2 657 Message no. 6
|
||||
CRITICAL myapp 657 Message no. 7
|
||||
DEBUG myapp.lib1 657 Message no. 8
|
||||
(and so on)
|
||||
|
||||
In the listener shell:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ python listener.py
|
||||
Press Ctrl-C to stop.
|
||||
DEBUG myapp 613 Message no. 1
|
||||
WARNING myapp.lib2 613 Message no. 2
|
||||
INFO myapp.lib2 657 Message no. 1
|
||||
CRITICAL myapp.lib2 613 Message no. 3
|
||||
CRITICAL myapp.lib2 657 Message no. 2
|
||||
CRITICAL myapp 657 Message no. 3
|
||||
WARNING myapp.lib2 613 Message no. 4
|
||||
CRITICAL myapp.lib1 613 Message no. 5
|
||||
CRITICAL myapp.lib1 657 Message no. 4
|
||||
INFO myapp.lib1 657 Message no. 5
|
||||
DEBUG myapp 613 Message no. 6
|
||||
WARNING myapp.lib2 657 Message no. 6
|
||||
CRITICAL myapp 657 Message no. 7
|
||||
CRITICAL myapp.lib1 613 Message no. 7
|
||||
INFO myapp.lib1 613 Message no. 8
|
||||
DEBUG myapp.lib1 657 Message no. 8
|
||||
(and so on)
|
||||
|
||||
As you can see, the logging from the two sender processes is interleaved in the
|
||||
listener's output.
|
||||
|
||||
|
||||
An example dictionary-based configuration
|
||||
-----------------------------------------
|
||||
|
||||
|
@ -3403,9 +3575,8 @@ A Qt GUI for logging
|
|||
|
||||
A question that comes up from time to time is about how to log to a GUI
|
||||
application. The `Qt <https://www.qt.io/>`_ framework is a popular
|
||||
cross-platform UI framework with Python bindings using `PySide2
|
||||
<https://pypi.org/project/PySide2/>`_ or `PyQt5
|
||||
<https://pypi.org/project/PyQt5/>`_ libraries.
|
||||
cross-platform UI framework with Python bindings using :pypi:`PySide2`
|
||||
or :pypi:`PyQt5` libraries.
|
||||
|
||||
The following example shows how to log to a Qt GUI. This introduces a simple
|
||||
``QtHandler`` class which takes a callable, which should be a slot in the main
|
||||
|
@ -3418,7 +3589,7 @@ The worker thread is implemented using Qt's ``QThread`` class rather than the
|
|||
:mod:`threading` module, as there are circumstances where one has to use
|
||||
``QThread``, which offers better integration with other ``Qt`` components.
|
||||
|
||||
The code should work with recent releases of either ``PySide6``, ``PyQt6``,
|
||||
The code should work with recent releases of any of ``PySide6``, ``PyQt6``,
|
||||
``PySide2`` or ``PyQt5``. You should be able to adapt the approach to earlier
|
||||
versions of Qt. Please refer to the comments in the code snippet for more
|
||||
detailed information.
|
||||
|
|
671
Doc/howto/mro.rst
Normal file
671
Doc/howto/mro.rst
Normal file
|
@ -0,0 +1,671 @@
|
|||
.. _python_2.3_mro:
|
||||
|
||||
The Python 2.3 Method Resolution Order
|
||||
======================================
|
||||
|
||||
.. note::
|
||||
|
||||
This is a historical document, provided as an appendix to the official
|
||||
documentation.
|
||||
The Method Resolution Order discussed here was *introduced* in Python 2.3,
|
||||
but it is still used in later versions -- including Python 3.
|
||||
|
||||
By `Michele Simionato <https://www.phyast.pitt.edu/~micheles/>`__.
|
||||
|
||||
:Abstract:
|
||||
|
||||
*This document is intended for Python programmers who want to
|
||||
understand the C3 Method Resolution Order used in Python 2.3.
|
||||
Although it is not intended for newbies, it is quite pedagogical with
|
||||
many worked out examples. I am not aware of other publicly available
|
||||
documents with the same scope, therefore it should be useful.*
|
||||
|
||||
Disclaimer:
|
||||
|
||||
*I donate this document to the Python Software Foundation, under the
|
||||
Python 2.3 license. As usual in these circumstances, I warn the
|
||||
reader that what follows* should *be correct, but I don't give any
|
||||
warranty. Use it at your own risk and peril!*
|
||||
|
||||
Acknowledgments:
|
||||
|
||||
*All the people of the Python mailing list who sent me their support.
|
||||
Paul Foley who pointed out various imprecisions and made me to add the
|
||||
part on local precedence ordering. David Goodger for help with the
|
||||
formatting in reStructuredText. David Mertz for help with the editing.
|
||||
Finally, Guido van Rossum who enthusiastically added this document to
|
||||
the official Python 2.3 home-page.*
|
||||
|
||||
The beginning
|
||||
-------------
|
||||
|
||||
*Felix qui potuit rerum cognoscere causas* -- Virgilius
|
||||
|
||||
Everything started with a post by Samuele Pedroni to the Python
|
||||
development mailing list [#]_. In his post, Samuele showed that the
|
||||
Python 2.2 method resolution order is not monotonic and he proposed to
|
||||
replace it with the C3 method resolution order. Guido agreed with his
|
||||
arguments and therefore now Python 2.3 uses C3. The C3 method itself
|
||||
has nothing to do with Python, since it was invented by people working
|
||||
on Dylan and it is described in a paper intended for lispers [#]_. The
|
||||
present paper gives a (hopefully) readable discussion of the C3
|
||||
algorithm for Pythonistas who want to understand the reasons for the
|
||||
change.
|
||||
|
||||
First of all, let me point out that what I am going to say only applies
|
||||
to the *new style classes* introduced in Python 2.2: *classic classes*
|
||||
maintain their old method resolution order, depth first and then left to
|
||||
right. Therefore, there is no breaking of old code for classic classes;
|
||||
and even if in principle there could be breaking of code for Python 2.2
|
||||
new style classes, in practice the cases in which the C3 resolution
|
||||
order differs from the Python 2.2 method resolution order are so rare
|
||||
that no real breaking of code is expected. Therefore:
|
||||
|
||||
*Don't be scared!*
|
||||
|
||||
Moreover, unless you make strong use of multiple inheritance and you
|
||||
have non-trivial hierarchies, you don't need to understand the C3
|
||||
algorithm, and you can easily skip this paper. On the other hand, if
|
||||
you really want to know how multiple inheritance works, then this paper
|
||||
is for you. The good news is that things are not as complicated as you
|
||||
might expect.
|
||||
|
||||
Let me begin with some basic definitions.
|
||||
|
||||
1) Given a class C in a complicated multiple inheritance hierarchy, it
|
||||
is a non-trivial task to specify the order in which methods are
|
||||
overridden, i.e. to specify the order of the ancestors of C.
|
||||
|
||||
2) The list of the ancestors of a class C, including the class itself,
|
||||
ordered from the nearest ancestor to the furthest, is called the
|
||||
class precedence list or the *linearization* of C.
|
||||
|
||||
3) The *Method Resolution Order* (MRO) is the set of rules that
|
||||
construct the linearization. In the Python literature, the idiom
|
||||
"the MRO of C" is also used as a synonymous for the linearization of
|
||||
the class C.
|
||||
|
||||
4) For instance, in the case of single inheritance hierarchy, if C is a
|
||||
subclass of C1, and C1 is a subclass of C2, then the linearization of
|
||||
C is simply the list [C, C1 , C2]. However, with multiple
|
||||
inheritance hierarchies, the construction of the linearization is
|
||||
more cumbersome, since it is more difficult to construct a
|
||||
linearization that respects *local precedence ordering* and
|
||||
*monotonicity*.
|
||||
|
||||
5) I will discuss the local precedence ordering later, but I can give
|
||||
the definition of monotonicity here. A MRO is monotonic when the
|
||||
following is true: *if C1 precedes C2 in the linearization of C,
|
||||
then C1 precedes C2 in the linearization of any subclass of C*.
|
||||
Otherwise, the innocuous operation of deriving a new class could
|
||||
change the resolution order of methods, potentially introducing very
|
||||
subtle bugs. Examples where this happens will be shown later.
|
||||
|
||||
6) Not all classes admit a linearization. There are cases, in
|
||||
complicated hierarchies, where it is not possible to derive a class
|
||||
such that its linearization respects all the desired properties.
|
||||
|
||||
Here I give an example of this situation. Consider the hierarchy
|
||||
|
||||
>>> O = object
|
||||
>>> class X(O): pass
|
||||
>>> class Y(O): pass
|
||||
>>> class A(X,Y): pass
|
||||
>>> class B(Y,X): pass
|
||||
|
||||
which can be represented with the following inheritance graph, where I
|
||||
have denoted with O the ``object`` class, which is the beginning of any
|
||||
hierarchy for new style classes:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
-----------
|
||||
| |
|
||||
| O |
|
||||
| / \ |
|
||||
- X Y /
|
||||
| / | /
|
||||
| / |/
|
||||
A B
|
||||
\ /
|
||||
?
|
||||
|
||||
In this case, it is not possible to derive a new class C from A and B,
|
||||
since X precedes Y in A, but Y precedes X in B, therefore the method
|
||||
resolution order would be ambiguous in C.
|
||||
|
||||
Python 2.3 raises an exception in this situation (TypeError: MRO
|
||||
conflict among bases Y, X) forbidding the naive programmer from creating
|
||||
ambiguous hierarchies. Python 2.2 instead does not raise an exception,
|
||||
but chooses an *ad hoc* ordering (CABXYO in this case).
|
||||
|
||||
The C3 Method Resolution Order
|
||||
------------------------------
|
||||
|
||||
Let me introduce a few simple notations which will be useful for the
|
||||
following discussion. I will use the shortcut notation::
|
||||
|
||||
C1 C2 ... CN
|
||||
|
||||
to indicate the list of classes [C1, C2, ... , CN].
|
||||
|
||||
The *head* of the list is its first element::
|
||||
|
||||
head = C1
|
||||
|
||||
whereas the *tail* is the rest of the list::
|
||||
|
||||
tail = C2 ... CN.
|
||||
|
||||
I shall also use the notation::
|
||||
|
||||
C + (C1 C2 ... CN) = C C1 C2 ... CN
|
||||
|
||||
to denote the sum of the lists [C] + [C1, C2, ... ,CN].
|
||||
|
||||
Now I can explain how the MRO works in Python 2.3.
|
||||
|
||||
Consider a class C in a multiple inheritance hierarchy, with C
|
||||
inheriting from the base classes B1, B2, ... , BN. We want to
|
||||
compute the linearization L[C] of the class C. The rule is the
|
||||
following:
|
||||
|
||||
*the linearization of C is the sum of C plus the merge of the
|
||||
linearizations of the parents and the list of the parents.*
|
||||
|
||||
In symbolic notation::
|
||||
|
||||
L[C(B1 ... BN)] = C + merge(L[B1] ... L[BN], B1 ... BN)
|
||||
|
||||
In particular, if C is the ``object`` class, which has no parents, the
|
||||
linearization is trivial::
|
||||
|
||||
L[object] = object.
|
||||
|
||||
However, in general one has to compute the merge according to the following
|
||||
prescription:
|
||||
|
||||
*take the head of the first list, i.e L[B1][0]; if this head is not in
|
||||
the tail of any of the other lists, then add it to the linearization
|
||||
of C and remove it from the lists in the merge, otherwise look at the
|
||||
head of the next list and take it, if it is a good head. Then repeat
|
||||
the operation until all the class are removed or it is impossible to
|
||||
find good heads. In this case, it is impossible to construct the
|
||||
merge, Python 2.3 will refuse to create the class C and will raise an
|
||||
exception.*
|
||||
|
||||
This prescription ensures that the merge operation *preserves* the
|
||||
ordering, if the ordering can be preserved. On the other hand, if the
|
||||
order cannot be preserved (as in the example of serious order
|
||||
disagreement discussed above) then the merge cannot be computed.
|
||||
|
||||
The computation of the merge is trivial if C has only one parent
|
||||
(single inheritance); in this case::
|
||||
|
||||
L[C(B)] = C + merge(L[B],B) = C + L[B]
|
||||
|
||||
However, in the case of multiple inheritance things are more cumbersome
|
||||
and I don't expect you can understand the rule without a couple of
|
||||
examples ;-)
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
First example. Consider the following hierarchy:
|
||||
|
||||
>>> O = object
|
||||
>>> class F(O): pass
|
||||
>>> class E(O): pass
|
||||
>>> class D(O): pass
|
||||
>>> class C(D,F): pass
|
||||
>>> class B(D,E): pass
|
||||
>>> class A(B,C): pass
|
||||
|
||||
In this case the inheritance graph can be drawn as:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
6
|
||||
---
|
||||
Level 3 | O | (more general)
|
||||
/ --- \
|
||||
/ | \ |
|
||||
/ | \ |
|
||||
/ | \ |
|
||||
--- --- --- |
|
||||
Level 2 3 | D | 4| E | | F | 5 |
|
||||
--- --- --- |
|
||||
\ \ _ / | |
|
||||
\ / \ _ | |
|
||||
\ / \ | |
|
||||
--- --- |
|
||||
Level 1 1 | B | | C | 2 |
|
||||
--- --- |
|
||||
\ / |
|
||||
\ / \ /
|
||||
---
|
||||
Level 0 0 | A | (more specialized)
|
||||
---
|
||||
|
||||
|
||||
The linearizations of O,D,E and F are trivial::
|
||||
|
||||
L[O] = O
|
||||
L[D] = D O
|
||||
L[E] = E O
|
||||
L[F] = F O
|
||||
|
||||
The linearization of B can be computed as::
|
||||
|
||||
L[B] = B + merge(DO, EO, DE)
|
||||
|
||||
We see that D is a good head, therefore we take it and we are reduced to
|
||||
compute ``merge(O,EO,E)``. Now O is not a good head, since it is in the
|
||||
tail of the sequence EO. In this case the rule says that we have to
|
||||
skip to the next sequence. Then we see that E is a good head; we take
|
||||
it and we are reduced to compute ``merge(O,O)`` which gives O. Therefore::
|
||||
|
||||
L[B] = B D E O
|
||||
|
||||
Using the same procedure one finds::
|
||||
|
||||
L[C] = C + merge(DO,FO,DF)
|
||||
= C + D + merge(O,FO,F)
|
||||
= C + D + F + merge(O,O)
|
||||
= C D F O
|
||||
|
||||
Now we can compute::
|
||||
|
||||
L[A] = A + merge(BDEO,CDFO,BC)
|
||||
= A + B + merge(DEO,CDFO,C)
|
||||
= A + B + C + merge(DEO,DFO)
|
||||
= A + B + C + D + merge(EO,FO)
|
||||
= A + B + C + D + E + merge(O,FO)
|
||||
= A + B + C + D + E + F + merge(O,O)
|
||||
= A B C D E F O
|
||||
|
||||
In this example, the linearization is ordered in a pretty nice way
|
||||
according to the inheritance level, in the sense that lower levels (i.e.
|
||||
more specialized classes) have higher precedence (see the inheritance
|
||||
graph). However, this is not the general case.
|
||||
|
||||
I leave as an exercise for the reader to compute the linearization for
|
||||
my second example:
|
||||
|
||||
>>> O = object
|
||||
>>> class F(O): pass
|
||||
>>> class E(O): pass
|
||||
>>> class D(O): pass
|
||||
>>> class C(D,F): pass
|
||||
>>> class B(E,D): pass
|
||||
>>> class A(B,C): pass
|
||||
|
||||
The only difference with the previous example is the change B(D,E) -->
|
||||
B(E,D); however even such a little modification completely changes the
|
||||
ordering of the hierarchy:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
6
|
||||
---
|
||||
Level 3 | O |
|
||||
/ --- \
|
||||
/ | \
|
||||
/ | \
|
||||
/ | \
|
||||
--- --- ---
|
||||
Level 2 2 | E | 4 | D | | F | 5
|
||||
--- --- ---
|
||||
\ / \ /
|
||||
\ / \ /
|
||||
\ / \ /
|
||||
--- ---
|
||||
Level 1 1 | B | | C | 3
|
||||
--- ---
|
||||
\ /
|
||||
\ /
|
||||
---
|
||||
Level 0 0 | A |
|
||||
---
|
||||
|
||||
|
||||
Notice that the class E, which is in the second level of the hierarchy,
|
||||
precedes the class C, which is in the first level of the hierarchy, i.e.
|
||||
E is more specialized than C, even if it is in a higher level.
|
||||
|
||||
A lazy programmer can obtain the MRO directly from Python 2.2, since in
|
||||
this case it coincides with the Python 2.3 linearization. It is enough
|
||||
to invoke the .mro() method of class A:
|
||||
|
||||
>>> A.mro() # doctest: +NORMALIZE_WHITESPACE
|
||||
[<class 'A'>, <class 'B'>, <class 'E'>,
|
||||
<class 'C'>, <class 'D'>, <class 'F'>,
|
||||
<class 'object'>]
|
||||
|
||||
Finally, let me consider the example discussed in the first section,
|
||||
involving a serious order disagreement. In this case, it is
|
||||
straightforward to compute the linearizations of O, X, Y, A and B:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
L[O] = 0
|
||||
L[X] = X O
|
||||
L[Y] = Y O
|
||||
L[A] = A X Y O
|
||||
L[B] = B Y X O
|
||||
|
||||
However, it is impossible to compute the linearization for a class C
|
||||
that inherits from A and B::
|
||||
|
||||
L[C] = C + merge(AXYO, BYXO, AB)
|
||||
= C + A + merge(XYO, BYXO, B)
|
||||
= C + A + B + merge(XYO, YXO)
|
||||
|
||||
At this point we cannot merge the lists XYO and YXO, since X is in the
|
||||
tail of YXO whereas Y is in the tail of XYO: therefore there are no
|
||||
good heads and the C3 algorithm stops. Python 2.3 raises an error and
|
||||
refuses to create the class C.
|
||||
|
||||
Bad Method Resolution Orders
|
||||
----------------------------
|
||||
|
||||
A MRO is *bad* when it breaks such fundamental properties as local
|
||||
precedence ordering and monotonicity. In this section, I will show
|
||||
that both the MRO for classic classes and the MRO for new style classes
|
||||
in Python 2.2 are bad.
|
||||
|
||||
It is easier to start with the local precedence ordering. Consider the
|
||||
following example:
|
||||
|
||||
>>> F=type('Food',(),{'remember2buy':'spam'})
|
||||
>>> E=type('Eggs',(F,),{'remember2buy':'eggs'})
|
||||
>>> G=type('GoodFood',(F,E),{}) # under Python 2.3 this is an error! # doctest: +SKIP
|
||||
|
||||
with inheritance diagram
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
O
|
||||
|
|
||||
(buy spam) F
|
||||
| \
|
||||
| E (buy eggs)
|
||||
| /
|
||||
G
|
||||
|
||||
(buy eggs or spam ?)
|
||||
|
||||
|
||||
We see that class G inherits from F and E, with F *before* E: therefore
|
||||
we would expect the attribute *G.remember2buy* to be inherited by
|
||||
*F.rembermer2buy* and not by *E.remember2buy*: nevertheless Python 2.2
|
||||
gives
|
||||
|
||||
>>> G.remember2buy # doctest: +SKIP
|
||||
'eggs'
|
||||
|
||||
This is a breaking of local precedence ordering since the order in the
|
||||
local precedence list, i.e. the list of the parents of G, is not
|
||||
preserved in the Python 2.2 linearization of G::
|
||||
|
||||
L[G,P22]= G E F object # F *follows* E
|
||||
|
||||
One could argue that the reason why F follows E in the Python 2.2
|
||||
linearization is that F is less specialized than E, since F is the
|
||||
superclass of E; nevertheless the breaking of local precedence ordering
|
||||
is quite non-intuitive and error prone. This is particularly true since
|
||||
it is a different from old style classes:
|
||||
|
||||
>>> class F: remember2buy='spam'
|
||||
>>> class E(F): remember2buy='eggs'
|
||||
>>> class G(F,E): pass # doctest: +SKIP
|
||||
>>> G.remember2buy # doctest: +SKIP
|
||||
'spam'
|
||||
|
||||
In this case the MRO is GFEF and the local precedence ordering is
|
||||
preserved.
|
||||
|
||||
As a general rule, hierarchies such as the previous one should be
|
||||
avoided, since it is unclear if F should override E or viceversa.
|
||||
Python 2.3 solves the ambiguity by raising an exception in the creation
|
||||
of class G, effectively stopping the programmer from generating
|
||||
ambiguous hierarchies. The reason for that is that the C3 algorithm
|
||||
fails when the merge::
|
||||
|
||||
merge(FO,EFO,FE)
|
||||
|
||||
cannot be computed, because F is in the tail of EFO and E is in the tail
|
||||
of FE.
|
||||
|
||||
The real solution is to design a non-ambiguous hierarchy, i.e. to derive
|
||||
G from E and F (the more specific first) and not from F and E; in this
|
||||
case the MRO is GEF without any doubt.
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
O
|
||||
|
|
||||
F (spam)
|
||||
/ |
|
||||
(eggs) E |
|
||||
\ |
|
||||
G
|
||||
(eggs, no doubt)
|
||||
|
||||
|
||||
Python 2.3 forces the programmer to write good hierarchies (or, at
|
||||
least, less error-prone ones).
|
||||
|
||||
On a related note, let me point out that the Python 2.3 algorithm is
|
||||
smart enough to recognize obvious mistakes, as the duplication of
|
||||
classes in the list of parents:
|
||||
|
||||
>>> class A(object): pass
|
||||
>>> class C(A,A): pass # error
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in ?
|
||||
TypeError: duplicate base class A
|
||||
|
||||
Python 2.2 (both for classic classes and new style classes) in this
|
||||
situation, would not raise any exception.
|
||||
|
||||
Finally, I would like to point out two lessons we have learned from this
|
||||
example:
|
||||
|
||||
1. despite the name, the MRO determines the resolution order of
|
||||
attributes, not only of methods;
|
||||
|
||||
2. the default food for Pythonistas is spam ! (but you already knew
|
||||
that ;-)
|
||||
|
||||
Having discussed the issue of local precedence ordering, let me now
|
||||
consider the issue of monotonicity. My goal is to show that neither the
|
||||
MRO for classic classes nor that for Python 2.2 new style classes is
|
||||
monotonic.
|
||||
|
||||
To prove that the MRO for classic classes is non-monotonic is rather
|
||||
trivial, it is enough to look at the diamond diagram:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
|
||||
C
|
||||
/ \
|
||||
/ \
|
||||
A B
|
||||
\ /
|
||||
\ /
|
||||
D
|
||||
|
||||
One easily discerns the inconsistency::
|
||||
|
||||
L[B,P21] = B C # B precedes C : B's methods win
|
||||
L[D,P21] = D A C B C # B follows C : C's methods win!
|
||||
|
||||
On the other hand, there are no problems with the Python 2.2 and 2.3
|
||||
MROs, they give both::
|
||||
|
||||
L[D] = D A B C
|
||||
|
||||
Guido points out in his essay [#]_ that the classic MRO is not so bad in
|
||||
practice, since one can typically avoids diamonds for classic classes.
|
||||
But all new style classes inherit from ``object``, therefore diamonds are
|
||||
unavoidable and inconsistencies shows up in every multiple inheritance
|
||||
graph.
|
||||
|
||||
The MRO of Python 2.2 makes breaking monotonicity difficult, but not
|
||||
impossible. The following example, originally provided by Samuele
|
||||
Pedroni, shows that the MRO of Python 2.2 is non-monotonic:
|
||||
|
||||
>>> class A(object): pass
|
||||
>>> class B(object): pass
|
||||
>>> class C(object): pass
|
||||
>>> class D(object): pass
|
||||
>>> class E(object): pass
|
||||
>>> class K1(A,B,C): pass
|
||||
>>> class K2(D,B,E): pass
|
||||
>>> class K3(D,A): pass
|
||||
>>> class Z(K1,K2,K3): pass
|
||||
|
||||
Here are the linearizations according to the C3 MRO (the reader should
|
||||
verify these linearizations as an exercise and draw the inheritance
|
||||
diagram ;-) ::
|
||||
|
||||
L[A] = A O
|
||||
L[B] = B O
|
||||
L[C] = C O
|
||||
L[D] = D O
|
||||
L[E] = E O
|
||||
L[K1]= K1 A B C O
|
||||
L[K2]= K2 D B E O
|
||||
L[K3]= K3 D A O
|
||||
L[Z] = Z K1 K2 K3 D A B C E O
|
||||
|
||||
Python 2.2 gives exactly the same linearizations for A, B, C, D, E, K1,
|
||||
K2 and K3, but a different linearization for Z::
|
||||
|
||||
L[Z,P22] = Z K1 K3 A K2 D B C E O
|
||||
|
||||
It is clear that this linearization is *wrong*, since A comes before D
|
||||
whereas in the linearization of K3 A comes *after* D. In other words, in
|
||||
K3 methods derived by D override methods derived by A, but in Z, which
|
||||
still is a subclass of K3, methods derived by A override methods derived
|
||||
by D! This is a violation of monotonicity. Moreover, the Python 2.2
|
||||
linearization of Z is also inconsistent with local precedence ordering,
|
||||
since the local precedence list of the class Z is [K1, K2, K3] (K2
|
||||
precedes K3), whereas in the linearization of Z K2 *follows* K3. These
|
||||
problems explain why the 2.2 rule has been dismissed in favor of the C3
|
||||
rule.
|
||||
|
||||
The end
|
||||
-------
|
||||
|
||||
This section is for the impatient reader, who skipped all the previous
|
||||
sections and jumped immediately to the end. This section is for the
|
||||
lazy programmer too, who didn't want to exercise her/his brain.
|
||||
Finally, it is for the programmer with some hubris, otherwise s/he would
|
||||
not be reading a paper on the C3 method resolution order in multiple
|
||||
inheritance hierarchies ;-) These three virtues taken all together (and
|
||||
*not* separately) deserve a prize: the prize is a short Python 2.2
|
||||
script that allows you to compute the 2.3 MRO without risk to your
|
||||
brain. Simply change the last line to play with the various examples I
|
||||
have discussed in this paper.::
|
||||
|
||||
#<mro.py>
|
||||
|
||||
"""C3 algorithm by Samuele Pedroni (with readability enhanced by me)."""
|
||||
|
||||
class __metaclass__(type):
|
||||
"All classes are metamagically modified to be nicely printed"
|
||||
__repr__ = lambda cls: cls.__name__
|
||||
|
||||
class ex_2:
|
||||
"Serious order disagreement" #From Guido
|
||||
class O: pass
|
||||
class X(O): pass
|
||||
class Y(O): pass
|
||||
class A(X,Y): pass
|
||||
class B(Y,X): pass
|
||||
try:
|
||||
class Z(A,B): pass #creates Z(A,B) in Python 2.2
|
||||
except TypeError:
|
||||
pass # Z(A,B) cannot be created in Python 2.3
|
||||
|
||||
class ex_5:
|
||||
"My first example"
|
||||
class O: pass
|
||||
class F(O): pass
|
||||
class E(O): pass
|
||||
class D(O): pass
|
||||
class C(D,F): pass
|
||||
class B(D,E): pass
|
||||
class A(B,C): pass
|
||||
|
||||
class ex_6:
|
||||
"My second example"
|
||||
class O: pass
|
||||
class F(O): pass
|
||||
class E(O): pass
|
||||
class D(O): pass
|
||||
class C(D,F): pass
|
||||
class B(E,D): pass
|
||||
class A(B,C): pass
|
||||
|
||||
class ex_9:
|
||||
"Difference between Python 2.2 MRO and C3" #From Samuele
|
||||
class O: pass
|
||||
class A(O): pass
|
||||
class B(O): pass
|
||||
class C(O): pass
|
||||
class D(O): pass
|
||||
class E(O): pass
|
||||
class K1(A,B,C): pass
|
||||
class K2(D,B,E): pass
|
||||
class K3(D,A): pass
|
||||
class Z(K1,K2,K3): pass
|
||||
|
||||
def merge(seqs):
|
||||
print '\n\nCPL[%s]=%s' % (seqs[0][0],seqs),
|
||||
res = []; i=0
|
||||
while 1:
|
||||
nonemptyseqs=[seq for seq in seqs if seq]
|
||||
if not nonemptyseqs: return res
|
||||
i+=1; print '\n',i,'round: candidates...',
|
||||
for seq in nonemptyseqs: # find merge candidates among seq heads
|
||||
cand = seq[0]; print ' ',cand,
|
||||
nothead=[s for s in nonemptyseqs if cand in s[1:]]
|
||||
if nothead: cand=None #reject candidate
|
||||
else: break
|
||||
if not cand: raise "Inconsistent hierarchy"
|
||||
res.append(cand)
|
||||
for seq in nonemptyseqs: # remove cand
|
||||
if seq[0] == cand: del seq[0]
|
||||
|
||||
def mro(C):
|
||||
"Compute the class precedence list (mro) according to C3"
|
||||
return merge([[C]]+map(mro,C.__bases__)+[list(C.__bases__)])
|
||||
|
||||
def print_mro(C):
|
||||
print '\nMRO[%s]=%s' % (C,mro(C))
|
||||
print '\nP22 MRO[%s]=%s' % (C,C.mro())
|
||||
|
||||
print_mro(ex_9.Z)
|
||||
|
||||
#</mro.py>
|
||||
|
||||
That's all folks,
|
||||
|
||||
enjoy !
|
||||
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
.. [#] The thread on python-dev started by Samuele Pedroni:
|
||||
https://mail.python.org/pipermail/python-dev/2002-October/029035.html
|
||||
|
||||
.. [#] The paper *A Monotonic Superclass Linearization for Dylan*:
|
||||
https://doi.org/10.1145/236337.236343
|
||||
|
||||
.. [#] Guido van Rossum's essay, *Unifying types and classes in Python 2.2*:
|
||||
https://web.archive.org/web/20140210194412/http://www.python.org/download/releases/2.2.2/descrintro
|
|
@ -162,12 +162,12 @@ the :option:`!-X` option takes precedence over the environment variable.
|
|||
|
||||
Example, using the environment variable::
|
||||
|
||||
$ PYTHONPERFSUPPORT=1 python script.py
|
||||
$ PYTHONPERFSUPPORT=1 perf record -F 9999 -g -o perf.data python script.py
|
||||
$ perf report -g -i perf.data
|
||||
|
||||
Example, using the :option:`!-X` option::
|
||||
|
||||
$ python -X perf script.py
|
||||
$ perf record -F 9999 -g -o perf.data python -X perf script.py
|
||||
$ perf report -g -i perf.data
|
||||
|
||||
Example, using the :mod:`sys` APIs in file :file:`example.py`:
|
||||
|
@ -184,7 +184,7 @@ Example, using the :mod:`sys` APIs in file :file:`example.py`:
|
|||
|
||||
...then::
|
||||
|
||||
$ python ./example.py
|
||||
$ perf record -F 9999 -g -o perf.data python ./example.py
|
||||
$ perf report -g -i perf.data
|
||||
|
||||
|
||||
|
@ -205,3 +205,62 @@ You can check if your system has been compiled with this flag by running::
|
|||
If you don't see any output it means that your interpreter has not been compiled with
|
||||
frame pointers and therefore it may not be able to show Python functions in the output
|
||||
of ``perf``.
|
||||
|
||||
|
||||
How to work without frame pointers
|
||||
----------------------------------
|
||||
|
||||
If you are working with a Python interpreter that has been compiled without
|
||||
frame pointers, you can still use the ``perf`` profiler, but the overhead will be
|
||||
a bit higher because Python needs to generate unwinding information for every
|
||||
Python function call on the fly. Additionally, ``perf`` will take more time to
|
||||
process the data because it will need to use the DWARF debugging information to
|
||||
unwind the stack and this is a slow process.
|
||||
|
||||
To enable this mode, you can use the environment variable
|
||||
:envvar:`PYTHON_PERF_JIT_SUPPORT` or the :option:`-X perf_jit <-X>` option,
|
||||
which will enable the JIT mode for the ``perf`` profiler.
|
||||
|
||||
.. note::
|
||||
|
||||
Due to a bug in the ``perf`` tool, only ``perf`` versions higher than v6.8
|
||||
will work with the JIT mode. The fix was also backported to the v6.7.2
|
||||
version of the tool.
|
||||
|
||||
Note that when checking the version of the ``perf`` tool (which can be done
|
||||
by running ``perf version``) you must take into account that some distros
|
||||
add some custom version numbers including a ``-`` character. This means
|
||||
that ``perf 6.7-3`` is not necessarily ``perf 6.7.3``.
|
||||
|
||||
When using the perf JIT mode, you need an extra step before you can run ``perf
|
||||
report``. You need to call the ``perf inject`` command to inject the JIT
|
||||
information into the ``perf.data`` file.::
|
||||
|
||||
$ perf record -F 9999 -g --call-graph dwarf -o perf.data python -Xperf_jit my_script.py
|
||||
$ perf inject -i perf.data --jit --output perf.jit.data
|
||||
$ perf report -g -i perf.jit.data
|
||||
|
||||
or using the environment variable::
|
||||
|
||||
$ PYTHON_PERF_JIT_SUPPORT=1 perf record -F 9999 -g --call-graph dwarf -o perf.data python my_script.py
|
||||
$ perf inject -i perf.data --jit --output perf.jit.data
|
||||
$ perf report -g -i perf.jit.data
|
||||
|
||||
``perf inject --jit`` command will read ``perf.data``,
|
||||
automatically pick up the perf dump file that Python creates (in
|
||||
``/tmp/perf-$PID.dump``), and then create ``perf.jit.data`` which merges all the
|
||||
JIT information together. It should also create a lot of ``jitted-XXXX-N.so``
|
||||
files in the current directory which are ELF images for all the JIT trampolines
|
||||
that were created by Python.
|
||||
|
||||
.. warning::
|
||||
Notice that when using ``--call-graph dwarf`` the ``perf`` tool will take
|
||||
snapshots of the stack of the process being profiled and save the
|
||||
information in the ``perf.data`` file. By default the size of the stack dump
|
||||
is 8192 bytes but the user can change the size by passing the size after
|
||||
comma like ``--call-graph dwarf,4096``. The size of the stack dump is
|
||||
important because if the size is too small ``perf`` will not be able to
|
||||
unwind the stack and the output will be incomplete. On the other hand, if
|
||||
the size is too big, then ``perf`` won't be able to sample the process as
|
||||
frequently as it would like as the overhead will be higher.
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ descriptors to wait until the file descriptor is ready for reading:
|
|||
# In 1.5 seconds, 1st timer, 2nd timer and 3rd timer fires at once.
|
||||
#
|
||||
# If a timer file descriptor is signaled more than once since
|
||||
# the last os.read() call, os.read() returns the nubmer of signaled
|
||||
# the last os.read() call, os.read() returns the number of signaled
|
||||
# as host order of class bytes.
|
||||
print(f"Signaled events={events}")
|
||||
for fd, event in events:
|
||||
|
|
|
@ -594,5 +594,5 @@ This document was reviewed and revised by John Lee.
|
|||
scripts with a localhost server, I have to prevent urllib from using
|
||||
the proxy.
|
||||
.. [#] urllib opener for SSL proxy (CONNECT method): `ASPN Cookbook Recipe
|
||||
<https://code.activestate.com/recipes/456195/>`_.
|
||||
<https://code.activestate.com/recipes/456195-urrlib2-opener-for-ssl-proxy-connect-method/>`_.
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ must be running an SMTP server.
|
|||
# Guess the content type based on the file's extension. Encoding
|
||||
# will be ignored, although we should check for simple things like
|
||||
# gzip'd or compressed files.
|
||||
ctype, encoding = mimetypes.guess_type(path)
|
||||
ctype, encoding = mimetypes.guess_file_type(path)
|
||||
if ctype is None or encoding is not None:
|
||||
# No guess could be made, or the file is encoded (compressed), so
|
||||
# use a generic bag-of-bits type.
|
||||
|
|
8
Doc/includes/wasm-ios-notavail.rst
Normal file
8
Doc/includes/wasm-ios-notavail.rst
Normal file
|
@ -0,0 +1,8 @@
|
|||
.. include for modules that don't work on WASM or iOS
|
||||
|
||||
.. availability:: not WASI, not iOS.
|
||||
|
||||
This module does not work or is not available on WebAssembly platforms, or
|
||||
on iOS. See :ref:`wasm-availability` for more information on WASM
|
||||
availability; see :ref:`iOS-availability` for more information on iOS
|
||||
availability.
|
|
@ -1,7 +1,6 @@
|
|||
.. include for modules that don't work on WASM
|
||||
|
||||
.. availability:: not Emscripten, not WASI.
|
||||
.. availability:: not WASI.
|
||||
|
||||
This module does not work or is not available on WebAssembly platforms
|
||||
``wasm32-emscripten`` and ``wasm32-wasi``. See
|
||||
This module does not work or is not available on WebAssembly. See
|
||||
:ref:`wasm-availability` for more information.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`__future__` --- Future statement definitions
|
||||
==================================================
|
||||
:mod:`!__future__` --- Future statement definitions
|
||||
===================================================
|
||||
|
||||
.. module:: __future__
|
||||
:synopsis: Future statement definitions
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`__main__` --- Top-level code environment
|
||||
==============================================
|
||||
:mod:`!__main__` --- Top-level code environment
|
||||
===============================================
|
||||
|
||||
.. module:: __main__
|
||||
:synopsis: The environment where top-level code is run. Covers command-line
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`_thread` --- Low-level threading API
|
||||
==========================================
|
||||
:mod:`!_thread` --- Low-level threading API
|
||||
===========================================
|
||||
|
||||
.. module:: _thread
|
||||
:synopsis: Low-level threading API.
|
||||
|
@ -169,14 +169,14 @@ Lock objects have the following methods:
|
|||
time can acquire a lock --- that's their reason for existence).
|
||||
|
||||
If the *blocking* argument is present, the action depends on its
|
||||
value: if it is False, the lock is only acquired if it can be acquired
|
||||
immediately without waiting, while if it is True, the lock is acquired
|
||||
value: if it is false, the lock is only acquired if it can be acquired
|
||||
immediately without waiting, while if it is true, the lock is acquired
|
||||
unconditionally as above.
|
||||
|
||||
If the floating-point *timeout* argument is present and positive, it
|
||||
specifies the maximum wait time in seconds before returning. A negative
|
||||
*timeout* argument specifies an unbounded wait. You cannot specify
|
||||
a *timeout* if *blocking* is False.
|
||||
a *timeout* if *blocking* is false.
|
||||
|
||||
The return value is ``True`` if the lock is acquired successfully,
|
||||
``False`` if not.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`abc` --- Abstract Base Classes
|
||||
====================================
|
||||
:mod:`!abc` --- Abstract Base Classes
|
||||
=====================================
|
||||
|
||||
.. module:: abc
|
||||
:synopsis: Abstract base classes according to :pep:`3119`.
|
||||
|
|
|
@ -16,7 +16,6 @@ but they are available on most other systems as well. Here's an overview:
|
|||
io.rst
|
||||
time.rst
|
||||
argparse.rst
|
||||
getopt.rst
|
||||
logging.rst
|
||||
logging.config.rst
|
||||
logging.handlers.rst
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`argparse` --- Parser for command-line options, arguments and sub-commands
|
||||
===============================================================================
|
||||
:mod:`!argparse` --- Parser for command-line options, arguments and sub-commands
|
||||
================================================================================
|
||||
|
||||
.. module:: argparse
|
||||
:synopsis: Command-line option and argument parsing library.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`array` --- Efficient arrays of numeric values
|
||||
===================================================
|
||||
:mod:`!array` --- Efficient arrays of numeric values
|
||||
====================================================
|
||||
|
||||
.. module:: array
|
||||
:synopsis: Space efficient arrays of uniformly typed numeric values.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`ast` --- Abstract Syntax Trees
|
||||
====================================
|
||||
:mod:`!ast` --- Abstract Syntax Trees
|
||||
=====================================
|
||||
|
||||
.. module:: ast
|
||||
:synopsis: Abstract Syntax Tree classes and manipulation.
|
||||
|
@ -61,7 +61,7 @@ Node classes
|
|||
|
||||
.. attribute:: _fields
|
||||
|
||||
Each concrete class has an attribute :attr:`_fields` which gives the names
|
||||
Each concrete class has an attribute :attr:`!_fields` which gives the names
|
||||
of all child nodes.
|
||||
|
||||
Each instance of a concrete class has one attribute for each child node,
|
||||
|
@ -74,6 +74,18 @@ Node classes
|
|||
as Python lists. All possible attributes must be present and have valid
|
||||
values when compiling an AST with :func:`compile`.
|
||||
|
||||
.. attribute:: _field_types
|
||||
|
||||
The :attr:`!_field_types` attribute on each concrete class is a dictionary
|
||||
mapping field names (as also listed in :attr:`_fields`) to their types.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> ast.TypeVar._field_types
|
||||
{'name': <class 'str'>, 'bound': ast.expr | None, 'default_value': ast.expr | None}
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
.. attribute:: lineno
|
||||
col_offset
|
||||
end_lineno
|
||||
|
@ -108,7 +120,8 @@ Node classes
|
|||
|
||||
If a field that is optional in the grammar is omitted from the constructor,
|
||||
it defaults to ``None``. If a list field is omitted, it defaults to the empty
|
||||
list. If any other field is omitted, a :exc:`DeprecationWarning` is raised
|
||||
list. If a field of type :class:`!ast.expr_context` is omitted, it defaults to
|
||||
:class:`Load() <ast.Load>`. If any other field is omitted, a :exc:`DeprecationWarning` is raised
|
||||
and the AST node will not have this field. In Python 3.15, this condition will
|
||||
raise an error.
|
||||
|
||||
|
@ -173,8 +186,7 @@ Root nodes
|
|||
Assign(
|
||||
targets=[
|
||||
Name(id='x', ctx=Store())],
|
||||
value=Constant(value=1))],
|
||||
type_ignores=[])
|
||||
value=Constant(value=1))])
|
||||
|
||||
|
||||
.. class:: Expression(body)
|
||||
|
@ -302,8 +314,7 @@ Literals
|
|||
value=Call(
|
||||
func=Name(id='sin', ctx=Load()),
|
||||
args=[
|
||||
Name(id='a', ctx=Load())],
|
||||
keywords=[]),
|
||||
Name(id='a', ctx=Load())]),
|
||||
conversion=-1,
|
||||
format_spec=JoinedStr(
|
||||
values=[
|
||||
|
@ -398,8 +409,7 @@ Variables
|
|||
Module(
|
||||
body=[
|
||||
Expr(
|
||||
value=Name(id='a', ctx=Load()))],
|
||||
type_ignores=[])
|
||||
value=Name(id='a', ctx=Load()))])
|
||||
|
||||
>>> print(ast.dump(ast.parse('a = 1'), indent=4))
|
||||
Module(
|
||||
|
@ -407,16 +417,14 @@ Variables
|
|||
Assign(
|
||||
targets=[
|
||||
Name(id='a', ctx=Store())],
|
||||
value=Constant(value=1))],
|
||||
type_ignores=[])
|
||||
value=Constant(value=1))])
|
||||
|
||||
>>> print(ast.dump(ast.parse('del a'), indent=4))
|
||||
Module(
|
||||
body=[
|
||||
Delete(
|
||||
targets=[
|
||||
Name(id='a', ctx=Del())])],
|
||||
type_ignores=[])
|
||||
Name(id='a', ctx=Del())])])
|
||||
|
||||
|
||||
.. class:: Starred(value, ctx)
|
||||
|
@ -439,8 +447,7 @@ Variables
|
|||
value=Name(id='b', ctx=Store()),
|
||||
ctx=Store())],
|
||||
ctx=Store())],
|
||||
value=Name(id='it', ctx=Load()))],
|
||||
type_ignores=[])
|
||||
value=Name(id='it', ctx=Load()))])
|
||||
|
||||
|
||||
.. _ast-expressions:
|
||||
|
@ -463,8 +470,7 @@ Expressions
|
|||
Expr(
|
||||
value=UnaryOp(
|
||||
op=USub(),
|
||||
operand=Name(id='a', ctx=Load())))],
|
||||
type_ignores=[])
|
||||
operand=Name(id='a', ctx=Load())))])
|
||||
|
||||
|
||||
.. class:: UnaryOp(op, operand)
|
||||
|
@ -591,8 +597,7 @@ Expressions
|
|||
* ``keywords`` holds a list of :class:`.keyword` objects representing
|
||||
arguments passed by keyword.
|
||||
|
||||
When creating a ``Call`` node, ``args`` and ``keywords`` are required, but
|
||||
they can be empty lists.
|
||||
The ``args`` and ``keywords`` arguments are optional and default to empty lists.
|
||||
|
||||
.. doctest::
|
||||
|
||||
|
@ -729,7 +734,10 @@ Comprehensions
|
|||
|
||||
.. doctest::
|
||||
|
||||
>>> print(ast.dump(ast.parse('[x for x in numbers]', mode='eval'), indent=4))
|
||||
>>> print(ast.dump(
|
||||
... ast.parse('[x for x in numbers]', mode='eval'),
|
||||
... indent=4,
|
||||
... ))
|
||||
Expression(
|
||||
body=ListComp(
|
||||
elt=Name(id='x', ctx=Load()),
|
||||
|
@ -737,9 +745,11 @@ Comprehensions
|
|||
comprehension(
|
||||
target=Name(id='x', ctx=Store()),
|
||||
iter=Name(id='numbers', ctx=Load()),
|
||||
ifs=[],
|
||||
is_async=0)]))
|
||||
>>> print(ast.dump(ast.parse('{x: x**2 for x in numbers}', mode='eval'), indent=4))
|
||||
>>> print(ast.dump(
|
||||
... ast.parse('{x: x**2 for x in numbers}', mode='eval'),
|
||||
... indent=4,
|
||||
... ))
|
||||
Expression(
|
||||
body=DictComp(
|
||||
key=Name(id='x', ctx=Load()),
|
||||
|
@ -751,9 +761,11 @@ Comprehensions
|
|||
comprehension(
|
||||
target=Name(id='x', ctx=Store()),
|
||||
iter=Name(id='numbers', ctx=Load()),
|
||||
ifs=[],
|
||||
is_async=0)]))
|
||||
>>> print(ast.dump(ast.parse('{x for x in numbers}', mode='eval'), indent=4))
|
||||
>>> print(ast.dump(
|
||||
... ast.parse('{x for x in numbers}', mode='eval'),
|
||||
... indent=4,
|
||||
... ))
|
||||
Expression(
|
||||
body=SetComp(
|
||||
elt=Name(id='x', ctx=Load()),
|
||||
|
@ -761,7 +773,6 @@ Comprehensions
|
|||
comprehension(
|
||||
target=Name(id='x', ctx=Store()),
|
||||
iter=Name(id='numbers', ctx=Load()),
|
||||
ifs=[],
|
||||
is_async=0)]))
|
||||
|
||||
|
||||
|
@ -784,18 +795,15 @@ Comprehensions
|
|||
elt=Call(
|
||||
func=Name(id='ord', ctx=Load()),
|
||||
args=[
|
||||
Name(id='c', ctx=Load())],
|
||||
keywords=[]),
|
||||
Name(id='c', ctx=Load())]),
|
||||
generators=[
|
||||
comprehension(
|
||||
target=Name(id='line', ctx=Store()),
|
||||
iter=Name(id='file', ctx=Load()),
|
||||
ifs=[],
|
||||
is_async=0),
|
||||
comprehension(
|
||||
target=Name(id='c', ctx=Store()),
|
||||
iter=Name(id='line', ctx=Load()),
|
||||
ifs=[],
|
||||
is_async=0)]))
|
||||
|
||||
>>> print(ast.dump(ast.parse('(n**2 for n in it if n>5 if n<10)', mode='eval'),
|
||||
|
@ -834,7 +842,6 @@ Comprehensions
|
|||
comprehension(
|
||||
target=Name(id='i', ctx=Store()),
|
||||
iter=Name(id='soc', ctx=Load()),
|
||||
ifs=[],
|
||||
is_async=1)]))
|
||||
|
||||
|
||||
|
@ -864,8 +871,7 @@ Statements
|
|||
targets=[
|
||||
Name(id='a', ctx=Store()),
|
||||
Name(id='b', ctx=Store())],
|
||||
value=Constant(value=1))],
|
||||
type_ignores=[])
|
||||
value=Constant(value=1))])
|
||||
|
||||
>>> print(ast.dump(ast.parse('a,b = c'), indent=4)) # Unpacking
|
||||
Module(
|
||||
|
@ -877,8 +883,7 @@ Statements
|
|||
Name(id='a', ctx=Store()),
|
||||
Name(id='b', ctx=Store())],
|
||||
ctx=Store())],
|
||||
value=Name(id='c', ctx=Load()))],
|
||||
type_ignores=[])
|
||||
value=Name(id='c', ctx=Load()))])
|
||||
|
||||
|
||||
.. class:: AnnAssign(target, annotation, value, simple)
|
||||
|
@ -898,8 +903,7 @@ Statements
|
|||
AnnAssign(
|
||||
target=Name(id='c', ctx=Store()),
|
||||
annotation=Name(id='int', ctx=Load()),
|
||||
simple=1)],
|
||||
type_ignores=[])
|
||||
simple=1)])
|
||||
|
||||
>>> print(ast.dump(ast.parse('(a): int = 1'), indent=4)) # Annotation with parenthesis
|
||||
Module(
|
||||
|
@ -908,8 +912,7 @@ Statements
|
|||
target=Name(id='a', ctx=Store()),
|
||||
annotation=Name(id='int', ctx=Load()),
|
||||
value=Constant(value=1),
|
||||
simple=0)],
|
||||
type_ignores=[])
|
||||
simple=0)])
|
||||
|
||||
>>> print(ast.dump(ast.parse('a.b: int'), indent=4)) # Attribute annotation
|
||||
Module(
|
||||
|
@ -920,8 +923,7 @@ Statements
|
|||
attr='b',
|
||||
ctx=Store()),
|
||||
annotation=Name(id='int', ctx=Load()),
|
||||
simple=0)],
|
||||
type_ignores=[])
|
||||
simple=0)])
|
||||
|
||||
>>> print(ast.dump(ast.parse('a[1]: int'), indent=4)) # Subscript annotation
|
||||
Module(
|
||||
|
@ -932,8 +934,7 @@ Statements
|
|||
slice=Constant(value=1),
|
||||
ctx=Store()),
|
||||
annotation=Name(id='int', ctx=Load()),
|
||||
simple=0)],
|
||||
type_ignores=[])
|
||||
simple=0)])
|
||||
|
||||
|
||||
.. class:: AugAssign(target, op, value)
|
||||
|
@ -954,8 +955,7 @@ Statements
|
|||
AugAssign(
|
||||
target=Name(id='x', ctx=Store()),
|
||||
op=Add(),
|
||||
value=Constant(value=2))],
|
||||
type_ignores=[])
|
||||
value=Constant(value=2))])
|
||||
|
||||
|
||||
.. class:: Raise(exc, cause)
|
||||
|
@ -971,8 +971,7 @@ Statements
|
|||
body=[
|
||||
Raise(
|
||||
exc=Name(id='x', ctx=Load()),
|
||||
cause=Name(id='y', ctx=Load()))],
|
||||
type_ignores=[])
|
||||
cause=Name(id='y', ctx=Load()))])
|
||||
|
||||
|
||||
.. class:: Assert(test, msg)
|
||||
|
@ -987,8 +986,7 @@ Statements
|
|||
body=[
|
||||
Assert(
|
||||
test=Name(id='x', ctx=Load()),
|
||||
msg=Name(id='y', ctx=Load()))],
|
||||
type_ignores=[])
|
||||
msg=Name(id='y', ctx=Load()))])
|
||||
|
||||
|
||||
.. class:: Delete(targets)
|
||||
|
@ -1005,8 +1003,7 @@ Statements
|
|||
targets=[
|
||||
Name(id='x', ctx=Del()),
|
||||
Name(id='y', ctx=Del()),
|
||||
Name(id='z', ctx=Del())])],
|
||||
type_ignores=[])
|
||||
Name(id='z', ctx=Del())])])
|
||||
|
||||
|
||||
.. class:: Pass()
|
||||
|
@ -1018,8 +1015,7 @@ Statements
|
|||
>>> print(ast.dump(ast.parse('pass'), indent=4))
|
||||
Module(
|
||||
body=[
|
||||
Pass()],
|
||||
type_ignores=[])
|
||||
Pass()])
|
||||
|
||||
|
||||
.. class:: TypeAlias(name, type_params, value)
|
||||
|
@ -1036,9 +1032,7 @@ Statements
|
|||
body=[
|
||||
TypeAlias(
|
||||
name=Name(id='Alias', ctx=Store()),
|
||||
type_params=[],
|
||||
value=Name(id='int', ctx=Load()))],
|
||||
type_ignores=[])
|
||||
value=Name(id='int', ctx=Load()))])
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
|
@ -1061,8 +1055,7 @@ Imports
|
|||
names=[
|
||||
alias(name='x'),
|
||||
alias(name='y'),
|
||||
alias(name='z')])],
|
||||
type_ignores=[])
|
||||
alias(name='z')])])
|
||||
|
||||
|
||||
.. class:: ImportFrom(module, names, level)
|
||||
|
@ -1083,8 +1076,7 @@ Imports
|
|||
alias(name='x'),
|
||||
alias(name='y'),
|
||||
alias(name='z')],
|
||||
level=0)],
|
||||
type_ignores=[])
|
||||
level=0)])
|
||||
|
||||
|
||||
.. class:: alias(name, asname)
|
||||
|
@ -1102,8 +1094,7 @@ Imports
|
|||
names=[
|
||||
alias(name='a', asname='b'),
|
||||
alias(name='c')],
|
||||
level=2)],
|
||||
type_ignores=[])
|
||||
level=2)])
|
||||
|
||||
Control flow
|
||||
^^^^^^^^^^^^
|
||||
|
@ -1146,8 +1137,7 @@ Control flow
|
|||
value=Constant(value=Ellipsis))],
|
||||
orelse=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])])
|
||||
|
||||
|
||||
.. class:: For(target, iter, body, orelse, type_comment)
|
||||
|
@ -1181,8 +1171,7 @@ Control flow
|
|||
value=Constant(value=Ellipsis))],
|
||||
orelse=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])
|
||||
|
||||
|
||||
.. class:: While(test, body, orelse)
|
||||
|
@ -1207,8 +1196,7 @@ Control flow
|
|||
value=Constant(value=Ellipsis))],
|
||||
orelse=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])
|
||||
|
||||
|
||||
.. class:: Break
|
||||
|
@ -1242,9 +1230,7 @@ Control flow
|
|||
body=[
|
||||
Break()],
|
||||
orelse=[
|
||||
Continue()])],
|
||||
orelse=[])],
|
||||
type_ignores=[])
|
||||
Continue()])])])
|
||||
|
||||
|
||||
.. class:: Try(body, handlers, orelse, finalbody)
|
||||
|
@ -1289,8 +1275,7 @@ Control flow
|
|||
value=Constant(value=Ellipsis))],
|
||||
finalbody=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])
|
||||
|
||||
|
||||
.. class:: TryStar(body, handlers, orelse, finalbody)
|
||||
|
@ -1318,10 +1303,7 @@ Control flow
|
|||
type=Name(id='Exception', ctx=Load()),
|
||||
body=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])],
|
||||
orelse=[],
|
||||
finalbody=[])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])])
|
||||
|
||||
.. versionadded:: 3.11
|
||||
|
||||
|
@ -1353,10 +1335,7 @@ Control flow
|
|||
ExceptHandler(
|
||||
type=Name(id='TypeError', ctx=Load()),
|
||||
body=[
|
||||
Pass()])],
|
||||
orelse=[],
|
||||
finalbody=[])],
|
||||
type_ignores=[])
|
||||
Pass()])])])
|
||||
|
||||
|
||||
.. class:: With(items, body, type_comment)
|
||||
|
@ -1398,9 +1377,7 @@ Control flow
|
|||
func=Name(id='something', ctx=Load()),
|
||||
args=[
|
||||
Name(id='b', ctx=Load()),
|
||||
Name(id='d', ctx=Load())],
|
||||
keywords=[]))])],
|
||||
type_ignores=[])
|
||||
Name(id='d', ctx=Load())]))])])
|
||||
|
||||
|
||||
Pattern matching
|
||||
|
@ -1457,14 +1434,10 @@ Pattern matching
|
|||
value=Constant(value=Ellipsis))]),
|
||||
match_case(
|
||||
pattern=MatchClass(
|
||||
cls=Name(id='tuple', ctx=Load()),
|
||||
patterns=[],
|
||||
kwd_attrs=[],
|
||||
kwd_patterns=[]),
|
||||
cls=Name(id='tuple', ctx=Load())),
|
||||
body=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])])
|
||||
|
||||
.. versionadded:: 3.10
|
||||
|
||||
|
@ -1492,8 +1465,7 @@ Pattern matching
|
|||
value=Constant(value='Relevant')),
|
||||
body=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])])
|
||||
|
||||
.. versionadded:: 3.10
|
||||
|
||||
|
@ -1519,8 +1491,7 @@ Pattern matching
|
|||
pattern=MatchSingleton(value=None),
|
||||
body=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])])
|
||||
|
||||
.. versionadded:: 3.10
|
||||
|
||||
|
@ -1552,8 +1523,7 @@ Pattern matching
|
|||
value=Constant(value=2))]),
|
||||
body=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])])
|
||||
|
||||
.. versionadded:: 3.10
|
||||
|
||||
|
@ -1594,8 +1564,7 @@ Pattern matching
|
|||
MatchStar()]),
|
||||
body=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])])
|
||||
|
||||
.. versionadded:: 3.10
|
||||
|
||||
|
@ -1639,11 +1608,10 @@ Pattern matching
|
|||
Expr(
|
||||
value=Constant(value=Ellipsis))]),
|
||||
match_case(
|
||||
pattern=MatchMapping(keys=[], patterns=[], rest='rest'),
|
||||
pattern=MatchMapping(rest='rest'),
|
||||
body=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])])
|
||||
|
||||
.. versionadded:: 3.10
|
||||
|
||||
|
@ -1685,16 +1653,13 @@ Pattern matching
|
|||
MatchValue(
|
||||
value=Constant(value=0)),
|
||||
MatchValue(
|
||||
value=Constant(value=0))],
|
||||
kwd_attrs=[],
|
||||
kwd_patterns=[]),
|
||||
value=Constant(value=0))]),
|
||||
body=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))]),
|
||||
match_case(
|
||||
pattern=MatchClass(
|
||||
cls=Name(id='Point3D', ctx=Load()),
|
||||
patterns=[],
|
||||
kwd_attrs=[
|
||||
'x',
|
||||
'y',
|
||||
|
@ -1708,8 +1673,7 @@ Pattern matching
|
|||
value=Constant(value=0))]),
|
||||
body=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])])
|
||||
|
||||
.. versionadded:: 3.10
|
||||
|
||||
|
@ -1751,8 +1715,7 @@ Pattern matching
|
|||
pattern=MatchAs(),
|
||||
body=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])])
|
||||
|
||||
.. versionadded:: 3.10
|
||||
|
||||
|
@ -1785,8 +1748,7 @@ Pattern matching
|
|||
MatchAs(name='y')]),
|
||||
body=[
|
||||
Expr(
|
||||
value=Constant(value=Ellipsis))])])],
|
||||
type_ignores=[])
|
||||
value=Constant(value=Ellipsis))])])])
|
||||
|
||||
.. versionadded:: 3.10
|
||||
|
||||
|
@ -1798,15 +1760,17 @@ Type parameters
|
|||
:ref:`Type parameters <type-params>` can exist on classes, functions, and type
|
||||
aliases.
|
||||
|
||||
.. class:: TypeVar(name, bound)
|
||||
.. class:: TypeVar(name, bound, default_value)
|
||||
|
||||
A :class:`typing.TypeVar`. ``name`` is the name of the type variable.
|
||||
``bound`` is the bound or constraints, if any. If ``bound`` is a :class:`Tuple`,
|
||||
it represents constraints; otherwise it represents the bound.
|
||||
A :class:`typing.TypeVar`. *name* is the name of the type variable.
|
||||
*bound* is the bound or constraints, if any. If *bound* is a :class:`Tuple`,
|
||||
it represents constraints; otherwise it represents the bound. *default_value*
|
||||
is the default value; if the :class:`!TypeVar` has no default, this
|
||||
attribute will be set to ``None``.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> print(ast.dump(ast.parse("type Alias[T: int] = list[T]"), indent=4))
|
||||
>>> print(ast.dump(ast.parse("type Alias[T: int = bool] = list[T]"), indent=4))
|
||||
Module(
|
||||
body=[
|
||||
TypeAlias(
|
||||
|
@ -1814,28 +1778,39 @@ aliases.
|
|||
type_params=[
|
||||
TypeVar(
|
||||
name='T',
|
||||
bound=Name(id='int', ctx=Load()))],
|
||||
bound=Name(id='int', ctx=Load()),
|
||||
default_value=Name(id='bool', ctx=Load()))],
|
||||
value=Subscript(
|
||||
value=Name(id='list', ctx=Load()),
|
||||
slice=Name(id='T', ctx=Load()),
|
||||
ctx=Load()))],
|
||||
type_ignores=[])
|
||||
ctx=Load()))])
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
.. class:: ParamSpec(name)
|
||||
.. versionchanged:: 3.13
|
||||
Added the *default_value* parameter.
|
||||
|
||||
A :class:`typing.ParamSpec`. ``name`` is the name of the parameter specification.
|
||||
.. class:: ParamSpec(name, default_value)
|
||||
|
||||
A :class:`typing.ParamSpec`. *name* is the name of the parameter specification.
|
||||
*default_value* is the default value; if the :class:`!ParamSpec` has no default,
|
||||
this attribute will be set to ``None``.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> print(ast.dump(ast.parse("type Alias[**P] = Callable[P, int]"), indent=4))
|
||||
>>> print(ast.dump(ast.parse("type Alias[**P = (int, str)] = Callable[P, int]"), indent=4))
|
||||
Module(
|
||||
body=[
|
||||
TypeAlias(
|
||||
name=Name(id='Alias', ctx=Store()),
|
||||
type_params=[
|
||||
ParamSpec(name='P')],
|
||||
ParamSpec(
|
||||
name='P',
|
||||
default_value=Tuple(
|
||||
elts=[
|
||||
Name(id='int', ctx=Load()),
|
||||
Name(id='str', ctx=Load())],
|
||||
ctx=Load()))],
|
||||
value=Subscript(
|
||||
value=Name(id='Callable', ctx=Load()),
|
||||
slice=Tuple(
|
||||
|
@ -1843,24 +1818,30 @@ aliases.
|
|||
Name(id='P', ctx=Load()),
|
||||
Name(id='int', ctx=Load())],
|
||||
ctx=Load()),
|
||||
ctx=Load()))],
|
||||
type_ignores=[])
|
||||
ctx=Load()))])
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
.. class:: TypeVarTuple(name)
|
||||
.. versionchanged:: 3.13
|
||||
Added the *default_value* parameter.
|
||||
|
||||
A :class:`typing.TypeVarTuple`. ``name`` is the name of the type variable tuple.
|
||||
.. class:: TypeVarTuple(name, default_value)
|
||||
|
||||
A :class:`typing.TypeVarTuple`. *name* is the name of the type variable tuple.
|
||||
*default_value* is the default value; if the :class:`!TypeVarTuple` has no
|
||||
default, this attribute will be set to ``None``.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> print(ast.dump(ast.parse("type Alias[*Ts] = tuple[*Ts]"), indent=4))
|
||||
>>> print(ast.dump(ast.parse("type Alias[*Ts = ()] = tuple[*Ts]"), indent=4))
|
||||
Module(
|
||||
body=[
|
||||
TypeAlias(
|
||||
name=Name(id='Alias', ctx=Store()),
|
||||
type_params=[
|
||||
TypeVarTuple(name='Ts')],
|
||||
TypeVarTuple(
|
||||
name='Ts',
|
||||
default_value=Tuple(ctx=Load()))],
|
||||
value=Subscript(
|
||||
value=Name(id='tuple', ctx=Load()),
|
||||
slice=Tuple(
|
||||
|
@ -1869,11 +1850,13 @@ aliases.
|
|||
value=Name(id='Ts', ctx=Load()),
|
||||
ctx=Load())],
|
||||
ctx=Load()),
|
||||
ctx=Load()))],
|
||||
type_ignores=[])
|
||||
ctx=Load()))])
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
Added the *default_value* parameter.
|
||||
|
||||
Function and class definitions
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -1910,15 +1893,10 @@ Function and class definitions
|
|||
Expr(
|
||||
value=Lambda(
|
||||
args=arguments(
|
||||
posonlyargs=[],
|
||||
args=[
|
||||
arg(arg='x'),
|
||||
arg(arg='y')],
|
||||
kwonlyargs=[],
|
||||
kw_defaults=[],
|
||||
defaults=[]),
|
||||
body=Constant(value=Ellipsis)))],
|
||||
type_ignores=[])
|
||||
arg(arg='y')]),
|
||||
body=Constant(value=Ellipsis)))])
|
||||
|
||||
|
||||
.. class:: arguments(posonlyargs, args, vararg, kwonlyargs, kw_defaults, kwarg, defaults)
|
||||
|
@ -1957,7 +1935,6 @@ Function and class definitions
|
|||
FunctionDef(
|
||||
name='f',
|
||||
args=arguments(
|
||||
posonlyargs=[],
|
||||
args=[
|
||||
arg(
|
||||
arg='a',
|
||||
|
@ -1980,9 +1957,7 @@ Function and class definitions
|
|||
decorator_list=[
|
||||
Name(id='decorator1', ctx=Load()),
|
||||
Name(id='decorator2', ctx=Load())],
|
||||
returns=Constant(value='return annotation'),
|
||||
type_params=[])],
|
||||
type_ignores=[])
|
||||
returns=Constant(value='return annotation'))])
|
||||
|
||||
|
||||
.. class:: Return(value)
|
||||
|
@ -1995,8 +1970,7 @@ Function and class definitions
|
|||
Module(
|
||||
body=[
|
||||
Return(
|
||||
value=Constant(value=4))],
|
||||
type_ignores=[])
|
||||
value=Constant(value=4))])
|
||||
|
||||
|
||||
.. class:: Yield(value)
|
||||
|
@ -2012,16 +1986,14 @@ Function and class definitions
|
|||
body=[
|
||||
Expr(
|
||||
value=Yield(
|
||||
value=Name(id='x', ctx=Load())))],
|
||||
type_ignores=[])
|
||||
value=Name(id='x', ctx=Load())))])
|
||||
|
||||
>>> print(ast.dump(ast.parse('yield from x'), indent=4))
|
||||
Module(
|
||||
body=[
|
||||
Expr(
|
||||
value=YieldFrom(
|
||||
value=Name(id='x', ctx=Load())))],
|
||||
type_ignores=[])
|
||||
value=Name(id='x', ctx=Load())))])
|
||||
|
||||
|
||||
.. class:: Global(names)
|
||||
|
@ -2038,8 +2010,7 @@ Function and class definitions
|
|||
names=[
|
||||
'x',
|
||||
'y',
|
||||
'z'])],
|
||||
type_ignores=[])
|
||||
'z'])])
|
||||
|
||||
>>> print(ast.dump(ast.parse('nonlocal x,y,z'), indent=4))
|
||||
Module(
|
||||
|
@ -2048,8 +2019,7 @@ Function and class definitions
|
|||
names=[
|
||||
'x',
|
||||
'y',
|
||||
'z'])],
|
||||
type_ignores=[])
|
||||
'z'])])
|
||||
|
||||
|
||||
.. class:: ClassDef(name, bases, keywords, body, decorator_list, type_params)
|
||||
|
@ -2089,9 +2059,7 @@ Function and class definitions
|
|||
Pass()],
|
||||
decorator_list=[
|
||||
Name(id='decorator1', ctx=Load()),
|
||||
Name(id='decorator2', ctx=Load())],
|
||||
type_params=[])],
|
||||
type_ignores=[])
|
||||
Name(id='decorator2', ctx=Load())])])
|
||||
|
||||
.. versionchanged:: 3.12
|
||||
Added ``type_params``.
|
||||
|
@ -2123,22 +2091,12 @@ Async and await
|
|||
body=[
|
||||
AsyncFunctionDef(
|
||||
name='f',
|
||||
args=arguments(
|
||||
posonlyargs=[],
|
||||
args=[],
|
||||
kwonlyargs=[],
|
||||
kw_defaults=[],
|
||||
defaults=[]),
|
||||
args=arguments(),
|
||||
body=[
|
||||
Expr(
|
||||
value=Await(
|
||||
value=Call(
|
||||
func=Name(id='other_func', ctx=Load()),
|
||||
args=[],
|
||||
keywords=[])))],
|
||||
decorator_list=[],
|
||||
type_params=[])],
|
||||
type_ignores=[])
|
||||
func=Name(id='other_func', ctx=Load()))))])])
|
||||
|
||||
|
||||
.. class:: AsyncFor(target, iter, body, orelse, type_comment)
|
||||
|
@ -2425,7 +2383,7 @@ and classes for traversing abstract syntax trees:
|
|||
node = YourTransformer().visit(node)
|
||||
|
||||
|
||||
.. function:: dump(node, annotate_fields=True, include_attributes=False, *, indent=None)
|
||||
.. function:: dump(node, annotate_fields=True, include_attributes=False, *, indent=None, show_empty=False)
|
||||
|
||||
Return a formatted dump of the tree in *node*. This is mainly useful for
|
||||
debugging purposes. If *annotate_fields* is true (by default),
|
||||
|
@ -2442,9 +2400,42 @@ and classes for traversing abstract syntax trees:
|
|||
indents that many spaces per level. If *indent* is a string (such as ``"\t"``),
|
||||
that string is used to indent each level.
|
||||
|
||||
If *show_empty* is ``False`` (the default), empty lists and fields that are ``None``
|
||||
will be omitted from the output.
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
Added the *indent* option.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
Added the *show_empty* option.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> print(ast.dump(ast.parse("""\
|
||||
... async def f():
|
||||
... await other_func()
|
||||
... """), indent=4, show_empty=True))
|
||||
Module(
|
||||
body=[
|
||||
AsyncFunctionDef(
|
||||
name='f',
|
||||
args=arguments(
|
||||
posonlyargs=[],
|
||||
args=[],
|
||||
kwonlyargs=[],
|
||||
kw_defaults=[],
|
||||
defaults=[]),
|
||||
body=[
|
||||
Expr(
|
||||
value=Await(
|
||||
value=Call(
|
||||
func=Name(id='other_func', ctx=Load()),
|
||||
args=[],
|
||||
keywords=[])))],
|
||||
decorator_list=[],
|
||||
type_params=[])],
|
||||
type_ignores=[])
|
||||
|
||||
|
||||
.. _ast-compiler-flags:
|
||||
|
||||
|
@ -2536,7 +2527,8 @@ to stdout. Otherwise, the content is read from stdin.
|
|||
code that generated them. This is helpful for tools that make source code
|
||||
transformations.
|
||||
|
||||
`leoAst.py <https://leoeditor.com/appendices.html#leoast-py>`_ unifies the
|
||||
`leoAst.py <https://leo-editor.github.io/leo-editor/appendices.html#leoast-py>`_
|
||||
unifies the
|
||||
token-based and parse-tree-based views of python programs by inserting
|
||||
two-way links between tokens and ast nodes.
|
||||
|
||||
|
@ -2548,4 +2540,4 @@ to stdout. Otherwise, the content is read from stdin.
|
|||
`Parso <https://parso.readthedocs.io>`_ is a Python parser that supports
|
||||
error recovery and round-trip parsing for different Python versions (in
|
||||
multiple Python versions). Parso is also able to list multiple syntax errors
|
||||
in your python file.
|
||||
in your Python file.
|
||||
|
|
|
@ -796,7 +796,7 @@ Creating network servers
|
|||
:class:`str`, :class:`bytes`, and :class:`~pathlib.Path` paths
|
||||
are supported.
|
||||
|
||||
If *cleanup_socket* is True then the Unix socket will automatically
|
||||
If *cleanup_socket* is true then the Unix socket will automatically
|
||||
be removed from the filesystem when the server is closed, unless the
|
||||
socket has been replaced after the server has been created.
|
||||
|
||||
|
|
|
@ -62,6 +62,9 @@ Queue
|
|||
Remove and return an item from the queue. If queue is empty,
|
||||
wait until an item is available.
|
||||
|
||||
Raises :exc:`QueueShutDown` if the queue has been shut down and
|
||||
is empty, or if the queue has been shut down immediately.
|
||||
|
||||
.. method:: get_nowait()
|
||||
|
||||
Return an item if one is immediately available, else raise
|
||||
|
@ -82,6 +85,8 @@ Queue
|
|||
Put an item into the queue. If the queue is full, wait until a
|
||||
free slot is available before adding the item.
|
||||
|
||||
Raises :exc:`QueueShutDown` if the queue has been shut down.
|
||||
|
||||
.. method:: put_nowait(item)
|
||||
|
||||
Put an item into the queue without blocking.
|
||||
|
@ -92,6 +97,22 @@ Queue
|
|||
|
||||
Return the number of items in the queue.
|
||||
|
||||
.. method:: shutdown(immediate=False)
|
||||
|
||||
Shut down the queue, making :meth:`~Queue.get` and :meth:`~Queue.put`
|
||||
raise :exc:`QueueShutDown`.
|
||||
|
||||
By default, :meth:`~Queue.get` on a shut down queue will only
|
||||
raise once the queue is empty. Set *immediate* to true to make
|
||||
:meth:`~Queue.get` raise immediately instead.
|
||||
|
||||
All blocked callers of :meth:`~Queue.put` and :meth:`~Queue.get`
|
||||
will be unblocked. If *immediate* is true, a task will be marked
|
||||
as done for each remaining item in the queue, which may unblock
|
||||
callers of :meth:`~Queue.join`.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
.. method:: task_done()
|
||||
|
||||
Indicate that a formerly enqueued task is complete.
|
||||
|
@ -105,6 +126,9 @@ Queue
|
|||
call was received for every item that had been :meth:`~Queue.put`
|
||||
into the queue).
|
||||
|
||||
``shutdown(immediate=True)`` calls :meth:`task_done` for each
|
||||
remaining item in the queue.
|
||||
|
||||
Raises :exc:`ValueError` if called more times than there were
|
||||
items placed in the queue.
|
||||
|
||||
|
@ -145,6 +169,14 @@ Exceptions
|
|||
on a queue that has reached its *maxsize*.
|
||||
|
||||
|
||||
.. exception:: QueueShutDown
|
||||
|
||||
Exception raised when :meth:`~Queue.put` or :meth:`~Queue.get` is
|
||||
called on a queue which has been shut down.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
|
|
|
@ -260,8 +260,19 @@ StreamReader
|
|||
buffer is reset. The :attr:`IncompleteReadError.partial` attribute
|
||||
may contain a portion of the separator.
|
||||
|
||||
The *separator* may also be a tuple of separators. In this
|
||||
case the return value will be the shortest possible that has any
|
||||
separator as the suffix. For the purposes of :exc:`LimitOverrunError`,
|
||||
the shortest possible separator is considered to be the one that
|
||||
matched.
|
||||
|
||||
.. versionadded:: 3.5.2
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
|
||||
The *separator* parameter may now be a :class:`tuple` of
|
||||
separators.
|
||||
|
||||
.. method:: at_eof()
|
||||
|
||||
Return ``True`` if the buffer is empty and :meth:`feed_eof`
|
||||
|
|
|
@ -392,6 +392,27 @@ is also included in the exception group.
|
|||
The same special case is made for
|
||||
:exc:`KeyboardInterrupt` and :exc:`SystemExit` as in the previous paragraph.
|
||||
|
||||
Task groups are careful not to mix up the internal cancellation used to
|
||||
"wake up" their :meth:`~object.__aexit__` with cancellation requests
|
||||
for the task in which they are running made by other parties.
|
||||
In particular, when one task group is syntactically nested in another,
|
||||
and both experience an exception in one of their child tasks simultaneously,
|
||||
the inner task group will process its exceptions, and then the outer task group
|
||||
will receive another cancellation and process its own exceptions.
|
||||
|
||||
In the case where a task group is cancelled externally and also must
|
||||
raise an :exc:`ExceptionGroup`, it will call the parent task's
|
||||
:meth:`~asyncio.Task.cancel` method. This ensures that a
|
||||
:exc:`asyncio.CancelledError` will be raised at the next
|
||||
:keyword:`await`, so the cancellation is not lost.
|
||||
|
||||
Task groups preserve the cancellation count
|
||||
reported by :meth:`asyncio.Task.cancelling`.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
|
||||
Improved handling of simultaneous internal and external cancellations
|
||||
and correct preservation of cancellation counts.
|
||||
|
||||
Sleeping
|
||||
========
|
||||
|
@ -517,7 +538,7 @@ Running Tasks Concurrently
|
|||
# [2, 6, 24]
|
||||
|
||||
.. note::
|
||||
If *return_exceptions* is False, cancelling gather() after it
|
||||
If *return_exceptions* is false, cancelling gather() after it
|
||||
has been marked done won't cancel any submitted awaitables.
|
||||
For instance, gather can be marked done after propagating an
|
||||
exception to the caller, therefore, calling ``gather.cancel()``
|
||||
|
@ -867,19 +888,50 @@ Waiting Primitives
|
|||
|
||||
.. function:: as_completed(aws, *, timeout=None)
|
||||
|
||||
Run :ref:`awaitable objects <asyncio-awaitables>` in the *aws*
|
||||
iterable concurrently. Return an iterator of coroutines.
|
||||
Each coroutine returned can be awaited to get the earliest next
|
||||
result from the iterable of the remaining awaitables.
|
||||
Run :ref:`awaitable objects <asyncio-awaitables>` in the *aws* iterable
|
||||
concurrently. The returned object can be iterated to obtain the results
|
||||
of the awaitables as they finish.
|
||||
|
||||
Raises :exc:`TimeoutError` if the timeout occurs before
|
||||
all Futures are done.
|
||||
The object returned by ``as_completed()`` can be iterated as an
|
||||
:term:`asynchronous iterator` or a plain :term:`iterator`. When asynchronous
|
||||
iteration is used, the originally-supplied awaitables are yielded if they
|
||||
are tasks or futures. This makes it easy to correlate previously-scheduled
|
||||
tasks with their results. Example::
|
||||
|
||||
Example::
|
||||
ipv4_connect = create_task(open_connection("127.0.0.1", 80))
|
||||
ipv6_connect = create_task(open_connection("::1", 80))
|
||||
tasks = [ipv4_connect, ipv6_connect]
|
||||
|
||||
for coro in as_completed(aws):
|
||||
earliest_result = await coro
|
||||
# ...
|
||||
async for earliest_connect in as_completed(tasks):
|
||||
# earliest_connect is done. The result can be obtained by
|
||||
# awaiting it or calling earliest_connect.result()
|
||||
reader, writer = await earliest_connect
|
||||
|
||||
if earliest_connect is ipv6_connect:
|
||||
print("IPv6 connection established.")
|
||||
else:
|
||||
print("IPv4 connection established.")
|
||||
|
||||
During asynchronous iteration, implicitly-created tasks will be yielded for
|
||||
supplied awaitables that aren't tasks or futures.
|
||||
|
||||
When used as a plain iterator, each iteration yields a new coroutine that
|
||||
returns the result or raises the exception of the next completed awaitable.
|
||||
This pattern is compatible with Python versions older than 3.13::
|
||||
|
||||
ipv4_connect = create_task(open_connection("127.0.0.1", 80))
|
||||
ipv6_connect = create_task(open_connection("::1", 80))
|
||||
tasks = [ipv4_connect, ipv6_connect]
|
||||
|
||||
for next_connect in as_completed(tasks):
|
||||
# next_connect is not one of the original task objects. It must be
|
||||
# awaited to obtain the result value or raise the exception of the
|
||||
# awaitable that finishes next.
|
||||
reader, writer = await next_connect
|
||||
|
||||
A :exc:`TimeoutError` is raised if the timeout occurs before all awaitables
|
||||
are done. This is raised by the ``async for`` loop during asynchronous
|
||||
iteration or by the coroutines yielded during plain iteration.
|
||||
|
||||
.. versionchanged:: 3.10
|
||||
Removed the *loop* parameter.
|
||||
|
@ -891,6 +943,10 @@ Waiting Primitives
|
|||
.. versionchanged:: 3.12
|
||||
Added support for generators yielding tasks.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
The result can now be used as either an :term:`asynchronous iterator`
|
||||
or as a plain :term:`iterator` (previously it was only a plain iterator).
|
||||
|
||||
|
||||
Running in Threads
|
||||
==================
|
||||
|
@ -1330,10 +1386,19 @@ Task Object
|
|||
with :meth:`uncancel`. :class:`TaskGroup` context managers use
|
||||
:func:`uncancel` in a similar fashion.
|
||||
|
||||
If end-user code is, for some reason, suppresing cancellation by
|
||||
If end-user code is, for some reason, suppressing cancellation by
|
||||
catching :exc:`CancelledError`, it needs to call this method to remove
|
||||
the cancellation state.
|
||||
|
||||
When this method decrements the cancellation count to zero,
|
||||
the method checks if a previous :meth:`cancel` call had arranged
|
||||
for :exc:`CancelledError` to be thrown into the task.
|
||||
If it hasn't been thrown yet, that arrangement will be
|
||||
rescinded (by resetting the internal ``_must_cancel`` flag).
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
Changed to rescind pending cancellation requests upon reaching zero.
|
||||
|
||||
.. method:: cancelling()
|
||||
|
||||
Return the number of pending cancellation requests to this Task, i.e.,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`asyncio` --- Asynchronous I/O
|
||||
===================================
|
||||
:mod:`!asyncio` --- Asynchronous I/O
|
||||
====================================
|
||||
|
||||
.. module:: asyncio
|
||||
:synopsis: Asynchronous I/O.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`atexit` --- Exit handlers
|
||||
===============================
|
||||
:mod:`!atexit` --- Exit handlers
|
||||
================================
|
||||
|
||||
.. module:: atexit
|
||||
:synopsis: Register and execute cleanup functions.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`base64` --- Base16, Base32, Base64, Base85 Data Encodings
|
||||
===============================================================
|
||||
:mod:`!base64` --- Base16, Base32, Base64, Base85 Data Encodings
|
||||
================================================================
|
||||
|
||||
.. module:: base64
|
||||
:synopsis: RFC 4648: Base16, Base32, Base64 Data Encodings;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`bdb` --- Debugger framework
|
||||
=================================
|
||||
:mod:`!bdb` --- Debugger framework
|
||||
==================================
|
||||
|
||||
.. module:: bdb
|
||||
:synopsis: Debugger framework.
|
||||
|
@ -86,7 +86,7 @@ The :mod:`bdb` module also defines two classes:
|
|||
|
||||
.. attribute:: temporary
|
||||
|
||||
True if a :class:`Breakpoint` at (file, line) is temporary.
|
||||
``True`` if a :class:`Breakpoint` at (file, line) is temporary.
|
||||
|
||||
.. attribute:: cond
|
||||
|
||||
|
@ -99,7 +99,7 @@ The :mod:`bdb` module also defines two classes:
|
|||
|
||||
.. attribute:: enabled
|
||||
|
||||
True if :class:`Breakpoint` is enabled.
|
||||
``True`` if :class:`Breakpoint` is enabled.
|
||||
|
||||
.. attribute:: bpbynumber
|
||||
|
||||
|
@ -215,22 +215,22 @@ The :mod:`bdb` module also defines two classes:
|
|||
|
||||
.. method:: is_skipped_line(module_name)
|
||||
|
||||
Return True if *module_name* matches any skip pattern.
|
||||
Return ``True`` if *module_name* matches any skip pattern.
|
||||
|
||||
.. method:: stop_here(frame)
|
||||
|
||||
Return True if *frame* is below the starting frame in the stack.
|
||||
Return ``True`` if *frame* is below the starting frame in the stack.
|
||||
|
||||
.. method:: break_here(frame)
|
||||
|
||||
Return True if there is an effective breakpoint for this line.
|
||||
Return ``True`` if there is an effective breakpoint for this line.
|
||||
|
||||
Check whether a line or function breakpoint exists and is in effect. Delete temporary
|
||||
breakpoints based on information from :func:`effective`.
|
||||
|
||||
.. method:: break_anywhere(frame)
|
||||
|
||||
Return True if any breakpoint exists for *frame*'s filename.
|
||||
Return ``True`` if any breakpoint exists for *frame*'s filename.
|
||||
|
||||
Derived classes should override these methods to gain control over debugger
|
||||
operation.
|
||||
|
@ -240,6 +240,9 @@ The :mod:`bdb` module also defines two classes:
|
|||
Called from :meth:`dispatch_call` if a break might stop inside the
|
||||
called function.
|
||||
|
||||
*argument_list* is not used anymore and will always be ``None``.
|
||||
The argument is kept for backwards compatibility.
|
||||
|
||||
.. method:: user_line(frame)
|
||||
|
||||
Called from :meth:`dispatch_line` when either :meth:`stop_here` or
|
||||
|
@ -286,6 +289,10 @@ The :mod:`bdb` module also defines two classes:
|
|||
Start debugging from *frame*. If *frame* is not specified, debugging
|
||||
starts from caller's frame.
|
||||
|
||||
.. versionchanged:: 3.13
|
||||
:func:`set_trace` will enter the debugger immediately, rather than
|
||||
on the next line of code to be executed.
|
||||
|
||||
.. method:: set_continue()
|
||||
|
||||
Stop only at breakpoints or when finished. If there are no breakpoints,
|
||||
|
@ -341,7 +348,7 @@ The :mod:`bdb` module also defines two classes:
|
|||
|
||||
.. method:: get_break(filename, lineno)
|
||||
|
||||
Return True if there is a breakpoint for *lineno* in *filename*.
|
||||
Return ``True`` if there is a breakpoint for *lineno* in *filename*.
|
||||
|
||||
.. method:: get_breaks(filename, lineno)
|
||||
|
||||
|
@ -405,7 +412,7 @@ Finally, the module defines the following functions:
|
|||
|
||||
.. function:: checkfuncname(b, frame)
|
||||
|
||||
Return True if we should break here, depending on the way the
|
||||
Return ``True`` if we should break here, depending on the way the
|
||||
:class:`Breakpoint` *b* was set.
|
||||
|
||||
If it was set via line number, it checks if
|
||||
|
@ -424,14 +431,14 @@ Finally, the module defines the following functions:
|
|||
:attr:`bplist <bdb.Breakpoint.bplist>` for the
|
||||
(:attr:`file <bdb.Breakpoint.file>`, :attr:`line <bdb.Breakpoint.line>`)
|
||||
(which must exist) that is :attr:`enabled <bdb.Breakpoint.enabled>`, for
|
||||
which :func:`checkfuncname` is True, and that has neither a False
|
||||
which :func:`checkfuncname` is true, and that has neither a false
|
||||
:attr:`condition <bdb.Breakpoint.cond>` nor positive
|
||||
:attr:`ignore <bdb.Breakpoint.ignore>` count. The *flag*, meaning that a
|
||||
temporary breakpoint should be deleted, is False only when the
|
||||
temporary breakpoint should be deleted, is ``False`` only when the
|
||||
:attr:`cond <bdb.Breakpoint.cond>` cannot be evaluated (in which case,
|
||||
:attr:`ignore <bdb.Breakpoint.ignore>` count is ignored).
|
||||
|
||||
If no such entry exists, then (None, None) is returned.
|
||||
If no such entry exists, then ``(None, None)`` is returned.
|
||||
|
||||
|
||||
.. function:: set_trace()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`binascii` --- Convert between binary and ASCII
|
||||
====================================================
|
||||
:mod:`!binascii` --- Convert between binary and ASCII
|
||||
=====================================================
|
||||
|
||||
.. module:: binascii
|
||||
:synopsis: Tools for converting between binary and various ASCII-encoded binary
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`bisect` --- Array bisection algorithm
|
||||
===========================================
|
||||
:mod:`!bisect` --- Array bisection algorithm
|
||||
============================================
|
||||
|
||||
.. module:: bisect
|
||||
:synopsis: Array bisection algorithms for binary searching.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`builtins` --- Built-in objects
|
||||
====================================
|
||||
:mod:`!builtins` --- Built-in objects
|
||||
=====================================
|
||||
|
||||
.. module:: builtins
|
||||
:synopsis: The module that provides the built-in namespace.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`bz2` --- Support for :program:`bzip2` compression
|
||||
=======================================================
|
||||
:mod:`!bz2` --- Support for :program:`bzip2` compression
|
||||
========================================================
|
||||
|
||||
.. module:: bz2
|
||||
:synopsis: Interfaces for bzip2 compression and decompression.
|
||||
|
@ -91,7 +91,7 @@ The :mod:`bz2` module contains:
|
|||
and :meth:`~io.IOBase.truncate`.
|
||||
Iteration and the :keyword:`with` statement are supported.
|
||||
|
||||
:class:`BZ2File` also provides the following methods:
|
||||
:class:`BZ2File` also provides the following methods and attributes:
|
||||
|
||||
.. method:: peek([n])
|
||||
|
||||
|
@ -148,6 +148,19 @@ The :mod:`bz2` module contains:
|
|||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
.. attribute:: mode
|
||||
|
||||
``'rb'`` for reading and ``'wb'`` for writing.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
.. attribute:: name
|
||||
|
||||
The bzip2 file name. Equivalent to the :attr:`~io.FileIO.name`
|
||||
attribute of the underlying :term:`file object`.
|
||||
|
||||
.. versionadded:: 3.13
|
||||
|
||||
|
||||
.. versionchanged:: 3.1
|
||||
Support for the :keyword:`with` statement was added.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`calendar` --- General calendar-related functions
|
||||
======================================================
|
||||
:mod:`!calendar` --- General calendar-related functions
|
||||
=======================================================
|
||||
|
||||
.. module:: calendar
|
||||
:synopsis: Functions for working with calendars, including some emulation
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`cmath` --- Mathematical functions for complex numbers
|
||||
===========================================================
|
||||
:mod:`!cmath` --- Mathematical functions for complex numbers
|
||||
============================================================
|
||||
|
||||
.. module:: cmath
|
||||
:synopsis: Mathematical functions for complex numbers.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`cmd` --- Support for line-oriented command interpreters
|
||||
=============================================================
|
||||
:mod:`!cmd` --- Support for line-oriented command interpreters
|
||||
==============================================================
|
||||
|
||||
.. module:: cmd
|
||||
:synopsis: Build line-oriented command interpreters.
|
||||
|
|
|
@ -36,6 +36,7 @@ The following modules have a command-line interface.
|
|||
* :mod:`pyclbr`
|
||||
* :mod:`pydoc`
|
||||
* :mod:`quopri`
|
||||
* :ref:`random <random-cli>`
|
||||
* :mod:`runpy`
|
||||
* :ref:`site <site-commandline>`
|
||||
* :ref:`sqlite3 <sqlite3-cli>`
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`code` --- Interpreter base classes
|
||||
========================================
|
||||
:mod:`!code` --- Interpreter base classes
|
||||
=========================================
|
||||
|
||||
.. module:: code
|
||||
:synopsis: Facilities to implement read-eval-print loops.
|
||||
|
@ -27,7 +27,7 @@ build applications which provide an interactive interpreter prompt.
|
|||
|
||||
Closely emulate the behavior of the interactive Python interpreter. This class
|
||||
builds on :class:`InteractiveInterpreter` and adds prompting using the familiar
|
||||
``sys.ps1`` and ``sys.ps2``, and input buffering. If *local_exit* is True,
|
||||
``sys.ps1`` and ``sys.ps2``, and input buffering. If *local_exit* is true,
|
||||
``exit()`` and ``quit()`` in the console will not raise :exc:`SystemExit`, but
|
||||
instead return to the calling code.
|
||||
|
||||
|
@ -41,7 +41,7 @@ build applications which provide an interactive interpreter prompt.
|
|||
the :meth:`InteractiveConsole.raw_input` method, if provided. If *local* is
|
||||
provided, it is passed to the :class:`InteractiveConsole` constructor for
|
||||
use as the default namespace for the interpreter loop. If *local_exit* is provided,
|
||||
it is passed to the :class:`InteractiveConsole` constructor. The :meth:`interact`
|
||||
it is passed to the :class:`InteractiveConsole` constructor. The :meth:`~InteractiveConsole.interact`
|
||||
method of the instance is then run with *banner* and *exitmsg* passed as the
|
||||
banner and exit message to use, if provided. The console object is discarded
|
||||
after use.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`codecs` --- Codec registry and base classes
|
||||
=================================================
|
||||
:mod:`!codecs` --- Codec registry and base classes
|
||||
==================================================
|
||||
|
||||
.. module:: codecs
|
||||
:synopsis: Encode and decode data and streams.
|
||||
|
@ -1478,7 +1478,7 @@ Internationalized Domain Names (IDN)). It builds upon the ``punycode`` encoding
|
|||
and :mod:`stringprep`.
|
||||
|
||||
If you need the IDNA 2008 standard from :rfc:`5891` and :rfc:`5895`, use the
|
||||
third-party `idna module <https://pypi.org/project/idna/>`_.
|
||||
third-party :pypi:`idna` module.
|
||||
|
||||
These RFCs together define a protocol to support non-ASCII characters in domain
|
||||
names. A domain name containing non-ASCII characters (such as
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`codeop` --- Compile Python code
|
||||
=====================================
|
||||
:mod:`!codeop` --- Compile Python code
|
||||
======================================
|
||||
|
||||
.. module:: codeop
|
||||
:synopsis: Compile (possibly incomplete) Python code.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`collections.abc` --- Abstract Base Classes for Containers
|
||||
===============================================================
|
||||
:mod:`!collections.abc` --- Abstract Base Classes for Containers
|
||||
================================================================
|
||||
|
||||
.. module:: collections.abc
|
||||
:synopsis: Abstract base classes for containers
|
||||
|
@ -141,9 +141,6 @@ ABC Inherits from Abstract Methods Mi
|
|||
``__len__``,
|
||||
``insert``
|
||||
|
||||
:class:`ByteString` :class:`Sequence` ``__getitem__``, Inherited :class:`Sequence` methods
|
||||
``__len__``
|
||||
|
||||
:class:`Set` :class:`Collection` ``__contains__``, ``__le__``, ``__lt__``, ``__eq__``, ``__ne__``,
|
||||
``__iter__``, ``__gt__``, ``__ge__``, ``__and__``, ``__or__``,
|
||||
``__len__`` ``__sub__``, ``__xor__``, and ``isdisjoint``
|
||||
|
@ -257,7 +254,6 @@ Collections Abstract Base Classes -- Detailed Descriptions
|
|||
|
||||
.. class:: Sequence
|
||||
MutableSequence
|
||||
ByteString
|
||||
|
||||
ABCs for read-only and mutable :term:`sequences <sequence>`.
|
||||
|
||||
|
@ -274,12 +270,6 @@ Collections Abstract Base Classes -- Detailed Descriptions
|
|||
The index() method added support for *stop* and *start*
|
||||
arguments.
|
||||
|
||||
.. deprecated-removed:: 3.12 3.14
|
||||
The :class:`ByteString` ABC has been deprecated.
|
||||
For use in typing, prefer a union, like ``bytes | bytearray``, or
|
||||
:class:`collections.abc.Buffer`.
|
||||
For use as an ABC, prefer :class:`Sequence` or :class:`collections.abc.Buffer`.
|
||||
|
||||
.. class:: Set
|
||||
MutableSet
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`collections` --- Container datatypes
|
||||
==========================================
|
||||
:mod:`!collections` --- Container datatypes
|
||||
===========================================
|
||||
|
||||
.. module:: collections
|
||||
:synopsis: Container datatypes
|
||||
|
@ -134,7 +134,7 @@ The class can be used to simulate nested scopes and is useful in templating.
|
|||
:attr:`~collections.ChainMap.parents` property.
|
||||
|
||||
* The `Nested Contexts recipe
|
||||
<https://code.activestate.com/recipes/577434/>`_ has options to control
|
||||
<https://code.activestate.com/recipes/577434-nested-contexts-a-chain-of-mapping-objects/>`_ has options to control
|
||||
whether writes and other mutations apply only to the first mapping or to
|
||||
any mapping in the chain.
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`colorsys` --- Conversions between color systems
|
||||
=====================================================
|
||||
:mod:`!colorsys` --- Conversions between color systems
|
||||
======================================================
|
||||
|
||||
.. module:: colorsys
|
||||
:synopsis: Conversion functions between RGB and other color systems.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`compileall` --- Byte-compile Python libraries
|
||||
===================================================
|
||||
:mod:`!compileall` --- Byte-compile Python libraries
|
||||
====================================================
|
||||
|
||||
.. module:: compileall
|
||||
:synopsis: Tools for byte-compiling all Python source files in a directory tree.
|
||||
|
@ -226,7 +226,7 @@ Public functions
|
|||
The *invalidation_mode* parameter was added.
|
||||
|
||||
.. versionchanged:: 3.7.2
|
||||
The *invalidation_mode* parameter's default value is updated to None.
|
||||
The *invalidation_mode* parameter's default value is updated to ``None``.
|
||||
|
||||
.. versionchanged:: 3.8
|
||||
Setting *workers* to 0 now chooses the optimal number of cores.
|
||||
|
@ -289,7 +289,7 @@ Public functions
|
|||
The *invalidation_mode* parameter was added.
|
||||
|
||||
.. versionchanged:: 3.7.2
|
||||
The *invalidation_mode* parameter's default value is updated to None.
|
||||
The *invalidation_mode* parameter's default value is updated to ``None``.
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
Added *stripdir*, *prependdir*, *limit_sl_dest* and *hardlink_dupes* arguments.
|
||||
|
@ -318,7 +318,7 @@ Public functions
|
|||
The *invalidation_mode* parameter was added.
|
||||
|
||||
.. versionchanged:: 3.7.2
|
||||
The *invalidation_mode* parameter's default value is updated to None.
|
||||
The *invalidation_mode* parameter's default value is updated to ``None``.
|
||||
|
||||
To force a recompile of all the :file:`.py` files in the :file:`Lib/`
|
||||
subdirectory and all its subdirectories::
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`concurrent.futures` --- Launching parallel tasks
|
||||
======================================================
|
||||
:mod:`!concurrent.futures` --- Launching parallel tasks
|
||||
=======================================================
|
||||
|
||||
.. module:: concurrent.futures
|
||||
:synopsis: Execute computations concurrently using threads or processes.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`configparser` --- Configuration file parser
|
||||
=================================================
|
||||
:mod:`!configparser` --- Configuration file parser
|
||||
==================================================
|
||||
|
||||
.. module:: configparser
|
||||
:synopsis: Configuration file parser.
|
||||
|
@ -274,6 +274,11 @@ may be treated as parts of multiline values or ignored.
|
|||
By default, a valid section name can be any string that does not contain '\\n'.
|
||||
To change this, see :attr:`ConfigParser.SECTCRE`.
|
||||
|
||||
The first section name may be omitted if the parser is configured to allow an
|
||||
unnamed top level section with ``allow_unnamed_section=True``. In this case,
|
||||
the keys/values may be retrieved by :const:`UNNAMED_SECTION` as in
|
||||
``config[UNNAMED_SECTION]``.
|
||||
|
||||
Configuration files may include comments, prefixed by specific
|
||||
characters (``#`` and ``;`` by default [1]_). Comments may appear on
|
||||
their own on an otherwise empty line, possibly indented. [1]_
|
||||
|
@ -325,6 +330,27 @@ For example:
|
|||
# Did I mention we can indent comments, too?
|
||||
|
||||
|
||||
.. _unnamed-sections:
|
||||
|
||||
Unnamed Sections
|
||||
----------------
|
||||
|
||||
The name of the first section (or unique) may be omitted and values
|
||||
retrieved by the :const:`UNNAMED_SECTION` attribute.
|
||||
|
||||
.. doctest::
|
||||
|
||||
>>> config = """
|
||||
... option = value
|
||||
...
|
||||
... [ Section 2 ]
|
||||
... another = val
|
||||
... """
|
||||
>>> unnamed = configparser.ConfigParser(allow_unnamed_section=True)
|
||||
>>> unnamed.read_string(config)
|
||||
>>> unnamed.get(configparser.UNNAMED_SECTION, 'option')
|
||||
'value'
|
||||
|
||||
Interpolation of values
|
||||
-----------------------
|
||||
|
||||
|
@ -1216,6 +1242,11 @@ ConfigParser Objects
|
|||
names is stripped before :meth:`optionxform` is called.
|
||||
|
||||
|
||||
.. data:: UNNAMED_SECTION
|
||||
|
||||
A special object representing a section name used to reference the unnamed section (see :ref:`unnamed-sections`).
|
||||
|
||||
|
||||
.. data:: MAX_INTERPOLATION_DEPTH
|
||||
|
||||
The maximum depth for recursive interpolation for :meth:`~configparser.ConfigParser.get` when the *raw*
|
||||
|
|
|
@ -53,9 +53,12 @@ A small number of constants live in the built-in namespace. They are:
|
|||
See :exc:`NotImplementedError` for details on when to use it.
|
||||
|
||||
.. versionchanged:: 3.9
|
||||
Evaluating :data:`!NotImplemented` in a boolean context is deprecated. While
|
||||
it currently evaluates as true, it will emit a :exc:`DeprecationWarning`.
|
||||
It will raise a :exc:`TypeError` in a future version of Python.
|
||||
Evaluating :data:`!NotImplemented` in a boolean context was deprecated.
|
||||
|
||||
.. versionchanged:: 3.14
|
||||
Evaluating :data:`!NotImplemented` in a boolean context now raises a :exc:`TypeError`.
|
||||
It previously evaluated to :const:`True` and emitted a :exc:`DeprecationWarning`
|
||||
since Python 3.9.
|
||||
|
||||
|
||||
.. index:: single: ...; ellipsis literal
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`contextvars` --- Context Variables
|
||||
========================================
|
||||
:mod:`!contextvars` --- Context Variables
|
||||
=========================================
|
||||
|
||||
.. module:: contextvars
|
||||
:synopsis: Context Variables
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`copy` --- Shallow and deep copy operations
|
||||
================================================
|
||||
:mod:`!copy` --- Shallow and deep copy operations
|
||||
=================================================
|
||||
|
||||
.. module:: copy
|
||||
:synopsis: Shallow and deep copy operations.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`copyreg` --- Register :mod:`pickle` support functions
|
||||
===========================================================
|
||||
:mod:`!copyreg` --- Register :mod:`!pickle` support functions
|
||||
=============================================================
|
||||
|
||||
.. module:: copyreg
|
||||
:synopsis: Register pickle support functions.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`csv` --- CSV File Reading and Writing
|
||||
===========================================
|
||||
:mod:`!csv` --- CSV File Reading and Writing
|
||||
============================================
|
||||
|
||||
.. module:: csv
|
||||
:synopsis: Write and read tabular data to and from delimited files.
|
||||
|
@ -156,8 +156,10 @@ The :mod:`csv` module defines the following classes:
|
|||
|
||||
The *fieldnames* parameter is a :term:`sequence`. If *fieldnames* is
|
||||
omitted, the values in the first row of file *f* will be used as the
|
||||
fieldnames. Regardless of how the fieldnames are determined, the
|
||||
dictionary preserves their original ordering.
|
||||
fieldnames and will be omitted from the results. If
|
||||
*fieldnames* is provided, they will be used and the first row will be
|
||||
included in the results. Regardless of how the fieldnames are determined,
|
||||
the dictionary preserves their original ordering.
|
||||
|
||||
If a row has more fields than fieldnames, the remaining data is put in a
|
||||
list and stored with the fieldname specified by *restkey* (which defaults
|
||||
|
@ -347,8 +349,8 @@ The :mod:`csv` module defines the following constants:
|
|||
``None``. This is similar to :data:`QUOTE_ALL`, except that if a
|
||||
field value is ``None`` an empty (unquoted) string is written.
|
||||
|
||||
Instructs :class:`reader` objects to interpret an empty (unquoted) field as None and
|
||||
to otherwise behave as :data:`QUOTE_ALL`.
|
||||
Instructs :class:`reader` objects to interpret an empty (unquoted) field
|
||||
as ``None`` and to otherwise behave as :data:`QUOTE_ALL`.
|
||||
|
||||
.. versionadded:: 3.12
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
:mod:`ctypes` --- A foreign function library for Python
|
||||
=======================================================
|
||||
:mod:`!ctypes` --- A foreign function library for Python
|
||||
========================================================
|
||||
|
||||
.. module:: ctypes
|
||||
:synopsis: A foreign function library for Python.
|
||||
|
@ -2077,13 +2077,13 @@ Utility functions
|
|||
Does the same as the C ``sizeof`` operator.
|
||||
|
||||
|
||||
.. function:: string_at(address, size=-1)
|
||||
.. function:: string_at(ptr, size=-1)
|
||||
|
||||
This function returns the C string starting at memory address *address* as a bytes
|
||||
object. If size is specified, it is used as size, otherwise the string is assumed
|
||||
Return the byte string at *void \*ptr*.
|
||||
If *size* is specified, it is used as size, otherwise the string is assumed
|
||||
to be zero-terminated.
|
||||
|
||||
.. audit-event:: ctypes.string_at address,size ctypes.string_at
|
||||
.. audit-event:: ctypes.string_at ptr,size ctypes.string_at
|
||||
|
||||
|
||||
.. function:: WinError(code=None, descr=None)
|
||||
|
@ -2099,14 +2099,14 @@ Utility functions
|
|||
alias of :exc:`OSError`.
|
||||
|
||||
|
||||
.. function:: wstring_at(address, size=-1)
|
||||
.. function:: wstring_at(ptr, size=-1)
|
||||
|
||||
This function returns the wide character string starting at memory address
|
||||
*address* as a string. If *size* is specified, it is used as the number of
|
||||
Return the wide-character string at *void \*ptr*.
|
||||
If *size* is specified, it is used as the number of
|
||||
characters of the string, otherwise the string is assumed to be
|
||||
zero-terminated.
|
||||
|
||||
.. audit-event:: ctypes.wstring_at address,size ctypes.wstring_at
|
||||
.. audit-event:: ctypes.wstring_at ptr,size ctypes.wstring_at
|
||||
|
||||
|
||||
.. _ctypes-data-types:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue