标签 Python 下的文章

pyenv 来管理 Python 版本

pyenv 是一个强大 Python 包管理工具,可以灵活地切换各种 Python 版本,使用 pyenv 来管理我们的 Python 版本,优雅高效且不会破坏掉系统自带的 Python 环境:

macOS 安装 pyenv

➜ brew install pyenv

接着为 pyenv 配置 shell 环境,提高工作效率,可自动联想 Tab 补全我们本地安装的 Python 版本:

echo 'eval "$(pyenv init -)"' >> ~/.zshrc

pyenv 安装 Python

# 查看已经安装的Python版本
➜ pyenv versions

# 查看当前的 Python 版本
➜ pyenv version

# 查看可安装的版本
➜ pyenv install -l

# 安装与卸载 pypy3.8-7.3.11
➜ pyenv install pypy3.8-7.3.11
➜ pyenv uninstall pypy3.8-7.3.11

版本切换确实很方便,所安装的版本都在 ~/.pyenv/versions 目录下:

# global 全局设置 一般不建议改变全局设置
➜ pyenv global <python版本>

# shell 会话设置 只影响当前的shell会话
➜ pyenv shell <python版本>
# 取消 shell 会话的设置
➜ pyenv shell --unset

# local 本地设置 只影响所在文件夹
➜ pyenv local <python版本>

pyenv 的 global、local、shell 的优先级关系是:shell > local > global

Python 的 pip 管理工具

pipenv 是一个强大的工具,用于简化 Python 项目中的依赖管理和虚拟环境管理。以下是一些常见的 pipenv 用法示例:

更多关于 pipenv 的信息,可以查看它的官方文档:

安装 pipenv

首先,你需要确保已经安装了 pipenv。你可以通过以下命令安装它:

pip install pipenv

创建和管理虚拟环境

创建一个新项目并初始化 **pipenv** 环境

mkdir my_project
cd my_project
pipenv install

安装一个新的包

pipenv 会自动创建一个虚拟环境(如果还没有创建),并将包安装到该虚拟环境中。

pipenv install requests

安装一个开发依赖包

开发依赖包只在开发环境中需要,比如测试工具。

pipenv install --dev pytest

卸载一个包

pipenv uninstall requests

激活虚拟环境

激活虚拟环境后,你可以在其中运行 Python 命令和脚本。

pipenv shell

要退出虚拟环境,使用 exit 命令。

运行脚本而不激活虚拟环境

如果你不想手动激活虚拟环境,可以使用以下命令直接在虚拟环境中运行脚本。

pipenv run python your_script.py

管理依赖文件

生成 Pipfile.lock
Pipfile.lock 文件记录了所有包的精确版本,保证项目的一致性。

pipenv lock

安装 Pipfile 中的所有依赖
如果你克隆了一个包含 Pipfile 的项目,可以使用以下命令安装所有依赖。

pipenv install

检查包的安全性
pipenv 提供了一个方便的命令来检查已安装的包是否有已知的安全漏洞。

pipenv check

其他有用的命令

查看已安装包列表

pipenv graph

更新包

pipenv update requests

或更新所有包:

pipenv update

清理未使用的包

pipenv clean

查看虚拟环境路径

pipenv --venv

查看 Python 解释器路径

pipenv --py

这些命令和用法应该可以帮助你更有效地管理 Python 项目的依赖和虚拟环境。如果你需要更多信息和详细的用法,可以查看 pipenv 的官方文档

例如:

# 安装 pipenv
pip install pipenv

mkdir my_project

cd my_project

# 初始化 pipenv 环境
pipenv install

# 安装模块 openpyxl
pipenv install openpyxl

# 激活虚拟环境,运行python,使用 exit 命令退出环境
pipenv shell

# 直接在虚拟环境中运行脚本
pipenv run python your_script.py

Python的模块为日常编程中许多问题提供了标准的解决方案,同时也能让我们更加简洁优雅地管理代码。

模块

