pip换源、poetry换源

本文提供了一种简单的方法来更换pip及poetry的源地址,包括使用脚本自动配置和手动命令行配置两种方式,适用于Windows、Linux及MacOS等操作系统,并支持腾讯云、华为、阿里及豆瓣等多个镜像源。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

# pip 换源(Windows/Linux/MacOS通用):

方法一:使用脚本(支持Python2和Python3)

wget https://raw.githubusercontent.com/waketzheng/carstino/main/pip_conf.py
# 下载太慢的话,可以用这个加速:
#wget https://g.waketzheng.top/raw.githubusercontent.com/waketzheng/carstino/main/pip_conf.py
python pip_conf.py  # 不带参数默认为腾讯云
python pip_conf.py hw  # 华为
python pip_conf.py ali  # 阿里
python pip_conf.py douban  # 豆瓣

方法二:手动在命令行修改:

# 升级pip版本(可跳过)
python -m pip install -i https://mirrors.cloud.tencent.com/pypi/simple/ --upgrade --user pip
# 配置镜像为腾讯云
pip config set global.index-url https://mirrors.cloud.tencent.com/pypi/simple/

## 2022-05-11 补充

如果是云服务器,可以这样配置:

sudo python pip_conf.py --etc

会配置到/etc目录下,root用户和普通用户都会用这个源

## poetry换源

python pip_conf.py --poetry

将自动加载插件poetry-plugin-pypi-mirror并将镜像源URL写入到config.toml文件

## 2025-04-01 补充

### uv换源

python pip_conf.py --tool=uv --source=qinghua

### pdm换源

python pip_conf.py tx --pdm

* 更多用法见:./pip_conf.py --help

完整脚本内容如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
This script is for pip source config.
Worked both Windows and Linux/Mac, support python2.7 and python3.5+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Usage:
    $ ./pip_conf.py  # default to tencent source

Or:
    $ python pip_conf.py tx
    $ python pip_conf.py huawei
    $ python pip_conf.py aliyun
    $ python pip_conf.py douban
    $ python pip_conf.py qinghua

    $ python pip_conf.py https://pypi.mirrors.ustc.edu.cn/simple  # conf with full url

    $ python pip_conf.py --list  # show choices
    $ python pip_conf.py --poetry  # set mirrors in poetry's config.toml
    $ python pip_conf.py --pdm  # set pypi.url for pdm
    $ python pip_conf.py --uv  # set mirror for uv
    $ python pip_conf.py --tool=auto # find out manage tool at current directory and set mirror for it

    $ sudo python pip_conf.py --etc  # Set conf to /etc/pip.[conf|ini]

If there is any bug or feature request, report it to:
    https://github.com/waketzheng/carstino/issues
"""

__author__ = "waketzheng@gmail.com"
__updated_at__ = "2025.04.01"
__version__ = "0.7.0"
import contextlib
import functools
import os
import platform
import pprint
import re
import socket
import subprocess
import sys

try:
    import typing
except ImportError:
    pass
else:
    if typing.TYPE_CHECKING:
        from typing import Optional  # NOQA:F401

"""
A sample of the pip.conf/pip.ini:

