首页 / 资讯中心 / 文章详情

python函数声明和调用定义及原理详解

python函数声明和调用定义及原理详解 ★ FEATURED ARTICLE
前言函数是 Python 里最基本的复用单位。写一个函数要做两件事声明定义用def告诉解释器有这么个东西、要什么参数、做什么调用用函数名加 括号真的去执行它。看似简单但函数从被定义到被调用之间解释器悄悄做 了不少动作理解了这些动作很多怪现象就不再怪了。最经典的误解是默认参数的求值时机。很多人以为def f(x, lst[])里的[]是每次调用时新建一个空列表实际上它只在函数定义时计算一次之后所有 调用共用同一个列表对象。这就是可变默认参数这个著名陷阱的根源。本文讲清楚def到底做了什么、五类参数怎么排布、默认值何时求值、返回值与None的关系、以及调用时的传参规则。示例以 Python 3.8 及以上为准涉及更新 版本的语法会单独注明。一、def 是一条可执行语句def不是编译期的声明而是运行到那一行时才执行的语句。执行时它做三件事 创建一个函数对象、把参数信息包括默认值存进这个对象、然后把名字绑定到 当前作用域。# 适用于 Python 3.8def add(a, b):返回两数之和。return a bprint(add.__name__) # addprint(add.__doc__) # 返回两数之和。函数对象自带__name__、__doc__等属性这也说明函数是对象——它可以被 赋值、放进容器、当参数传。因为def是可执行语句它必须在你调用之前被 执行到在模块顶层就是先定义、后调用的书写顺序。二、五类参数Python 的参数槽位可以分成五类按从左到右的顺序排列# 适用于 Python 3.8def demo(a, b2, *args, c, d4, **kwargs):print(a, b, args, c, d, kwargs)demo(1, 3, 5, 6, c7, x9)# 1 3 (5, 6) 7 4 {x: 9}逐段解释普通参数a调用时必须提供。带默认值b2可以省略省略时用 2。可变位置参数*args把多余的位置实参收成一个元组。仅关键字参数c*或*args之后只能按c...传。可变关键字参数**kwargs把多余的关键字实参收成一个字典。它们的顺序有硬性规定正常参数在前带默认值的在后否则语法错误*args再往后**kwargs必须最后。写法类型收成什么关键点a普通参数单个对象必传b2默认参数单个对象可省默认值定义时求值*args可变位置元组收多余位置实参c在*后仅关键字单个对象只能c...**kwargs可变关键字字典收多余关键字实参三、仅位置参数/3.8反过来只能按位置传、不能按关键字传的参数用/标记需要 Python 3.8 及以上PEP 570。# 适用于 Python 3.8def divide(a, b, /):return a / bprint(divide(6, 3)) # 2.0# divide(a6, b3) # TypeErrora、b 是仅位置参数/放在仅位置参数和其余参数之间。3.7 及更早版本没有这个语法写了会直接SyntaxError升级前不要用。仅位置参数的意义是让参数名可以自由更改而不影响 调用方常见于标准库的底层接口。四、默认值只在定义时求值一次这是全篇最重要的一条。默认值表达式在def执行的那一刻计算结果被存进 函数对象之后每次调用省略该参数拿到的都是同一个对象。# 适用于 Python 3.8import timedef log(msg, whentime.time()):return whenprint(log(a) log(b)) # True两次拿到的是同一个时间戳对不可变默认值数字、字符串、None这没问题。但默认值是可变对象时 就会跨调用累积# 适用于 Python 3.8def add_item(x, lst[]):lst.append(x)return lstprint(add_item(1)) # [1]print(add_item(2)) # [1, 2] ← 上一次的结果还在print(add_item(3)) # [1, 2, 3]正确写法是用None当哨兵在函数体里判断并新建# 适用于 Python 3.8def add_item(x, lstNone):if lst is None:lst []lst.append(x)return lst五、返回值与 None函数没写return等于在末尾隐式return None。只写return不带值也返回None。return可以一次返回多个值Python 会打包成元组。# 适用于 Python 3.8def minmax(nums):return min(nums), max(nums)lo, hi minmax([3, 1, 2])print(lo, hi) # 1 3print(minmax([3, 1, 2])) # (1, 3) 其实是一个元组注意区分返回None和什么都不返回——Python 里没有后者一切函数调用都有 返回值只是可能是None。这也是为什么x my_list.sort()之后x是Nonelist.sort()是原地排序、返回None。六、调用时的传参规则调用时位置实参必须在关键字实参之前。# 适用于 Python 3.8def box(width, height, colorred):return (width, height, color)print(box(3, 4, blue)) # (3, 4, blue)print(box(3, height4)) # (3, 4, red)print(box(width3, height4)) # (3, 4, red)# box(width3, 4) # SyntaxError位置实参在关键字实参之后还可以把序列和字典摊开传进去f(*seq)摊开位置参数f(**mapping)摊开 关键字参数。# 适用于 Python 3.8args (3, 4)kwargs {color: green}print(box(*args, **kwargs)) # (3, 4, green)常见坑点可变默认参数跨调用累积python # ❌ lst 只在定义时创建一次 def add_item(x, lst[]): lst.append(x) return lst # ✅ 用 None 哨兵 def add_item(x, lstNone): if lst is None: lst [] lst.append(x) return lst默认值在定义时就算好了python import time # ❌ 想每次调用都取现在其实取的是定义那一刻 def log(msg, whentime.time()): return when # ✅ 默认 None调用时再算 def log(msg, whenNone): if when is None: when time.time() return when带默认值的参数写在普通参数前面python # ❌ 语法错误非默认参数不能跟在默认参数后面 def f(a1, b): pass # ✅ 调整顺序 def f(a, b1): pass位置实参放在关键字实参后面python # ❌ 调用时语法错误 box(width3, 4) # ✅ 位置在前 box(3, height4)以为没写 return 就不是返回值python def save(x): print(x) # ❌ 以为 result 就是 x其实 save 没 returnresult 是 None result save(1) # ✅ 需要结果就显式 return def save(x): return x把可变对象当默认值互传python # ❌ 函数被多个调用方共用参数互相污染 def collect(x, bucket{}): bucket[x] True return bucket # ✅ 让调用方决定容器 def collect(x, bucketNone): if bucket is None: bucket {} bucket[x] True return bucket用kwargs 吞掉拼错的参数名**python # ❌ 拼错 timeout 也不会报错被 kwargs 悄悄收走 def connect(host, **kwargs): return kwargs connect(a, timeuot5) # {timeuot: 5} # ✅ 明确列出真正需要的参数 def connect(host, timeout5): return timeout省略括号把函数对象当结果用python # ❌ 忘了括号结果是函数对象本身 result add # ✅ 调用它 result add(2, 3)总结关注点结论def 的性质可执行语句运行到才创建函数对象默认值求值时机函数定义时求值一次之后复用同一对象可变默认参数经典陷阱用None哨兵规避*args/**kwargs收成元组 / 字典*之后仅关键字参数/之前3.8仅位置参数无 return返回None抓住默认值只在定义时求值一次和没有 return 就返回None这两条函数相关 的坑就去掉了一大半。剩下的参数顺序、仅关键字和仅位置参数多写几遍就顺手了。
阅读完成 · 觉得有帮助?
咨询建站