定义:
每个模块是由1个或多个Python文件构成。是相关的类、函数和变量所组成的一个集合。

它可以被其它代码引入并直接使用。

通常,一个模块中的各个功能是相互关联的。

模块中的函数就像是积木,用一块一块的积木搭建成一个完整的图形,搭好的图形就是模块。

了解完模块的基本概念后,先来看看最常接触的第一大类模块:内置模块

内置模块是在安装Python时自带的模块。Python提供了大量丰富且高质量的内置模块来帮助完成一些系统化工作。

比如,生成一个随机的数字可以使用random模块,要处理日期和时间则可以通过datetime模块等。

为了使用这些模块,首先需要通过关键词import导入模块

导入模块(I)

import random
randomNum = random.random()
print(randomNum)

代码的作用
这三行代码输出了一个随机数。

第一行导入了random模块,该模块用于生成随机数;
第二行将生成的随机数赋值给了变量randomNum;
第三行输出了randomNum这个变量的数值。

import
关键字import,中文意思是“导入”,表明我们要在这里导入模块。

模块的名称
一个名为random的模块,该模块用于生成一个随机数。

模块内的函数
导入random模块后,就可以通过 random.xxx() 来使用该模块中的所有函数。

这里,我们调用了random模块里的random()函数,该函数用于生成一个0到1的随机浮点数。

模块内的函数
random模块中还有其它的函数,可以根据自己的需要来调用。

比如,想生成一个在1到7之间的随机整数,可以使用random模块里的randint()函数,代码为:random.randint(1,7)

代码小结
要导入模块时,就可以使用这样的格式:

使用import导入模块后,除了可以获取到模块内的函数,还可以获取到该模块中的类。通过调用类里面的方法,来实现某一功能。

比如,要获取今天的日期,就需要用到datetime模块里的date类,以及该类中的today()方法。

import datetime
todayDate = datetime.date.today()
print(todayDate)

导入模块
使用关键字import,中文意思是“导入”,表明我们要在这里导入一个名称为datetime的模块。

模块中的一个类
一个类,表明我们在调用datetime模块里的date类。

date类是一个日期类,里面提供了一些关于日期的方法与属性。

date类里面的某一个方法
一个方法,表明我们要调用的today()从属于date类。

该方法用于获取当前的本地日期。

代码小结
导入模块后,想获取到该模块中的类里面的某一个方法时,就可以使用这样的格式:

小练习

使用import导入random模块。

使用该模块下的random()函数,生成一个0到1之间的随机浮点数,并使用print()将结果输出。

import random
print(random.random())

获取今天日期的年份,并判断跟实际今年的年份(2022)是否相等。相等输出True,不相等输出False。

import datetime
today = datetime.date.today()
year = today.year
print(year == 2022)

不难看出,一个模块是由很多类和函数组成的。

有时候只想导入(import)模块里面的一部分内容,可能是一个类或是一个函数。 比如,我们只希望导入datetime模块中的date类。

这时候,就可以用到 from...import...

导入模块(II)

from datetime import date
todayDate = date.today()
print(todayDate)

两个关键字
第一个关键字from允许我们从模块中导入一个指定的部分。

这两个关键字,表明从datetime模块里导入date类。

模块的名称
一个名为datetime的模块,表示要从该模块中导入某个类。

一个类
一个类,表明我们要调用的date类属于datetime模块。

date类里面的某一个方法
一个方法,表明我们要调用的today()从属于date类。

可以看到,使用from...import...这种形式时,调用datetime模块中的类或函数时不需要datetime.前缀

代码小结
当我们只想导入模块里面的一部分内容时,就可以使用这样的格式:

第三方模块

学习了导入模块的两种方式,但是导入的都是内置模块。这些模块是Python自带的,具有较高的通用性。

除此之外,我们还会接触到许多的第三方模块。这些非内置的模块由其他的开发者所编写,提供给公众免费使用,功能更加个性化。可以从Python Package Index上找到自己需要的模块。

