当前位置: 代码网 > it编程>前端脚本>Python > Python函数中参数传递顺序的强制规则详解

Python函数中参数传递顺序的强制规则详解

2026年07月21日 Python 我要评论
一、开篇:一张图看清python参数的全貌在前几篇文章中,我们分别学习了位置参数、关键字参数、默认参数、*args和**kwargs。但当你把它们组合在一起时,有一个关键问题:这些不同类型的参数应该按

一、开篇:一张图看清python参数的全貌

在前几篇文章中,我们分别学习了位置参数、关键字参数、默认参数、*args**kwargs。但当你把它们组合在一起时,有一个关键问题:这些不同类型的参数应该按什么顺序排列?

python的答案是强制的、固定的顺序。你不能随心所欲地排列——语法层面就会报错。

# python函数参数的完整定义顺序(从前往后):
# 1. 位置专用参数      (/ 之前)     python 3.8+
# 2. 普通参数          (/ 和 * 之间)
# 3. 可变位置参数      (*args)
# 4. 关键字专用参数    (* 或 *args 之后)
# 5. 可变关键字参数    (**kwargs)    必须放在最后

# 下面我们逐一拆解为什么是这个顺序

理解这个顺序规则不仅是记住语法——它反映了python函数调用的底层逻辑。一旦你理解了"为什么",就永远不会写错了。

二、参数分类全景图

2.1 五种参数类型

# ⌨️ python函数的参数可以分为五大类:

# 第一类:位置专用参数(positional-only)
# 在 / 之前定义,只能用位置传递
def func1(a, b, /):
    """a和b只能用位置传递"""
    pass

# 第二类:位置或关键字参数(positional-or-keyword)
# 默认的参数类型,既可以用位置也可以用关键字
def func2(a, b):
    """a和b位置或关键字都可以"""
    pass

# 第三类:可变位置参数(*args)
# 用 * 标记,收集任意数量的位置参数
def func3(a, *args):
    """args收集多余的位置参数"""
    pass

# 第四类:关键字专用参数(keyword-only)
# 在 * 或 *args 之后定义,只能用关键字传递
def func4(*, a, b):
    """a和b只能用关键字传递"""
    pass

# 第五类:可变关键字参数(**kwargs)
# 用 ** 标记,收集任意数量的关键字参数
def func5(**kwargs):
    """kwargs收集多余的关键字参数"""
    pass

2.2 每条规则都有它的道理

# 规则一:普通参数必须在默认参数之前
# 原因:如果默认参数在前面,位置传参就无法确定哪个参数被省略了
# ✅ 正确
def f1(a, b=1): pass

# ❌ 错误
# def f1(a=1, b): pass
# syntaxerror: non-default argument follows default argument

# 规则二:*args必须在普通参数之后
# 原因:*args会"贪婪地"收集所有位置参数,如果放在前面,后面的参数永远得不到值
# ✅ 正确
def f2(a, *args): pass

# ❌ 错误
# def f2(*args, a): pass  # a永远得不到位置参数的值,只能用关键字传

# 规则三:**kwargs必须在*args之后
# 原因:**kwargs收集关键字参数,而*args收集位置参数,两者不冲突
# 但如果**kwargs在前面,会干扰*args的识别
# ✅ 正确
def f3(*args, **kwargs): pass

# ❌ 错误
# def f3(**kwargs, *args): pass
# syntaxerror: invalid syntax

# 规则四:**kwargs必须在最后
# 原因:**kwargs会收集所有剩余的关键字参数
# ✅ 正确
def f4(a, *args, **kwargs): pass

# ❌ 错误
# def f4(**kwargs, a): pass
# syntaxerror: invalid syntax

三、完整参数定义的语法规则

3.1 标准顺序(不使用位置专用参数)

# 最常用的参数定义顺序
# def func(普通参数, ..., *args, 关键字专用参数, ..., **kwargs):
#     pass

# 完整示例
def create_api(
    method,                    # 1. 普通参数(必选)
    path,                      #    普通参数(必选)
    version="v1",              #    普通参数(带默认值)
    *args,                     # 2. 可变位置参数
    timeout=30,                # 3. 关键字专用参数(有默认值)
    retries=3,                 #    关键字专用参数(有默认值)
    auth_token=none,           #    关键字专用参数(可选)
    **kwargs                   # 4. 可变关键字参数
):
    """创建api请求配置"""
    config = {
        "method": method,
        "path": f"/api/{version}/{path}",
        "timeout": timeout,
        "retries": retries,
        "auth_token": auth_token,
        "extra_positional": args,
        "extra_keyword": kwargs,
    }
    return config

