python 基本结构语句(函数和模块)
前言Python 程序的基本积木只有两样东西函数function把一段逻辑打包成可以 反复调用的单元模块module把一个.py文件打包成可以反复导入的单元。 两者的关系是层层套娃——模块里装函数包package里装模块。初学者常有的误解有两个。第一以为模块必须是一个很正式的东西其实任何 一个.py文件就是一个模块你随手写的tools.py就是一个模块import tools就能用它。第二以为import只是引用一下不会产生副作用——实际上import会从头到尾执行目标模块的顶层语句这决定了模块里哪些代码该写、 哪些该放进函数里。本文讲清楚def的定义与调用规则、模块与导入的几种写法、以及__name__的 作用。所有写法都以 Python 3 为准不涉及早已停止维护的 Python 2。一、函数定义与调用用def定义函数用函数名加括号调用。函数体靠缩进界定return把结果 交回调用方。# 适用于 Python 3.8def area(width, height):返回矩形面积。return width * heightresult area(3, 4)print(result) # 12要点return是可选参数。没写return或者只写return不带值函数都返回None。这不是什么都不返回而是返回None。def是可执行语句。解释器执行到def那一行时才创建函数对象并把名字绑定上去。所以函数必须在调用之前定义在模块顶层按从上到下的顺序。参数有五种形态普通参数、带默认值的参数、*args收集多余位置参数、**kwargs收集多余关键字参数、以及*后面的仅关键字参数。默认值在函数定义时求值一次这一点在常见坑点里单独说。二、模块一个文件就是一个模块把上面那个函数单独存成shapes.py它就变成一个模块。另一个文件里这样用# 适用于 Python 3.8import shapesprint(shapes.area(2, 5)) # 10import做三件事找到shapes.py、执行它的顶层代码、把得到的模块对象 绑定到名字shapes上。之后用shapes.area访问里面的函数。导入有多种写法各有适用场景写法调用方式适用场景import shapesshapes.area()最推荐来源清晰import shapes as spsp.area()模块名太长from shapes import areaarea()只用一两个名字from shapes import area as aa()避免与本地名字冲突from shapes import *area()不推荐污染名字空间包package就是含有__init__.py的目录里面可以放多个模块用点号 分层导入例如import mypkg.shapes。从 Python 3.3 起还有命名空间包 不一定需要__init__.py但初学阶段老老实实放一个__init__.py最不容易 出问题。三、模块的执行与 __name__前面说过import会执行模块的顶层代码。那如果这个模块既想被导入、又想在 命令行直接运行时执行一些额外逻辑怎么办答案是判断__name__。__name__是每个模块都有的变量被导入时它的值就是模块名作为脚本直接 运行时它的值是字符串__main__。于是就有了那句著名的守卫# 适用于 Python 3.8# 文件 shapes.pydef area(width, height):return width * heightif __name__ __main__:print(area(3, 4))直接python shapes.py运行__name__ __main__成立会打印 12。在别的文件里import shapes__name__ shapes那段print**不会执行**只把area导入进来。这就是主入口的含义把只应该在直接运行时做的事放进if __name__ __main__:里。背后的机制是直接运行的那个脚本文件其模块名被设为__main__。四、把函数和模块组织好的几条经验一个模块负责一件事。shapes.py就只放几何相关的函数不要塞进数据库连接、字符串工具等无关内容。顶层只放定义不放动作。顶层写def、class、常量赋值是安全的写打印、读写文件、发网络请求这类动作就要小心因为导入方一import就会执行。函数尽量小而单一。一个函数只做一件事参数和返回值都清晰测试和复用都更容易。数据用参数传不要藏在全局变量里。全局可变状态会让函数行为随调用顺序变化很难排查。# 适用于 Python 3.8# tools.pydef normalize(text):去掉首尾空白并转小写。return text.strip().lower()def split_words(text):按空白切成单词列表。return normalize(text).split()# 适用于 Python 3.8# main.pyfrom tools import split_wordsprint(split_words( Hello World )) # [hello, world]五、函数签名速查语法名称行为def f(a, b)普通参数必须传可按位置或关键字def f(a1)带默认值可省略省略时用 1def f(*args)可变位置参数多余的按位置参数收成元组def f(**kwargs)可变关键字参数多余的关键字参数收成字典def f(a, *, b)仅关键字参数b只能写b...传def f(a, /, b)仅位置参数a只能按位置传3.8def f(a, /, b)的/是Python 3.8 才引入的PEP 570。在 3.7 及更早 版本里写/会直接语法错误。仅位置参数常用于内建函数那种参数名不重要、 不该按关键字传的接口。常见坑点忘了 return结果拿到 Nonepython # ❌ 只打印不返回外面拿到的是 None def double(x): print(x * 2) # ✅ 用 return 把结果交出去 def double(x): return x * 2函数在定义之前调用python # ❌ def 还没执行到名字不存在 foo() def foo(): pass # ✅ 先定义后调用 def foo(): pass foo()导入模块时执行了不该执行的动作python # ❌ 顶层直接干活一 import 就触发 # report.py print(正在生成报表...) # ✅ 放进守卫或函数里 if __name__ __main__: print(正在生成报表...)**用 from x import * 造成名字覆盖**python # ❌ 不知道导入了什么可能覆盖本地名字 from math import * # ✅ 只导入用到的 from math import sqrt, pi循环导入python # ❌ a.py 导入 bb.py 又导入 a可能 ImportError # ✅ 把共用部分抽到第三个模块或在函数内部延迟导入 def need_b(): import b # 延迟到调用时才导入 return b.something()把可变数据挂在模块顶层被到处改python # ❌ 谁都来改 CACHE行为随调用顺序漂移 CACHE [] def add(x): CACHE.append(x) # ✅ 用参数传或封装进类里管理函数里给模块级变量赋值忘了 globalpython COUNT 0 # ❌ 有赋值语句COUNT 被当成局部变量读取时未绑定 → UnboundLocalError def bump(): COUNT COUNT 1 # ✅ 声明 global 后改的才是模块级那个 def bump(): global COUNT COUNT 1默认参数写成可变对象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总结概念关键点函数def是可执行语句必须在调用前定义返回值没有return就返回None参数普通 / 默认 /*args/**kwargs/*//模块一个.py文件就是一个模块导入import x会执行x的顶层代码主入口if __name__ __main__:区分脚本与导入把函数负责一件事、模块顶层只做定义、导入不做副作用这三条原则记牢代码结构 就不会乱。参数形态和__name__是最容易记混的两处动手写几个小模块跑一跑 就能形成稳定的直觉。