内置模块可以直接导入后使用,而非内置的模块则需要先进行安装,然后才能导入使用。

安装模块

为了安装第三方模块,需要一个叫作 pip 的工具。

在安装Python环境的时候,pip工具已经随着一起自动安装了,现在可以直接在电脑上使用pip工具来安装第三方模块。

安装第三方模块的方式在不同的电脑系统上略微有些不同

Windows

要使用pip工具安装第三方模块,需要在Windows的命令提示符里进行。

可以使用以下两种方式打开命令提示符:

  1. 在电脑左下角「开始菜单」旁的搜索栏里输入cmd
  2. 同时在键盘上按下快捷键WINR,在运行框里输入cmd

打开一个Windows命令提示窗口,就可以开始安装第三方模块了。

只需要在命令提示符的窗口里,使用 pip3 install「ModuleName」命令。这里,「ModuleName」是你所选择的模块的名称。

假如我们想制作一个可交互的柱状图,可以通过第三方模块 pyecharts,该模块是一个强大的数据可视化工具。

直接在终端中输入:pip3 install pyecharts==1.8.1 ,然后敲击回车键,就能进行安装。

这里由于pyecharts的特殊性,在安装时要指定版本1.8.1,而其它大多数模块都可以直接使用模块名安装。

如果在自己电脑上安装不上或安装缓慢,可在命令后添加如下配置进行加速:-i https://pypi.tuna.tsinghua.edu.cn/simple/

pip3 install pyecharts==1.8.1 -i https://pypi.tuna.tsinghua.edu.cn/simple/

点击回车后,会出现很多的安装信息。当看到「Successfully installed...」或显示「Requirement already satisfied」的时候,说明模块已经安装成功。

可能会遇到黄色的警告,这是在提示需要升级pip工具。可以选择忽视,也可以按照指示在终端中输入「pip install --upgrade pip」进行升级。

安装过程如图所示。

macOS

要使用pip工具安装第三方模块,需要在macOS的终端里进行。

我们可以点击「启动台」后,在上方的搜索栏里输入终端,然后打开即可。

打开终端窗口,就可以开始安装第三方模块了。

只需要在终端窗口里,使用 pip3 install「ModuleName」命令。这里,「ModuleName」是你所选择的模块的名称。

假如我们想制作一个可交互的柱状图,就可以通过第三方模块 pyecharts 来完成。

直接在终端中输入:pip3 install pyecharts==1.8.1 ,然后敲击回车键,就能进行安装。

这里由于pyecharts的特殊性,在安装时要指定版本1.8.1,而其它大多数模块都可以直接使用模块名安装。

如果在自己电脑上安装不上或安装缓慢,可在命令后添加如下配置进行加速:

pip3 install pyecharts==1.8.1 -i https://pypi.tuna.tsinghua.edu.cn/simple/

点击回车后,会出现很多的安装信息。当看到「Successfully installed...」的时候,说明模块已经安装成功。
安装过程如图所示。

示例

安装成功后,就可以关闭当前窗口,回到编辑器里使用该模块来绘制柱状图了。和内置模块一样,我们需要导入模块。

第一行代码:
使用 pyecharts.charts 的方式,访问了pyecharts模块里的子模块charts
然后,通过from...import...,从pyecharts模块的子模块charts里导入了Bar类。

第二行代码:
使用from...import...,从pyecharts模块中,导入子模块options,并将其简写为了opts

这里,import...as...是用于在导入一个模块的同时,为该模块取一个别名。

这是为了之后在代码中使用该模块时能更加方便,不用再通过options调用,可以直接通过opts。

导入完成后,我们就可以通过这些模块,绘制出一个可交互的柱状图啦。

from pyecharts.charts import Bar
from pyecharts import options as opts

bar = Bar()

bar.add_xaxis(["衬衫", "毛衣", "领带", "裤子", "风衣", "高跟鞋", "袜子"])
bar.add_yaxis("商家A", [114, 55, 27, 101, 125, 27, 105])
bar.add_yaxis("商家B", [57, 134, 137, 129, 145, 60, 49])
bar.set_global_opts(title_opts=opts.TitleOpts(title="某商场销售情况"))