# 各种调用方式
print(create_api("get", "users"))
# {'method': 'get', 'path': '/api/v1/users', 'timeout': 30, ...}

print(create_api("post", "users", "v2", timeout=60, auth_token="abc123"))
# {'method': 'post', 'path': '/api/v2/users', 'timeout': 60, ...}

print(create_api("get", "search", "v3", "extra1", "extra2",
                 timeout=10, retries=5, q="python", page=1))
# args=('extra1', 'extra2'), kwargs={'q': 'python', 'page': 1}

3.2 带位置专用参数的完整顺序(python 3.8+)

# def func(位置专用参数, /, 普通参数, *args, 关键字专用参数, **kwargs):

def advanced_function(
    a, b,                  # 位置专用参数(只能按位置传递)
    /,                     # ← 分隔符
    c, d,                  # 普通参数(位置或关键字都行)
    *args,                 # 可变位置参数
    e="default_e",         # 关键字专用参数
    f="default_f",         # 关键字专用参数
    **kwargs               # 可变关键字参数
):
    """所有参数类型都齐全的函数"""
    print(f"位置专用: a={a}, b={b}")
    print(f"普通参数: c={c}, d={d}")
    print(f"可变位置: args={args}")
    print(f"关键字专用: e={e}, f={f}")
    print(f"可变关键字: kwargs={kwargs}")

# ✅ 正确的调用方式
advanced_function(1, 2, 3, 4, 5, 6, e="e", f="f", extra="extra")
# 位置专用: a=1, b=2
# 普通参数: c=3, d=4
# 可变位置: args=(5, 6)
# 关键字专用: e=e, f=f
# 可变关键字: kwargs={'extra': 'extra'}

# ✅ 普通参数c,d可以用关键字
advanced_function(1, 2, d=4, c=3, e="e", f="f")
# 位置专用: a=1, b=2
# 普通参数: c=3, d=4 (注意:关键字不按顺序)
# 可变位置: args=()
# 关键字专用: e=e, f=f
# 可变关键字: kwargs={}

# ❌ 位置专用参数不能用关键字
# advanced_function(a=1, b=2, c=3, d=4, e="e", f="f")
# typeerror: got some positional-only arguments passed as keyword arguments: 'a, b'

# ❌ 关键字专用参数不能用位置
# advanced_function(1, 2, 3, 4, "e", "f")
# typeerror: takes 4 positional arguments but 6 were given
# "e"和"f"被*args吃掉了

3.3 只有没有args的情况

# * 本身也可以作为分隔符——不收集任何位置参数

def func(a, b, *, c, d):
    """
    a, b: 位置或关键字
    c, d: 关键字专用
    """
    print(f"a={a}, b={b}, c={c}, d={d}")

# ✅ 正确
func(1, 2, c=3, d=4)
func(a=1, b=2, c=3, d=4)

# ❌ 错误
# func(1, 2, 3, 4)
# typeerror: takes 2 positional arguments but 4 were given
# * 阻止了位置参数,c和d只能用关键字

# 这种模式很常用——当你确定某些参数容易混淆时
def copy_file(source, destination, *, overwrite=false):
    """source和destination容易混淆,但至少它们可以用位置传"""

def copy_file_safe(*, source, destination, overwrite=false):
    """更安全——所有参数都必须用关键字"""

四、pep文档中的规范

4.1 pep 3102——关键字专用参数

# pep 3102(2006年)引入了关键字专用参数
# 这是python 3.0中的重要改进

# 在pep 3102之前,无法强制用户使用关键字传参
# 只能用文档说明:"请使用关键字参数"

# pep 3102之后:
def sort_items(items, *, key=none, reverse=false):
    """key和reverse必须用关键字——和内置sorted()一样"""
    return sorted(items, key=key, reverse=reverse)

# 和内置sorted()保持一致的api风格
# sorted(iterable, /, *, key=none, reverse=false)
#       位置专用   关键字专用