[global]
index-url = https://mirrors.cloud.tencent.com/pypi/simple/
trusted-host = mirrors.cloud.tencent.com
"""

TEMPLATE = """
[global]
index-url = {}
trusted-host = {}
""".lstrip()
DEFAULT = "tx"
SOURCES = {
    "ali": "mirrors.aliyun.com/pypi",
    "db": "pypi.douban.com",
    "qh": "pypi.tuna.tsinghua.edu.cn",
    "tx": "mirrors.cloud.tencent.com/pypi",
    "tx_ecs": "mirrors.tencentyun.com/pypi",
    "hw": "repo.huaweicloud.com/repository/pypi",
    "ali_ecs": "mirrors.cloud.aliyuncs.com/pypi",
    "pypi": "pypi.org",
}
SOURCES["tencent"] = SOURCES["tengxun"] = SOURCES["tx"]
SOURCES["aliyun"] = SOURCES["ali"]
SOURCES["douban"] = SOURCES["db"]
SOURCES["qinghua"] = SOURCES["qh"]
SOURCES["huawei"] = SOURCES["hw"]
SOURCES["hw_inner"] = SOURCES["hw_ecs"] = (
    SOURCES["hw"]
    .replace("cloud", "")
    .replace("/repository", "")
    .replace("repo", "mirrors.tools")
)
CONF_PIP = "pip config set global.index-url "
INDEX_URL = "https://{}/simple/"
socket.setdefaulttimeout(5)


class ParamError(Exception):
    """Invalid parameters"""


class System:
    _system = None  # type: Optional[str]

    @classmethod
    def get_system(cls):
        # type: () -> str
        system = cls._system
        if system is None:
            system = cls._system = platform.system()
        return system

    @classmethod
    def is_win(cls):
        # type: () -> bool
        return cls.get_system() == "Windows"

    @classmethod
    def is_mac(cls):
        # type: () -> bool
        return cls.get_system() == "Darwin"


def is_mac():
    # type: () -> bool
    return System.is_mac()


def check_mirror_by_pip_download(domain, tmp=False):
    # type: (str, bool) -> bool
    if "/" not in domain:
        domain = "http://{0}/pypi/simple/ --trusted-host {0}".format(domain)
    elif "https:" not in domain:
        if not domain.startswith("http"):
            domain = "http://" + domain
        if domain.endswith("pypi"):
            domain += "/simple/"
        domain += " --trusted-host " + parse_host(domain)
    elif not domain.startswith("http"):
        domain = (
            "http://"
            + domain.lstrip("/")
            + " --trusted-host "
            + ensure_domain_name(domain)
        )
    cmd = "python -m pip download -i {} --isolated six".format(domain)
    if tmp:
        cmd += " -d /tmp"
    if os.system(cmd) == 0:
        dirname = "/tmp" if tmp else "."
        for name in os.listdir(dirname):
            if name.startswith("six-") and name.endswith(".whl"):
                os.remove(os.path.join(dirname, name))
        return True
    return False


def ensure_domain_name(host):
    # type: (str) -> str
    if "/" not in host:
        return host
    domain = host.split("://", 1)[-1].split("/", 1)[0]
    return domain


def is_pip_ready():
    # type: () -> bool
    return os.system("python -m pip --version") == 0


def check_url_reachable(url):
    # type: (str) -> bool
    try:
        from urllib.request import urlopen as _urlopen

        urlopen = functools.partial(_urlopen, timeout=5)
    except ImportError:
        from urllib import urlopen as _urlopen  # type:ignore

        class Response(object):
            def __init__(self, status):
                self.status = status

        @contextlib.contextmanager
        def urlopen(url):  # type:ignore
            f = _urlopen(url)
            r = f.read()
            status = 200 if r else 400
            yield Response(status)

    try:
        with urlopen(url) as response:
            if response.status == 200:
                return True
    except Exception as e:
        print(e)
        pass
    return False


def build_mirror_url(host):
    # type: (str) -> str
    if host.startswith("http"):
        url = host
    elif "/" not in host:
        url = "http://" + host
        if "pypi" not in host:
            url += "/pypi"
        url += "/simple/"
    else:
        url = "http://" + host
        if "simple" not in host:
            url += url.rstrip("/") + "/simple/"
    return url


def is_pingable(host="", is_windows=False, domain=""):
    # type: (str, bool,str) -> bool
    host = host or domain
    if is_windows:
        # 2024.12.23 Windows may need administrator to run `ping -c 1 xxx`
        # So use `pip download ...` instead.
        if is_pip_ready():
            return check_mirror_by_pip_download(host)
        else:
            url = build_mirror_url(host)
            return check_url_reachable(url)
    domain = ensure_domain_name(host)
    try:
        socket.gethostbyname(domain)
    except Exception:
        return False
    else:
        if is_pip_ready():
            return check_mirror_by_pip_download(host, tmp=True)
        return check_url_reachable(build_mirror_url(host))
    # Cost about 11 seconds to ping mirrors.cloud.aliyuncs.com
    cmd = "ping -c 1 {}".format(domain)
    return os.system(cmd) == 0


def load_bool(name):
    # type: (str) -> bool
    v = os.getenv(name)
    if not v:
        return False
    return v.lower() in ("1", "yes", "on", "true", "y")


def is_tx_cloud_server(is_windows=False):
    # type: (bool) -> bool
    return is_pingable(SOURCES["tx_ecs"], is_windows=is_windows)


def is_ali_cloud_server(is_windows=False):
    # type: (bool) -> bool
    return is_pingable(SOURCES["ali_ecs"], is_windows=is_windows)


def is_hw_inner(is_windows=False):
    # type: (bool) -> bool
    return is_pingable(SOURCES["hw_inner"], is_windows=is_windows)


def parse_host(url):
    # type: (str) -> str
    return url.split("://", 1)[-1].split("/", 1)[0]


def run_and_echo(cmd, dry=False):
    # type: (str, bool) -> int
    print("--> " + cmd)
    sys.stdout.flush()
    if dry:
        return 1
    return os.system(cmd)


def capture_output(cmd):
    # type: (str) -> str
    try:
        r = subprocess.run(cmd, shell=True, capture_output=True)
    except (TypeError, AttributeError):  # For python<=3.6
        with os.popen(cmd) as p:
            return p.read().strip()
    else:
        return r.stdout.decode().strip()


def config_by_cmd(url, is_windows=False):
    # type: (str, bool) -> None
    if not is_windows and is_mac():
        # MacOS need sudo to avoid PermissionError
        _config_by_cmd(url, sudo=True)
    else:
        _config_by_cmd(url, is_windows=is_windows)


def _config_by_cmd(url, sudo=False, is_windows=False):
    # type: (str, bool, bool) -> int
    cmd = CONF_PIP + url
    if not url.startswith("https"):
        print("cmd = {}".format(repr(cmd)))
        host = parse_host(url)
        if host in SOURCES["hw_inner"]:
            extra_host = host.replace("mirrors", "socapi").replace("tools", "cloudbu")
            try:
                socket.gethostbyname(ensure_domain_name(extra_host))
            except socket.gaierror:
                print("Ignore {} as it's not pingable".format(extra_host))
            else:
                extra_index = SOURCES["hw_inner"].replace(host, extra_host)
                extra_index_url = INDEX_URL.replace("https", "http").format(extra_index)
                cmd += " && pip config set global.extra-index-url " + extra_index_url
                host = '"{host} {extra_host}"'.format(host=host, extra_host=extra_host)
        cmd += " && pip config set global.trusted-host " + host
    if sudo:
        cmd = " && ".join("sudo " + i.strip() for i in cmd.split("&&"))
    return run_and_echo(cmd, dry="--dry" in sys.argv)


def smart_detect(source, is_windows):
    # type: (str, bool) -> tuple[str, bool]
    if is_windows:
        if is_hw_inner(True):
            return "hw_inner", True
    elif not is_mac():
        mirror_map = {
            "huawei": (is_hw_inner, "hw_inner"),
            "tencent": (is_tx_cloud_server, "tx_ecs"),
            "aliyun": (is_ali_cloud_server, "ali_ecs"),
        }
        mirrors = []
        welcome_file = "/etc/motd"
        if os.path.exists(welcome_file):
            with open(welcome_file) as f:
                msg = f.read().strip()
        else:
            msg = capture_output("python -m pip config list")
        if msg:
            mirrors = [v for k, v in mirror_map.items() if k in msg.lower()]
        if not mirrors:
            mirrors = list(mirror_map.values())
        for detect_func, source_name in mirrors:
            if detect_func(False):
                return source_name, True
    return source, False


def detect_inner_net(source, verbose=False, is_windows=False):
    # type: (str, bool, bool) -> str
    args = sys.argv[1:]
    inner = False
    if not args or all(i.startswith("-") for i in args):
        source, inner = smart_detect(source, is_windows=is_windows)
    elif source in ("huawei", "hw"):
        if is_hw_inner(is_windows):
            source = "hw_inner"
            inner = True
    elif "ali" in source:
        if is_ali_cloud_server(is_windows):
            source = "ali_ecs"
            inner = True
    elif "tx" in source or "ten" in source:
        inner = is_tx_cloud_server(is_windows)
        source = "tx_ecs" if inner else "tx"
    if verbose and inner:
        print("Use {} as it's pingable".format(source))
    return source


def build_index_url(source, force, verbose=False, strict=False, is_windows=False):
    # type: (str, bool, bool, bool, bool) -> str
    if source.startswith("http"):
        return source
    if not force:
        source = detect_inner_net(source, verbose, is_windows=is_windows)
    if strict:
        try:
            host = SOURCES[source]
        except KeyError:
            raise ParamError(  # noqa: B904
                "Unknown source: {}\nAvailables: {}".format(repr(source), list(SOURCES))
            )
    else:
        host = SOURCES.get(source, SOURCES[DEFAULT])
    url = INDEX_URL.format(host)
    if source in ("hw_inner", "hw_ecs", "tx_ecs", "ali_ecs"):
        url = url.replace("https", "http")
    return url


def get_parent_path(path):
    # type: (str) -> str
    return os.path.dirname(path.rstrip("/").rstrip("\\"))


def get_conf_path(is_windows, at_etc):
    # type: (bool, bool) -> str
    if is_windows:
        _pip_conf = ("pip", "pip.ini")
        conf_file = os.path.join(os.path.expanduser("~"), *_pip_conf)
    else:
        if at_etc:
            conf_file = os.path.join("/etc", "pip.conf")
        else:
            _pip_conf = (".pip", "pip.conf")
            conf_file = os.path.join(os.path.expanduser("~"), *_pip_conf)
    parent = get_parent_path(conf_file)
    if not os.path.exists(parent):
        os.mkdir(parent)
    return conf_file


class PdmMirror:
    @staticmethod
    def set(url):
        # type: (str) -> int
        cmd = "pdm config pypi.url " + url
        if url.startswith("http:"):
            cmd += " && pdm config pypi.verify_ssl false"
        return run_and_echo(cmd, dry="--dry" in sys.argv)


class Mirror:
    def __init__(self, url, is_windows, replace):
        # type: (str, bool, bool) -> None
        self.url = url
        self.is_windows = is_windows
        self.replace = replace
        self._version = ""

    @property
    def tool(self):
        # type: () -> str
        return self.__class__.__name__.replace("Mirror", "").lower()

    def prompt_y(self, filename, content):
        print("The {}'s {} content:".format(self.tool, filename))
        print(content)
        print('If you want to replace it, rerun with "-y" in args.')
        print("Exit!")

    def check_installed(self):
        # type: () -> Optional[bool]
        if self.tool == "poetry":
            not_install = run_and_echo(self.tool + " check --quiet") > 256
        else:
            not_install = run_and_echo(self.tool + " --version") != 0
        if not_install:
            msg = (
                "{0} not found!\n"
                "You can install it by:\n"
                "    pip install --user --upgrade pipx\n"
                "    pipx install {0}\n"
            )
            print(msg.format(self.tool))
            return True


class UvMirror(Mirror):
    def build_content(self, url=None):
        # type: (Optional[str]) -> str
        if url is None:
            url = self.url
        text = 'index-url="{}"'.format(url)
        if not url.startswith("https"):
            text += '\nallow-insecure-host=["{}"]'.format(parse_host(url))
        return text

    def set(self):
        # type: () -> Optional[int]
        filename = "uv.toml"
        dirpath = self.get_dirpath(self.is_windows, self.url, filename)
        if not dirpath:
            return 1
        config_toml_path = os.path.join(dirpath, filename)
        text = self.build_content()
        if os.path.exists(config_toml_path):
            with open(config_toml_path) as f:
                content = f.read().strip()
            if text in content:
                print("uv mirror set as expected. Skip!")
                return None
            if "index-url" in content:
                pattern = r'index-url\s*=\s*"([^"]*)"'
                m = re.search(pattern, content, re.S)
                if m:
                    already = m.group(1)
                    if not self.replace:
                        self.prompt_y(filename, m.group())
                        return None
                    if self.url.startswith("https"):
                        text = content.replace(already, self.url)
        elif not os.path.exists(dirpath):
            parent = get_parent_path(dirpath)
            if not os.path.exists(parent):
                os.mkdir(parent)
            os.mkdir(dirpath)
        do_write(config_toml_path, text)

    def _get_dirpath(self, is_windows, filename, is_etc=False):
        # type: (bool, str, bool) -> str
        if is_etc:
            parent = (
                os.path.join(os.environ["SYSTEMDRIVE"], "ProgramData")
                if is_windows
                else os.getenv("XDG_CONFIG_DIRS", "/etc")
            )
        else:
            default = "~/.config"
            env_key = "APPDATA" if is_windows else "XDG_CONFIG_HOME"
            parent = os.path.expanduser(os.getenv(env_key, default))
        return os.path.join(parent, "uv")

    def get_dirpath(self, is_windows, url, filename):
        # type: (bool, str, str) -> Optional[str]
        if self.check_installed():
            return None
        return self._get_dirpath(is_windows, filename)


class PoetryMirror(Mirror):
    plugin_name = "poetry-plugin-pypi-mirror"
    extra_plugins = [  # You can set PIP_CONF_NO_EXTRA_POETRY_PLUGINS=1 to skip install extra plugins
        "poetry-dotenv-plugin",
        "poetry-plugin-i",
        "poetry-plugin-version",
    ]

    def fix_poetry_v1_6_error(self, version):
        # type: (str) -> None
        if version >= "1.6":
            self.fix_v1_6_error()

    @classmethod
    def fix_v1_6_error(cls):
        # type: () -> None
        pipx_envs = capture_output("pipx environment")
        env_key = "PIPX_LOCAL_VENVS"
        key = "PIPX_HOME"
        module = cls.plugin_name.replace("-", "_")
        filename = "plugins.py"
        if env_key in pipx_envs:
            path = pipx_envs.split(env_key + "=")[1].splitlines()[0]
            lib = os.path.join(path, "poetry/lib")
            ds = os.listdir(lib)
            file = os.path.join(lib, ds[0], "site-packages", module, filename)
        elif key in pipx_envs:
            path = pipx_envs.split(key + "=")[1].splitlines()[0]
            lib = os.path.join(path, "venvs/poetry/lib")
            ds = os.listdir(lib)
            file = os.path.join(lib, ds[0], "site-packages", module, filename)
        else:
            code = "import {} as m;print(m.__file__)".format(module)
            path = capture_output("python -c {}".format(repr(code)))
            file = os.path.join(get_parent_path(path), filename)
        if not os.path.exists(file):
            print("WARNING: plugin file not found {}".format(file))
            return
        s = "semver"
        with open(file) as f:
            text = f.read()
        if s in text:
            text = text.replace(s, "constraints")
            with open(file, "w") as f:
                f.write(text)
            print("pypi mirror plugin error fixed.")

    @property
    def poetry_version(self):
        # type: () -> str
        if not self._version:
            self._version = self.get_poetry_version()
        self.fix_poetry_v1_6_error(self._version)
        return self._version

    @staticmethod
    def get_poetry_version():
        # type: () -> str
        v = capture_output("poetry --version")
        return v.replace("Poetry (version ", "")

    @staticmethod
    def unset():
        # type: () -> None
        print("By pipx:\n", "pipx runpip poetry uninstall <plugin-name>")
        print("By poetry self:\n", "poetry self remove <plugin-name>")

    def set_self_pypi_mirror(self, is_windows, url):
        # type: (bool, str) -> None
        config_path = self._get_dirpath(is_windows)
        if not os.path.exists(os.path.join(config_path, "pyproject.toml")):
            try:
                from poetry.console.commands.self.self_command import SelfCommand
            except ImportError:
                pass
            else:
                SelfCommand().generate_system_pyproject()
        cmds = [
            "cd {}".format(config_path),
            "poetry source add -v --priority=default pypi_mirror {}".format(url),
        ]
        run_and_echo(" && ".join(cmds), dry="--dry" in sys.argv)

    def get_dirpath(self, is_windows, url):
        # type: (bool, str) -> Optional[str]
        if self.check_installed():
            return None
        plugins = capture_output("poetry self show plugins")
        mirror_plugin = self.plugin_name
        if mirror_plugin not in plugins:
            if run_and_echo("pipx --version") == 0:
                install_plugin = "pipx inject poetry "
            else:
                self.set_self_pypi_mirror(is_windows, url)
                install_plugin = "poetry self add "
            dry = "--dry" in sys.argv
            if run_and_echo(install_plugin + mirror_plugin, dry=dry) != 0:
                print("Failed to install plugin: {}".format(repr(mirror_plugin)))
                return None
            if not load_bool("PIP_CONF_NO_EXTRA_POETRY_PLUGINS"):
                cmd = install_plugin + " ".join(self.extra_plugins)
                run_and_echo(cmd, dry=dry)
        return self._get_dirpath(is_windows)

    def _get_dirpath(self, is_windows):
        # type: (bool) -> str
        dirpath = "~/Library/Application Support/pypoetry/"
        if is_windows:
            dirpath = os.getenv("APPDATA", "") + "/pypoetry/"
        elif not is_mac():
            dirpath = "~/.config/pypoetry/"
        elif self.poetry_version < "1.5":
            dirpath = "~/Library/Preferences/pypoetry/"
        return os.path.expanduser(dirpath)

    def set(self):
        # type: () -> Optional[int]
        filename = "config.toml"
        dirpath = self.get_dirpath(self.is_windows, self.url)
        if not dirpath:
            return 1
        config_toml_path = os.path.join(dirpath, filename)
        item = "[plugins.pypi_mirror]"
        text = item + '{}url = "{}"'.format(os.linesep, self.url)
        if os.path.exists(config_toml_path):
            with open(config_toml_path) as f:
                content = f.read().strip()
            if text in content:
                print("poetry mirror set as expected. Skip!")
                return None
            if item in content:
                pattern = r'\[plugins\.pypi_mirror\].url = "([^"]*)"'
                m = re.search(pattern, content, re.S)
                if m:
                    already = m.group(1)
                    if not self.replace:
                        self.prompt_y(filename, m.group())
                        return None
                    text = content.replace(already, self.url)
            else:
                text = content + os.linesep + text
        elif not os.path.exists(dirpath):
            parent = get_parent_path(dirpath)
            if not os.path.exists(parent):
                os.mkdir(parent)
            os.mkdir(dirpath)
        do_write(config_toml_path, text)


def init_pip_conf(
    url,
    replace=False,
    at_etc=False,
    write=False,
    poetry=False,
    pdm=False,
    verbose=False,
    uv=False,
    is_windows=False,
):
    # type: (str, bool, bool, bool, bool, bool, bool, bool, bool) -> Optional[int]
    if poetry:
        return PoetryMirror(url, is_windows, replace).set()
    poetry_set_env = "SET_POETRY"
    if load_bool(poetry_set_env):
        env_name = "PIP_CONF_POETRY_MIRROR"
        v = os.getenv(env_name)
        if v:
            tip = "Poetry mirror source will be set to {}"
            print(tip.format(repr(v)))
            url = build_index_url(
                v, force=True, verbose=True, strict=True, is_windows=is_windows
            )
            replace = True
        elif verbose:
            tip = "Going to configure poetry mirror source, because {} was set to {}"
            print(tip.format(repr(poetry_set_env), os.getenv(poetry_set_env)))
        return PoetryMirror(url, is_windows, replace).set()
    if pdm or load_bool("PIP_CONF_SET_PDM"):
        return PdmMirror.set(url)
    if uv or load_bool("PIP_CONF_SET_UV"):
        return UvMirror(url, is_windows, replace).set()
    if not write and (not at_etc or is_windows) and can_set_global():
        config_by_cmd(url, is_windows)
        return None
    text = TEMPLATE.format(url, parse_host(url))
    conf_file = get_conf_path(is_windows, at_etc)
    if os.path.exists(conf_file):
        with open(conf_file) as fp:
            s = fp.read()
        if text in s:
            print("Pip source already be configured as expected.\nSkip!")
            return None
        if not replace:
            print("The pip file {} exists! content:".format(conf_file))
            print(s)
            print('If you want to replace it, rerun with "-y" in args.\nExit!')
            return None
    do_write(conf_file, text)


def do_write(conf_file, text):
    # type: (str, str) -> None
    with open(conf_file, "w") as fp:
        fp.write(text + "\n")
    print("Write lines to `{}` as below:\n{}\n".format(conf_file, text))
    print("Done.")


def can_set_global():
    # type: () -> bool
    s = capture_output("pip --version")
    m = re.search(r"^pip (\d+)\.(\d+)", s)
    if not m:
        return False
    return [int(i) for i in m.groups()] >= [10, 1]


def read_lines(filename):
    # type: (str) -> list[str]
    with open(filename) as f:
        s = f.read()
    return s.splitlines()


def auto_detect_tool(args):
    if args.tool == "pip":
        return args
    if args.tool in ("uv", "poetry", "pdm"):
        setattr(args, args.tool, True)
    elif args.tool != "auto":
        raise ValueError("Unknown tool: " + repr(args.tool))
    else:
        files = os.listdir(".")
        pyproject = "pyproject.toml"
        locks = {"uv.lock", "poetry.lock", "pdm.lock"} - set(files)
        if len(locks) == 1:
            tool = list(locks)[0].split(".")[0]
        elif pyproject not in files:
            return args  # Same as args.tool == 'pip'
        else:
            for line in read_lines(pyproject):
                if not line:
                    continue
                m = re.match(r"\[\[?tool\.(uv|pdm|poetry)[.\]]", line)
                if m:
                    tool = m.group(1)
                    break
        setattr(args, tool, True)
    return args


def main():
    # type: () -> Optional[int]
    from argparse import ArgumentParser

    parser = ArgumentParser()
    source_help = "the source of pip, ali/douban/hw/qinghua or tx(default)"
    parser.add_argument("name", nargs="?", default="", help=source_help)
    parser.add_argument("files", nargs="*", default="", help="Add for pre-commit")
    # Be compatible with old version
    parser.add_argument("-s", "--source", default=DEFAULT, help=source_help)
    parser.add_argument(
        "-l", "--list", action="store_true", help="Show available mirrors"
    )
    parser.add_argument("-y", action="store_true", help="whether replace existing file")
    parser.add_argument("--etc", action="store_true", help="Set conf file to /etc")
    parser.add_argument("-f", action="store_true", help="Force to skip ecs cloud check")
    parser.add_argument("--write", action="store_true", help="Conf by write file")
    parser.add_argument("--poetry", action="store_true", help="Set mirrors for poetry")
    parser.add_argument("--pdm", action="store_true", help="Set pypi.url for pdm")
    parser.add_argument("--uv", action="store_true", help="Set index url for uv")
    parser.add_argument(
        "-t", "--tool", default="auto", help="Choices: pip/uv/pdm/poetry"
    )
    parser.add_argument("--url", action="store_true", help="Show mirrors url")
    parser.add_argument(
        "--dry",
        action="store_true",
        help="Display cmd command without actually executing",
    )
    parser.add_argument("--verbose", action="store_true", help="Print more info")
    parser.add_argument(
        "--fix", action="store_true", help="Fix poetry pypi mirror plugin error"
    )
    if not sys.argv[1:]:
        # In case of runing by curl result, try to get args from ENV
        env = os.getenv("PIP_CONF_ARGS")
        if env:
            sys.argv.extend(env.split())
    args = parser.parse_args()
    if args.list:
        print("There are several mirrors that can be used for pip/poetry:")
        pprint.pprint(SOURCES)
    elif args.fix:
        PoetryMirror.fix_v1_6_error()
    else:
        source = args.name or args.source
        is_windows = System.is_win()
        url = build_index_url(source, args.f, args.verbose, is_windows=is_windows)
        if args.url:  # Only display prefer source url, but not config
            print(url)
            return None
        if not args.poetry and not args.pdm and not args.uv:
            args = auto_detect_tool(args)
        if init_pip_conf(
            url,
            replace=args.y,
            at_etc=args.etc,
            write=args.write,
            poetry=args.poetry,
            pdm=args.pdm,
            verbose=args.verbose,
            uv=args.uv,
            is_windows=is_windows,
        ):
            return 1


if __name__ == "__main__":
    if "--url" not in sys.argv:
        try:
            from asynctor import timeit
        except (ImportError, SyntaxError, AttributeError):
            pass
        else:
            main = timeit(main)
    sys.exit(main())

<think>嗯,用户遇到了一个JavaScript错误:“data.reduce is not a function”,并且显示在某个Vue组件的代码中。我需要先理解这个错误的原因,然后一步步帮助用户解决。 首先,这个错误通常发生在尝试对一个不是数组的数据类型调用`reduce`方法的时候。因为`reduce`是数组的方法,如果`data`不是数组,就会抛出这个错误。比如,如果`data`是null、undefined、对象或者其他类型,调用`data.reduce`就会失败。 接下来,用户提供的堆栈跟踪信息显示错误发生在LinE.vue组件的第22行30列的位置。这说明问题出在这个Vue组件的脚本部分,具体是在使用`reduce`方法处理`data`变量的时候。 那么,我需要让用户检查几个方面。首先,确认`data`变量的类型。可能的情况是,`data`可能没有被正确初始化为数组,或者在某个时刻被赋予了非数组的值。例如,如果从API获取数据,可能返回了非数组的结构,或者在处理过程中出现了错误,导致`data`变成了null或undefined。 其次,用户需要检查`data`的来源。如果是通过异步请求获取的数据,可能在数据还未到达时就已经尝试调用`reduce`,这时候`data`可能还是undefined。这时候应该确保数据已经正确加载,并且处理可能的加载状态或错误情况。 另外,可能是在处理数据的过程中,某个操作改变了`data`的类型。比如,如果对数组进行了某些操作但返回了非数组结果,或者在赋值时出现了错误,导致`data`不再是数组。 解决方法方面,首先建议在调用`reduce`之前添加类型检查,比如使用`Array.isArray(data)`来确认`data`确实是数组。如果不是,可以给出默认值或者错误处理。此外,使用可选链操作符(?.)或者空值合并操作符(??)可以在一定程度上预防这类错误,确保在`data`为null或undefined时不会调用`reduce`。 还有,用户需要检查数据的流程,确保所有可能修改`data`的地方都正确处理了类型。比如,如果从父组件传递props,需要确认父组件传递的是数组。或者在Vue的data函数中,是否正确初始化了`data`为数组。 可能用户的问题还涉及到Vue的反应式系统。比如,可能在某个地方修改了`data`,但Vue的反应式更新没有正确触发,导致`data`的值不是预期的数组。这时候需要检查Vue的响应式相关操作,比如使用`this.$set`来确保数组的更新能被Vue正确追踪。 总结来说,用户需要逐步排查`data`变量的来源、类型变化以及确保在使用`reduce`前`data`确实是一个数组。添加必要的校验和错误处理可以避免这类错误的发生。</think>这个错误`TypeError: data.reduce is not a function`表示你尝试对非数组类型的变量调用了数组方法`reduce()`。以下是逐步排查和解决方案: --- ### **1. 直接原因分析** - **`reduce()`方法特性**:`reduce`是JavaScript数组对象的内置方法,只有数组才能调用它 - **错误场景**:当`data`变量是`null`/`undefined`/对象/字符串/数字等非数组类型时,调用`data.reduce()`会抛出此错误 --- ### **2. 关键排查步骤** #### **(1) 检查变量类型** 在调用`reduce`前添加类型验证: ```javascript console.log('data类型:', typeof data); // 应输出 "object" console.log('是否为数组:', Array.isArray(data)); // 应输出 true ``` #### **(2) 回溯数据来源** 根据堆栈路径`LinE.vue?vue&type=script&lang=js:22:30`定位到代码第22行,检查: - `data`是否来自异步请求未正确处理(如未初始化默认值) - 父组件传递的prop是否保证为数组类型 - 是否存在条件分支导致`data`被意外赋值为非数组 #### **(3) 典型错误场景** ```javascript // 错误示例1:未初始化默认值 let data; // undefined data.reduce(...); // 错误示例2:接口返回非数组结构 const response = { items: [] }; const data = response.result; // 若result字段不存在,data为undefined // 错误示例3:操作失误改变类型 const data = [1,2,3].map(...).filter(...); // 若操作返回空或非数组 ``` --- ### **3. 解决方案** #### **(1) 添加防御性校验** ```javascript if (!Array.isArray(data)) { console.error('data必须是数组'); return; // 或返回默认值 } const result = data.reduce(...); ``` #### **(2) 使用可选链操作符(Optional Chaining)** ```javascript const result = data?.reduce(...) || []; // 若data非数组则返回空数组 ``` #### **(3) 确保数据初始化** 对于Vue组件,在`data()`中明确初始化: ```javascript export default { data() { return { data: [] // 初始化为空数组 } } } ``` #### **(4) 处理异步数据** 若`data`来自异步请求,添加加载状态保护: ```javascript // 使用v-if确保数据就绪后再渲染 <template> <div v-if="isLoaded"> <!-- 使用data的代码 --> </div> </template> <script> export default { data() { return { data: [], isLoaded: false } }, async mounted() { try { this.data = await fetchData(); // 确保接口返回数组 this.isLoaded = true; } catch (error) { console.error('请求失败:', error); } } } </script> ``` --- ### **4. 总结** | 关键点 | 说明 | |-----------------------|----------------------------------| | 错误根源 | 对非数组变量调用`.reduce()` | | 核心排查方向 | 变量类型、数据来源、异步逻辑 | | 防御性代码建议 | 类型检查 + 可选链 + 数据初始化 | 通过逐步验证数据类型并确保数据流向的可靠性,可彻底解决此问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值