bar.render("bar.html")

小练习

引入math模块
使用import来导入内置模块:math,该模块提供了大量的数学运算函数。

导入模块后,完成以下两个操作:

  1. 通过math模块内 sqrt() 函数,输出99的开平方结果;
  2. 通过math模块内 fabs() 函数,输出-0.03的绝对值。
import math
print(math.sqrt(99))
print(math.fabs(-0.03))

计算平均值
statistics是Python中的一个数学统计模块。

使用statistics.mean()可以计算出一个列表的平均值,只需要将列表传入到括号中即可。

使用import导入statistics模块,并定义一个列表numList = [10, 99, 78, 50]。使用statistics.mean()计算该列表的平均值,并输出。

import statistics
numList = [10,99,78,50]
print(statistics.mean(numList))

from...import...
使用from...import...来完成上一道题目

定义一个numList = [10, 99, 78, 50],使用statistics模块下的mean()来计算该列表的平均值,并输出。

from statistics import mean
numList = [10,99,78,50]
print(mean(numList))

在生活中,我们通过特征与功能来描述一个具体对象,转换到代码中就变成了数据与函数。
回顾已经学过的内容,我们学习了用来存储数据的变量与用来存储代码的函数。
现在让我们把变量与函数打包,来学习在程序中创建类与对象。

类代表着一类事物,比如人类,鸟类,汽车,电脑。当我们提起这些事物的时候,我们并不特指某一个具体的东西,比如人类是泛指抽象的全人类

而对象指的是类的实例,比如人类的对象是某个具体的人,如张三,李四,tony和kevin。

类与对象

类代表一些拥有相同特性与功能的事物,如鸟类,人类,猫类等。

类中的某一个具体实例称为这个类的实例对象,简称为对象。

若请你介绍一下自己的手机,你会如何去做?
你可以通过颜色、型号、容量大小,屏幕尺寸等数据来描述它的特征;
也可以用拍照、安装应用、打电话等操作来描述它的功能。

我们可以把拥有上述特性的设备都归类于手机,即手机就是一个类。

手机的特征称为这个类的属性,手机的功能称为这个类的方法。

属性与方法

定义:
属性用来描述这个类的一些特征,如品牌,颜色,型号是手机的属性。

方法用来表现这个类的一些功能,如拍照,打电话等是手机的方法。

在Python中通过class关键字来创建一个类。
为了方便理解,接下来在程序中定义一个手机类。
并为手机设定两个方法:打电话,发短信。

类的定义

class Phone():
    def makeCall(self, who):
        return f"正在拨打电话给{who}"

    def sendMsg(self, who, txt):
        return f"正在发送短信给{who}, 内容为{txt}"

代码的作用
class定义一个类,并命名为Phone

第2~6行,为该类添加了打电话和发短信两个功能。即定义了两个方法makeCallsendMsg

第2,3行,makeCall方法接收联系人的名字为参数,并将文字格式化输出。

第5,6行,sendMsg方法接收联系人的名字与短信内容为参数,并将文字格式化输出。

关键字
class,在程序中定义类的关键字。

类名
一串文字,为类定义的名称。
为了区分类与函数,类的名称首字母建议大写。

括号
一对括号,定义类的标准写法。

:
一个冒号,表明接下来缩进的代码是属于这个类的。

四个空格
一个缩进,表示该代码块属于Phone这个类。

方法名
类中的两个特殊函数,称为方法。
方法又被称为成员函数,用来表现这个类的一些功能。

成员函数的第一个参数为self

特殊的参数
self参数,由程序自动传入的参数,指调用该方法的对象。

第一个参数为self,是定义实例方法的固定写法。

代码小结
当我们要定义一个类时就需要这几个部分

类是抽象的,我们要使用类,就需要实例化一个对象。

对象是以类为模板创建的。