4.2 pep 570——位置专用参数

# pep 570(2020年,python 3.8)引入了位置专用参数
# 使用 / 作为分隔符

# 这解决了长期存在的问题:
# 有些函数的参数名没有意义,用户不应该用关键字传参

# 标准库中大量使用位置专用参数的例子:

# len(obj, /)           —— 你不能写 len(obj=[1,2,3])
# abs(x, /)             —— 你不能写 abs(x=-5)
# int(x=0, /)           —— 第一个参数x只能用位置
# str(object='', /)     —— 第一个参数object只能用位置
# list(iterable=(), /)  —— 第一个参数iterable只能用位置

# 自定义函数中使用 / 的例子:
def isclose(a, b, /, *, rel_tol=1e-09, abs_tol=0.0):
    """
    比较两个浮点数是否接近
    a, b: 两个数值,应该用位置传递(哪个是哪个不重要)
    rel_tol, abs_tol: 容差配置,用关键字传递更清晰
    """
    return abs(a - b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

print(isclose(3.0, 3.0000000001))              # true
print(isclose(3.0, 3.0000000001, rel_tol=1e-11))  # false
print(isclose(3.0, 3.1, abs_tol=0.2))          # true

五、调用时的参数传递顺序

5.1 位置参数必须全部在关键字参数之前

# 这是python语法层面的强制规则

def func(a, b, c=3, d=4):
    print(f"a={a}, b={b}, c={c}, d={d}")

# ✅ 多种正确的调用方式
func(1, 2)                        # 全位置
func(1, 2, 3, 4)                  # 全位置
func(a=1, b=2)                    # 全关键字
func(1, 2, d=5)                   # 混用(位置在前)
func(1, b=2, c=3, d=4)           # 混用
func(1, 2, c=3)                   # 混用

# ❌ 关键字参数不能在位置参数之前
# func(a=1, 2)
# syntaxerror: positional argument follows keyword argument

# ❌ 不能给同一个参数传多个值
# func(1, 2, a=3)
# typeerror: got multiple values for argument 'a'

5.2 通过*args传递的参数占用位置

def func(a, *args, b):
    """b在*args后面,必须用关键字传"""
    print(f"a={a}, args={args}, b={b}")

# *args会贪婪地吃掉所有位置参数
func(1, 2, 3, 4, b=5)
# a=1, args=(2, 3, 4), b=5

# 如果你想让args为空,b必须用关键字
func(1, b=2)
# a=1, args=(), b=2

六、常见设计模式

6.1 模式一:核心+选项模式(最常用)

# ⌨️ 日常开发中最推荐的参数组织方式
# 核心参数放前面(位置传递),选项参数放后面(关键字传递)

def fetch_data(
    url,                    # 核心——位置传
    *,                      # ← 从这里开始都是选项
    method="get",
    headers=none,
    timeout=30,
    parse_json=true,
    allow_redirects=true,
    max_redirects=5,
):
    """获取数据——url是核心,其余是选项"""
    print(f"{method} {url} (timeout={timeout}s)")

# 使用
fetch_data("https://api.example.com/users")  # 简洁
fetch_data("https://api.example.com/admin", method="post", timeout=60)

6.2 模式二:透传模式

# 当一个函数需要将参数转发给另一个函数时
# *args和**kwargs是完美的工具

def with_retry(max_retries=3, delay=1):
    """重试装饰器——将任意参数透传给目标函数"""
    def decorator(func):
        import time

        def wrapper(*args, **kwargs):
            for attempt in range(max_retries):
                try:
                    return func(*args, **kwargs)  # 透传所有参数
                except exception as e:
                    if attempt == max_retries - 1:
                        raise
                    print(f"重试 {attempt + 1}/{max_retries}...")
                    time.sleep(delay)
        return wrapper
    return decorator

@with_retry(max_retries=3, delay=0.5)
def unstable_network_call(url, method="get", **params):
    """不稳定的网络调用"""
    import random
    if random.random() < 0.7:
        raise connectionerror("网络错误!")
    return f"成功: {method} {url}"

# 调用——参数可以任意变化
# unstable_network_call("https://api.example.com")
# unstable_network_call("https://api.example.com", method="post", data="hello")

6.3 模式三:子类重写模式

# 在类的继承体系中,使用*args和**kwargs保证灵活性

class basehandler:
    """基础处理器"""

    def __init__(self, name, **config):
        self.name = name
        self.config = config
        self.setup()

    def setup(self):
        """子类可以重写这个方法来初始化"""
        pass

class filehandler(basehandler):
    """文件处理器"""

    def __init__(self, name, file_path, **config):
        # 将不需要的参数通过**config传给父类
        super().__init__(name, **config)
        self.file_path = file_path
        print(f"文件处理器初始化: {file_path}, 配置={config}")

class databasehandler(basehandler):
    """数据库处理器"""

    def __init__(self, name, connection_string, **config):
        super().__init__(name, **config)
        self.connection_string = connection_string
        print(f"数据库处理器初始化: {connection_string}, 配置={config}")

# 使用
file_handler = filehandler("文件处理", "/tmp/data.log", max_size=1024, encoding="utf-8")
db_handler = databasehandler("数据库", "mysql://localhost/mydb", pool_size=10, timeout=30)

七、常见错误速查表

# ⚠️ 以下错误按出现频率排列

# 1. 默认参数在非默认参数前面
# def func(a=1, b):       # syntaxerror!
#     pass

# 2. **kwargs不在最后
# def func(**kwargs, a):  # syntaxerror!
#     pass

# 3. *args不在**kwargs前面
# def func(**kwargs, *args):  # syncerror!
#     pass

# 4. 关键字参数在位置参数前面(调用时)
# func(a=1, 2)            # syntaxerror!

# 5. 给同一个参数传了多个值(调用时)
# def func(a, b): pass
# func(1, a=2)            # typeerror!

# 6. 位置专用参数用了关键字(调用时)
# def func(a, /): pass
# func(a=1)               # typeerror!

# 7. 关键字专用参数用了位置(调用时)
# def func(*, a): pass
# func(1)                 # typeerror!

# 8. 传了不存在的关键字参数
# def func(a, b): pass
# func(1, 2, c=3)         # typeerror!

# 9. 忘了*args后面参数必须用关键字
# def func(*args, a): pass
# func(1, 2, 3)           # typeerror! a没有值

八、记忆口诀和决策树

8.1 记忆口诀

定义函数参数的顺序——"普位可变关键可变关键"

  • 普:普通参数
  • 位:位置专用参数(/)
  • 可变:*args
  • 关键:关键字专用参数(*)
  • 可变关键:**kwargs
  • 全展开:位置专用 / 普通 *args 关键字专用 **kwargs

8.2 参数类型选型决策

# 💡 设计函数时,如何选择参数类型?

# 决策1:这个参数是核心的还是可选的?
#   核心 → 普通参数(位置传递)
#   可选 → 默认参数 或 关键字专用参数

# 决策2:参数名有意义吗?
#   有 → 可以用关键字传递
#   没有(如数学函数)→ 位置专用参数

# 决策3:参数容易和别的参数混淆吗?
#   是 → 关键字专用参数(强制用名字区分)
#   否 → 普通参数

# 决策4:参数数量不确定?
#   是 → *args(多位置)或 **kwargs(多关键字)

# 决策5:这是布尔参数吗?
#   几乎总是 → 关键字专用参数
#   因为 func(true, false) 没人看得懂

# 决策6:这是配置选项吗?
#   是 → 关键字专用参数 + 合理默认值

九、总结

python的函数参数顺序规则看似复杂,但每一层规则都有其设计上的必然性。一旦你理解了"为什么是这个顺序",就永远不会写错。

终极参数顺序(完整版):

def func(
    位置专用参数,      # / 之前——参数名无意义时用
    /,                 # 分隔符
    普通参数,          # 可以位置也可以关键字——核心参数
    *args,             # 多余的位置参数——参数数量不定时用
    关键字专用参数,    # * 之后——容易混淆的选项参数
    **kwargs           # 最后——透传参数时用
):

日常最简化的实用版本:

def func(核心1, 核心2, 可选=默认值, *, 选项1=默认, 选项2=默认, **透传):
    """日常开发中最实用的参数模式"""

记住:好的函数签名能让代码自解释。花30秒想清楚参数的顺序和类型,能为未来省下30分钟的调试时间。

以上就是python函数中参数传递顺序的强制规则详解的详细内容,更多关于python函数参数传递顺序的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com