这个过程类似于以人类的共同特性为模板,创造一个特定的人。
可以这样说,你就是人类的一个实例对象。

接下来学习一下如何通过类来实例一个具体的对象。
用Phone类来实例化两个对象表示我的手机与你的手机。

实例化对象

class Phone():

    def makeCall(self, who):
        return f"正在拨打电话给{who}"

    def sendMsg(self, who, txt):
        return f"正在发送短信给{who}, 内容为{txt}"

myPhone = Phone()
yourPhone = Phone()

ret = myPhone.makeCall("Tony")
print(ret)
ret2 = yourPhone.sendMsg("Jeremy", "中午吃啥?")
print(ret2)

代码的作用
将刚才定义好的Phone类拿过来,并实例化两个对象myPhoneyourPhone

第9,10行,创建Phone的对象myPhoneyourPhone

第12行,调用myPhone的方法makeCall,把结果存储到ret中。

第14行,调用yourPhone的方法sendMsg,把结果存储到ret2

对象名
一个变量名,为对象设定的名称。

类名
定义好的类名。

括号
括号,表示调用Phone类实例化一个对象。

对象
对象名称,用对象名调用指定的方法。

句点
一个句点【.】。

用句点连接对象名与对象的方法,称为句点表示法。

例如第11行,表示myPhone使用了他的方法(功能)makeCall(打电话)。

方法名
方法名,对象要调用的方法名称。

实参
调用方法时传递的实参。

调用对象的方法时,不需要为self参数传递实参。

这里,第11行的“Tony”实参传递给makeCall“who”形参。

第13行的“Jeremy”实参传递给sendMsg“who”形参,“中午吃啥”传递给sendMsg“txt”形参。

代码小结
当我们要实例化对象时就需要这几个部分

小练习

实例化对象
为Cat实例化一个对象mimi,并使用该对象调用talk方法。

class Cat():
    def talk(self):
        print("喵喵喵")

# 创建一个Cat对象,并存储在mimi中
mimi = Cat()
# 调用mimi的方法talk
mimi.talk()

类的属性与初始化

在刚才的代码中,创建了一个包含两个方法的Phone类,并且创建两个不同的实例对象去调用这些方法。

现在让我们为类设置属性,并在创建实例化对象时,初始化该属性的值。

手机的属性有:颜色,品牌。

class Phone():
    def __init__(self, bd, clr):
        print("创建实例对象时,自动调用此方法")
        self.brand = bd
        self.color = clr

myPhone = Phone("华为", "白色")
yourPhone = Phone("苹果", "黑色")
print(f"我有一个{myPhone.color}的{myPhone.brand}手机")
print(f"你有一个{yourPhone.color}的{yourPhone.brand}手机")

代码的作用
Phone类添加品牌(brand)与颜色(color)两个属性。

第2行,定义初始化方法__init__并设定两个参数bdclr

第8,9行,创建实例化对象myPhoneyourPhone,并为__init__方法传递参数两组不同的参数。

第10,11行,分别格式化输出实例对象的colorbrand属性

初始化方法
初始化方法__init__,是一个特殊的方法。

init左右两边各有两个下划线,即整个名称共有四个下划线。

初始化类似于出厂设置,表示“开始时做好准备”,会在创建对象时自动被调用

特殊的参数
self参数,是调用方法时由程序自动传入的参数,指实例化后的对象。

句点
一个句点,用来连接对象名与属性。

因为这里还没有创建对象,所有对象名用self参数代替。

属性
对象的两个变量,称为属性。

与之前创建的变量不同,brandcolor是专属于该类对象的变量,只能被类的对象使用。

参数
初始化函数的两个形参名。接收传递的数据后赋值给对象的两个属性brandcolor

实参
两组数据。在创建对象时,为初始化函数中的brandcolor传递的数据。

使用属性
两个对象分别使用它们的属性,并格式化输出内容。

代码小结
当我们初始化属性时需要这几个部分

特殊的self参数

通过前面的内容知道,对象的属性与方法需要用句点表示法将对象名与方法名连接在一起。

但是在定义类时,我们还不知道要创建哪些对象,所以self的作用就是将实例化的对象名称(引用)传递到方法中。

比如在myPhone对象中,self.brand 代表的是myPhone.brand

初始化过程
在创建一个实例对象时,程序会自动调用init方法。

最后

总结一下类与对象的注意事项。

方法与属性只能被实例对象调用

就像append只能被列表使用一样,方法与属性只能用句点表示法被该类的实例使用,否则就会出错。

函数与方法
在Python中有着一切皆对象的说法,我们创建的字符串、列表、元组等本质上都是该类型的一个对象。

所以直接调用的print()range()为函数,用句点表示调用的append()keys()等为某个对象的方法。

小练习

属性的使用
现有一个Cat类,并为该类初始化了两个属性名称(name)与品种(breed)。

假设我有一只“橘猫”叫做“大黄”,实例化一个Cat对象存储到myCat中,并传递参数"大黄", "橘猫"。

假设你有一只"布偶猫"叫做“土豆”,实例化一个Cat对象存储到yourCat中,并传递参数"土豆", "布偶猫"

最后分别按照“有一只xx叫做xxx”的格式输出(需要用到格式化输出)。

class Cat():
    def __init__(self, name, breed):
        self.name = name
        self.breed = breed
    def talk(self):
        print("喵喵喵")

# 实例化一个Cat对象存储到myCat中,并传递参数"大黄", "橘猫"
myCat = Cat("大黄", "橘猫")
# 实例化一个Cat对象存储到yourCat中,并传递参数"土豆", "布偶猫"
yourCat = Cat("土豆", "布偶猫")
# 分别按照格式输出字符串
print(f"有一只{myCat.breed}叫做{myCat.name}")
print(f"有一只{yourCat.breed}叫做{yourCat.name}")

判断用户是否为会员
现有一个User类,并为该类初始化了两个属性用户名(name)与是否为会员(isVIP)。

实例化一个User对象存储到tony中,并传递参数"Tony", True
实例化一个User对象存储到jeremy中,并传递参数"Jeremy", False
最后分别调用tony与jeremy的sayHi方法。

class User():
    def __init__(self, name, isVIP):
        self.name = name
        self.isVip = isVIP

    def sayHi(self):
        if self.isVip:
            print(f'尊贵的会员{self.name},欢迎您!')
        else:
            print(f'{self.name},欢迎您!')

# 实例化一个User对象存储到tony中,并传递参数"Tony", True
tony = User("Tony", True)
# 实例化一个User对象存储到jeremy中,并传递参数"Jeremy", False
jeremy = User("Jeremy", False)
# 分别调用tony与jeremy的sayHi方法
tony.sayHi()
jeremy.sayHi()

计算学生平均成绩
现有一个MathScore类,并为该类初始化了两个属性班级名称(name)与本学期数学成绩列表(scoreList)。

实例化一个MathScore对象存储到class32 中,并传递参数"三年级二班", [78, 99, 88, 87, 67]
最后调用class32的showMsg方法

class MathScore():
    def __init__(self, className, scoreList):
        self.className = className
        self.scoreList = scoreList
        self.studentNums = len(self.scoreList)

    def getSum(self):
        # 计算成绩总和
        s = 0
        for score in self.scoreList:
            # 注:s += score 为 s = s + score 的简写
            s += score
        return s

    def mean(self):
        # 计算成绩平均数
        return self.getSum()/self.studentNums

    def showMsg(self):
        print(f"{self.className}共有{self.studentNums}人,数学平均成绩为{self.mean()}")

# 实例化一个MathScore对象存储到class32 中,并传递参数"三年级二班", [78, 99, 88, 87, 67]
class32 = MathScore("三年级二班", [78, 99, 88, 87, 67])
# 调用class32的showMsg方法
class32.showMsg()

在 Python 中,我们可以使用【def 函数名(参数):】的方式定义函数。

如果我们把 def 定义的函数看作“有名字函数”,那么还有一类函数,其不需要显示定义函数名。

这就是匿名函数。

匿名函数

匿名函数是一种不需要为函数命名的函数定义方式,以 lambda 关键字开头。

匿名函数之所以叫匿名函数,是因为它没有函数名称。
本例中,我们是把一个匿名函数赋值给了变量 square,该变量就可以调用函数。

匿名函数的结构

square = lambda x:x*x
result = square(9)
print(result)

multiply = lambda x,y:x*y
newResult = multiply(9,10)
print(newResult)

代码的作用
这两段代码创建两个匿名函数,并且使用变量调用,传入参数,输出了结果。

lambda
一个关键字 lambda,用来创建匿名函数。

空格
一个空格,固定格式,关键字 lambda 和具体计算式之间的空格。

参数
这个位置表示该匿名函数的参数,为形参。
如果传入多个参数,需要使用“逗号”隔开。

冒号:
一个冒号是“匿名函数”的固定格式,冒号后面是具体如何计算冒号前面的输入数值。

表达式
冒号后面是函数的返回值,注意这里不需使用 return 关键字。

变量
将匿名函数赋给一个变量,再由该变量来调用函数。

调用函数
匿名函数同样需要被调用才能执行,调用仍然使用 ( ) 传参的方式实现。

变量
变量,这个变量会“接受”匿名函数返回出来的值。

代码小结
当要定义匿名函数时,可以使用这样的格式

小练习


定义一个“匿名函数”,它的功能是计算一个输入值的3次幂,将匿名函数赋值给变量power,然后输入一个数值 5,并输出计算结果。

power = lambda number:number*number*number
print(power(5))

立方体体积
定义一个“匿名函数”,它的功能是计算立方体的体积,其公式为:长高,将匿名函数赋值给变量 area。

然后传入参数10, 8, 6,并输出计算结果。

area = lambda length,width,height:length*width*height

print(area(10, 8, 6))

匿名函数使用起来比较方便,但是它也有局限性。
lambda 的主体是一个表达式,而不是一个代码块,不适合处理复杂的逻辑情况。
我们在创建函数时,如果需要处理较为复杂的逻辑,建议使用 def 创建函数,如本例所示,计算列表各元素的累加。

在之前的学习中,都是先定义函数以后,在代码的其他地方调用这个函数。
其实,在函数的内部还可以调用自己,这种调用自己函数就是递归函数

递归

一段程序调用自身的过程我们叫做递归,多见于函数调用函数自身。

当两面镜子相互之间近似平行时,镜中嵌套的图像是以无限递归的形式出现的。也可以理解为自我复制的过程

语言例子
如果我们用语言的形式表示递归的话,这个通俗的故事最能体现:

从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?“从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?‘从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是什么呢?……

接下来,就在案例中,学习递归的使用方法和其中的程序运行逻辑。

递归

递归概念比较抽象,我们来一起梳理下。

假设创建一个递归函数,用于计算数字累加,传入数字 5 ,计算 5 以内的数字累加。
5+4+3+2+1+0

由于 5 大于 0,程序不会执行函数中 if 判断的代码块,而是返回 5 + sum(4)
这里的 sum(4) 又是一次 sum() 函数的调用,程序就会返回 4 + sum(3)

程序就一直调用自己,直到 sum(0),因为 if 语句的存在,它不需要等待 sum(0) 的计算了,而是直接给出结果 0
然后程序一路返回,回到最初的 sum(5),并给出最终的答案:5+4+3+2+1+0 = 15

递归函数会终止么?

在递归函数中,if 判断就是终止条件,在本例中当 n 小于等于 0,就返回 0 ,递归就终止了。
如果不加这个终止条件会无休止的递归下去,最终造成程序报错。

类似的,我们要算一个数的阶乘,一个正整数的阶乘是所有小于及等于该数的正整数的积,例如
5! = 5×4×3×2×1

我们可以用图中的代码,当 n > 0 的时候,函数会调用它本身,直到 n <= 0

小练习

200以内求和
写一个递归函数可以实现 200 以内的整数和。

将函数名命名为 sum,传入参数为 n。

# 定义名为sum()函数,传入参数n
def sum(n):
    # 如果n小于等于0
    if n <= 0:
        # 返回0
        return 0
    # 返回n加上sum(n-1)
    return n + sum(n-1)

# 调用sum()传入200并输出
print(sum(200))

必选参数

函数的一种参数类型,在调用这个函数的时候必须传入数据的参数。

传递实参时的顺序

在传递必选参数时,形参会按照定义的顺序依次接收数据。

调用函数时第一个参数传递给name,第二个参数传递给pet。

缺少实参

在调用函数时,缺少或超出对必选参数的传递会导致程序错误。

也就是说,在函数中定义了多少个必选参数就要传递多少个实参。

关键字传递

当我们忘记了传递顺序时,可以利用“形参名=实参名”的方式传递实参。

这样以关键字传递实参的形式,简称为关键字实参。

小练习

定义一个函数getArea,设定参数width与height分别接收矩形的宽高,计算矩形的面积并返回。

调用该函数,用关键字的形式传递参数宽为5、高为3,并按照“矩形的面积为xx”的格式输出结果。

def getArea(width, height):
    return width*height

ret = getArea(width=5, height=3)
print(f"矩形的面积为{ret}")

若定义一个计算圆形面积的函数,设定参数为半径(radius)与圆周率(pi)。

多数情况下,圆周率都会近似为3.14。

若pi不是必选参数,设其默认为3.14。只在需要修改圆周率时传递数据,函数调用起来会方便很多。

必选参数定义起来虽然简单,在调用函数时经常会因为缺少实参造成程序错误。

接下来学习给形参设置默认值。

默认值参数

def getArea(radius, pi=3.14):
    ret = radius * radius * pi
    return ret

ret1 = getArea(2)
print(ret1)
ret2 = getArea(2, 3.14159)
print(ret2)

代码的作用

定义一个函数getArea,根据传递的半径与圆周率,计算圆的面积并输出。

在第1行,定义形参radius与pi,并为pi设定默认值为3.14。

在第5行,调用函数getArea,并传递数据2。

在第7行,调用函数getArea,并传递数据2与3.14159。

关键字

一个关键字,def函数定义的关键字,是define的缩写。

括号
括号与冒号,这是函数定义的固定格式。

逗号
一个逗号,用来分割多个参数。

必选参数
一串字母radius,必选参数。

必选参数要定义在默认值参数之前。

默认参数
一串字母pi,默认值参数。

默认参数可以有任意个,但必须定义在必选参数之后(若必选参数存在)。

等号
一个等号,赋值符号。

默认值
一组数据,为参数pi设置默认值。

若在调用函数时,没有为参数pi传递实参,参数pi默认为3.14。

传递一个实参
调用函数,只为必选参数传递整数2。

因为pi没有接收到参数,所以pi将使用默认值3.14。

传递两个实参
调用函数,传递参数2与3.14159。

此时pi接收到的实参数据3.14159会覆盖默认值3.14。

代码小结
当我们定义一个默认值参数时就需要这几个部分

目前学习了两种类型的参数,必选参数与默认值参数。这两种参数形式涵盖了80%以上的函数使用。
在定义不同的参数时,需要注意避免一些常见的错误。

1.定义参数的顺序

在定义函数时,定义形参的顺序是:1,必选参数;2,默认参数。
当有必选参数和默认参数的时候,必选参数必须写在默认参数的前面,否则程序会出错

2.参数名避免使用无意义的字母

在传递实参给函数时,若忘记定义顺序可以使用“形参名=实参”的方式。
但如果定义的形参名是诸如a,abc这样无意义的名字,会减少代码可读性,容易混淆。
所以设定参数名时,尽量使用有意义的名称。

最后,对这两种参数的差异性做一个总结。