Python基础 -- 函数

目录

  • 函数的定义
  • 函数的参数
    • 参数的类型
      • 位置参数
      • 关键字参数
      • 默认参数
    • 不可变和可变的参数
      • 面试题 —— `+=`
    • 多值参数
      • 定义支持多值参数的函数
      • 多值参数案例 —— 计算任意多个数字的和
      • 元组和字典的拆包(知道)
  • 函数的返回值
    • 函数的多个返回值
  • 函数的命名
  • 函数的变量作用域
    • global关键字
  • 函数的嵌套调用
      • 函数嵌套的演练 —— 打印分隔线
  • 常见的报错处理

函数的定义

函数是一段组织好的可重复使用的用来实现特定功能的代码块。
为了更好地理解函数,我们可以模拟用榨汁机做一杯美味的猕猴桃汁:

print('将猕猴桃去皮')
print('将猕猴桃切块')
print('将切碎的猕猴桃放进榨汁机')
print('将果汁倒进杯子中')
print('制作完成!')

如果我们这样模拟此过程的话,无疑非常的麻烦,我们后续只要想再做一杯猕猴桃汁我们又要打五行代码。这里我们可以充分的发挥的函数的作用,将我们制作猕猴汁的过程封装进一个函数中。你也可以简单的理解为我们把某件事情的步骤教给 Python,并用一句“暗号”来指代要让 Python 做的事情,以后只要对 Python 说出这个“暗号”,Python 就能把整个事情做完。

def make_juice():
  print('将猕猴桃去皮')
  print('将猕猴桃切块')
  print('将切碎的猕猴桃放进榨汁机')
  print('将果汁倒进杯子中')
  print('制作完成!')

上面的代码,第一行使用了 def make_juice(): 这样的格式,def 是 define(中文含义:定义)的缩写,表示你要向 Python 传授做事流程了,make_juice 是你定义的流程暗号名。在这一行的末尾,一定要加上一对 英文括号 和一个 英文冒号

从第二行开始,我们要开始写制作猕猴桃汁的代码了。我们发现,它们的前面统一开头空了两格,也就是我们在第三关说到的 缩进,这是用来表示:这几行代码属于内部确定的做事流程。
Python基础 -- 函数_第1张图片
这就是完整的教 Python 做事的代码结构,要注意以下几点:

  • 用 def 引导出要定义的“暗号”,后面加上一对括号和冒号作结尾;
  • 具体的做事代码直接写在下面,前面统一加缩进。

这样,Python 就记住了, make_juice 是它学会的“新暗号”,专门用来制作猕猴桃汁。以后,我们只要使用 暗号名() 的格式,就能让 Python 进行我们刚刚教会它的一系列操作

注意:Python 的阅读顺序和我们人类是一样的,都是从上向下,一行一行地读。所以我们应该注意定义函数一定要在使用函数之前。

前面教 Python 做事,并设定好“暗号”的过程,叫作 定义函数。其中缩进的几行表示具体处理过程的代码,叫 函数体。而后面让 Python 真正根据暗号做事的过程,叫作 调用函数,我们可以让 Python 执行无数次暗号,也就是说,函数一旦定义,就可以任我们随意调用。

函数的参数

有了 参数,函数就更加灵活而强大了。定义函数时,我们只要专心总结出固定的操作流程,至于具体根据场景而变化的元素,只要交给 参数 去统一表示就可以。

函数的参数是可有可无的。并不是一个函数的必需元素。如果有多个参数则要用逗号隔开

参数的类型

根据不同的需求,Python 中的参数按传入方式分为两种:

  • 位置参数
  • 关键字参数
  • 默认参数

位置参数

例如我现在构造一个record 的函数,并将参数传入顺序定为:

  1. 姓名
  2. 年龄
  3. 身高
  4. 体重
def record(name, age, height, weight):
  print('欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

# 下面开始调用定义的函数,传入关键信息。
record('野原新之助', 5, 110, 22)
record('风间彻', 5, 112, 21)
# 输出:
# 欢迎小朋友们!
# 姓名: 野原新之助
# 年龄: 5
# 身高: 110 cm
# 体重: 22 kg
# 欢迎小朋友们!
# 姓名: 风间彻
# 年龄: 5
# 身高: 112 cm
# 体重: 21 kg

此时我们调用函数的时候就用到了 位置参数,按位置,依次传入了 name、age、height、weight 四个参数纪录小新和风间的信息。

关键字参数

还是上面的场景我们可以修改一下代码:

def record(name, age, height, weight):
  print('欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

record(age=5, weight=21, name='佐藤正男', height=109)
# 输出:
# 欢迎小朋友们!
# 姓名: 佐藤正男
# 年龄: 5
# 身高: 109 cm
# 体重: 21 kg

上面的代码中,在调用函数时按照 参数名 = 值 的格式传入了参数,这种参数就叫作 关键字参数。

在位置参数中,我们需要记得定义函数时每个参数对应的位置,而关键字参数可以 无视位置。我们可以很轻松地给对应的参数赋值,并且能一眼看出各个参数所对应的含义,在以后检查我们的代码时,也就更加方便了。

位置参数可以和关键字参数一起使用,但要保证所有位置参数都在关键字参数前,否则程序会因为识别错乱而报错。

默认参数

为了减少重复输入,我们引入了默认参数

默认参数一定要放在非默认参数的后面,否则程序会出现错误

例如:

# 我们可以在定义函数时,设置年龄默认为 5
def record(name, height, weight, age=5):
  print('欢迎小朋友们!')
  print('姓名:', name)
  print('年龄:', age)
  print('身高:', height, 'cm')
  print('体重:', weight, 'kg')

# 调用函数时,遇到 5 岁的小朋友,可以不输入年龄
record('阿呆', 115, 25)
# 输出:
# 欢迎小朋友们!
# 姓名: 阿呆
# 年龄 5
# 身高 115 cm
# 体重 25 kg

# 输入妮妮的信息时,要在最后填上她的年龄
record('樱田妮妮', 105, 18, 4)
# 输出:
# 欢迎小朋友们!
# 姓名: 樱田妮妮
# 年龄: 4
# 身高: 105 cm
# 体重: 18 kg

不可变和可变的参数

问题 1:在函数内部,针对参数使用 赋值语句,会不会影响调用函数时传递的 实参变量? —— 不会!

  • 无论传递的参数是 可变 还是 不可变
    • 只要 针对参数 使用 赋值语句,会在 函数内部 修改 局部变量的引用不会影响到 外部变量的引用
def demo(num, num_list):

    print("函数内部")

    # 赋值语句
    num = 200
    num_list = [1, 2, 3]

    print(num)
    print(num_list)

    print("函数代码完成")


gl_num = 99
gl_list = [4, 5, 6]
demo(gl_num, gl_list)
print(gl_num)
print(gl_list)
    

问题 2:如果传递的参数是 可变类型,在函数内部,使用 方法 修改了数据的内容,同样会影响到外部的数据

def mutable(num_list):

    # num_list = [1, 2, 3]
    num_list.extend([1, 2, 3])
    
    print(num_list)

gl_list = [6, 7, 8]
mutable(gl_list)
print(gl_list)

面试题 —— +=

  • python 中,列表变量调用 += 本质上是在执行列表变量的 extend 方法,不会修改变量的引用
def demo(num, num_list):

    print("函数内部代码")

    # num = num + num
    num += num
    # num_list.extend(num_list) 由于是调用方法,所以不会修改变量的引用
    # 函数执行结束后,外部数据同样会发生变化
    num_list += num_list

    print(num)
    print(num_list)
    print("函数代码完成")


gl_num = 9
gl_list = [1, 2, 3]
demo(gl_num, gl_list)
print(gl_num)
print(gl_list)

多值参数

定义支持多值参数的函数

  • 有时可能需要 一个函数 能够处理的参数 个数 是不确定的,这个时候,就可以使用 多值参数

  • python 中有 两种 多值参数:

    • 参数名前增加 一个 * 可以接收 元组
    • 参数名前增加 两个 * 可以接收 字典
  • 一般在给多值参数命名时,习惯使用以下两个名字

    • *args —— 存放 元组 参数,前面有一个 *
    • **kwargs —— 存放 字典 参数,前面有两个 *
  • argsarguments 的缩写,有变量的含义

  • kwkeyword 的缩写,kwargs 可以记忆 键值对参数

def demo(num, *args, **kwargs):

    print(num)
    print(args)
    print(kwargs)


demo(1, 2, 3, 4, 5, name="小明", age=18, gender=True)

提示:多值参数 的应用会经常出现在网络上一些大牛开发的框架中,知道多值参数,有利于我们能够读懂大牛的代码

多值参数案例 —— 计算任意多个数字的和

需求

  1. 定义一个函数 sum_numbers,可以接收的 任意多个整数
  2. 功能要求:将传递的 所有数字累加 并且返回累加结果
def sum_numbers(*args):

    num = 0
    # 遍历 args 元组顺序求和
    for n in args:
        num += n

    return num

print(sum_numbers(1, 2, 3))

元组和字典的拆包(知道)

  • 在调用带有多值参数的函数时,如果希望:
    • 将一个 元组变量,直接传递给 args
    • 将一个 字典变量,直接传递给 kwargs
  • 就可以使用 拆包,简化参数的传递,拆包 的方式是:
    • 元组变量前,增加 一个 *
    • 字典变量前,增加 两个 *
def demo(*args, **kwargs):

    print(args)
    print(kwargs)


# 需要将一个元组变量/字典变量传递给函数对应的参数
gl_nums = (1, 2, 3)
gl_xiaoming = {"name": "小明", "age": 18}

# 会把 num_tuple 和 xiaoming 作为元组传递个 args
# demo(gl_nums, gl_xiaoming)
demo(*gl_nums, **gl_xiaoming)

函数的返回值

return语句
函数体中, return c 的意思是,将 c 指定为唯一可以和外界交流的数据,外面的代码想要获取内部运行结果,就丢个 c 的值给他。至于外面如何处置,函数就管不着了。我们可以理解为return的作用就是函数内部与外部世界的“信使”,负责将函数内部的值传递给外面。
被 return 语句传到函数外的值,叫作函数的 返回值。返回值 要么被外部代码直接打印,要么赋值给变量,要么参与运算等。

return 语句有一个特别要注意的点,return 语句是函数执行结束的标记,运行完 return 语句,就会退出函数的执行,不管后面还有没有其它代码。

函数的多个返回值

和函数的参数一样,函数也支持 0 到多个返回值。
多个返回值也和多参数一样,放在关键词 return 后面并列,然后用英文逗号隔开。
例如:

def new_kids(kid1, kid2, kid3, kid4):
  # 多个值在 return 后并列,用英文逗号间隔开
  return kid1, kid2, kid3, kid4

print(new_kids('野原新之助', '风间彻', '佐藤正男', '樱田妮妮'))
# 输出:('野原新之助', '风间彻', '佐藤正男', '樱田妮妮')

我们将多个小朋友的姓名在 return 后并列了出来,最后函数的返回值是 (‘野原新之助’, ‘风间彻’, ‘佐藤正男’, ‘樱田妮妮’)。

由此可知函数若有多个返回值则会以元组的方式返回!

函数的命名

函数的命名和变量的命名是一样的,由数字、字母、下划线组成,并且不可以用数字作为名称的开头。另外,名称本身要表达自己的功能,要简单易读,并且中间不能有空格。如果有多个单词,要用下划线 _ 连接。

函数的变量作用域

在 Python 中变量分为两种:

  • 局部变量
  • 全局变量

全局变量 在当前写的代码中一直起作用。全局变量 有效的范围,叫作 全局作用域。
局部变量 只在一定范围内有效,在范围外就无效了。
在函数中定义的变量都是 局部变量 。局部变量 有效的范围,叫作 局部作用域。要和外界交流,除非将某个变量用 return 语句变成函数的返回值。
由于 全局变量 作用于整个环境,所以就算跑到自成一体的函数内部,也是有效的。而函数内部的 局部变量 出了函数外,离开自己的 局部作用域,就无效了。

下面,我们再来思考一个问题,全局变量名和局部变量名可以相同吗?
答案是可以。如果局部变量和全局变量名称相同,那函数内部会优先访问局部变量,外部只会访问全局变量(外部是不会去访问局部变量的,当然他也访问不到)。有了函数这堵“围墙”的保护,两种变量被完美地隔开,互不干扰,在各自的作用域内生效。

global关键字

我们可以在函数内部的局部变量前加上global关键字,我们就可以将局部变量转化为全局变量。加了 global 关键字后,相当于我们宣布,函数里的东西大家可以一起用。上面例子中,class_name 就成了全局变量,函数外也能使用,便不会再报错了。

使用global的情景:

  • 在函数外想要使用函数内的变量(全局作用域使用局部变量)。
  • 在函数内修改函数外的变量(局部作用域修改全局变量)。

函数的嵌套调用

  • 一个函数里面 又调用另外一个函数,这就是 函数嵌套调用
  • 如果函数 test2 中,调用了另外一个函数 test1
    • 那么执行到调用 test1 函数时,会先把函数 test1 中的任务都执行完
    • 才会回到 test2 中调用函数 test1 的位置,继续执行后续的代码
def test1():

    print("*" * 50)
    print("test 1")
    print("*" * 50)


def test2():

    print("-" * 50)
    print("test 2")
    
    test1()
    
    print("-" * 50)

test2()

函数嵌套的演练 —— 打印分隔线

体会一下工作中 需求是多变

需求 1

  • 定义一个 print_line 函数能够打印 * 组成的 一条分隔线
def print_line(char):

    print("*" * 50)

需求 2

  • 定义一个函数能够打印 由任意字符组成 的分隔线
def print_line(char):

    print(char * 50)
    

需求 3

  • 定义一个函数能够打印 任意重复次数 的分隔线
def print_line(char, times):

    print(char * times)

需求 4

  • 定义一个函数能够打印 5 行 的分隔线,分隔线要求符合需求 3

提示:工作中针对需求的变化,应该冷静思考,不要轻易修改之前已经完成的,能够正常执行的函数

def print_line(char, times):

    print(char * times)


def print_lines(char, times):

    row = 0
    
    while row < 5:
        print_line(char, times)

        row += 1

常见的报错处理

# 缺少英文括号后的英文冒号
def func1()
  print('Hello World')
# 报错:SyntaxError: bad input on line 1
# (语法错误:第 1 行输入有问题)

yntaxError 的意思是“语法错误”,而冒号后面的是语法错误的细节。这个例子中的具体细节为 bad input,一般来说就是你输入的代码不符合格式,可能多打或少打了一些必要的字母或者标点。最后的 on line xxx 意思是在哪一行发现的错误,这也有助于你直接去定位。

# 输入了中文标点(本例中输入了中文冒号)
def func2():
  print('Hello World')
# 报错:SyntaxError: invalid character in identifier
# (语法错误:识别不了不支持的字符)

这里的语法错误细节是 invalid character,也就是 Python 语法中不支持的字符。除字符串和注释外,Python 语法里支持的字符是不支持汉字和中文标点的。
这里的例子中输入了中文的冒号,Python 当然识别不了啦。所以一定要注意,见到 invalid character 就要看看是不是把括号、冒号、引号等输入错啦,平时写代码时,我们也要预先把输入法调好。

# y = 2 缩进错误
def func3():
  x = 1
 y = 2
# 报错:IndentationError: unindent does not match any outer indentation level
# (缩进错误:(y = 2 这一行)缩进变小了,但是其它地方匹配不到和他一样的缩进层级)

IndentationError 指“缩进错误”。“indentation” 和“indent” 表示缩进,“unindent” 表示“减少或取消缩进”。我们在第五关中说过,Python 非常注重缩进的层次,一旦确定了缩进层次,后面就要统一。一开始用两个或者四个空格作为一个缩进,后面也需要一直保持一样的标准,否则机器就会识别错乱。
比如这个例子中,第 2 行 x = 1 用两个空格表示一个缩进,但第 3 行 y = 2 中变成了一个空格缩进,发生了报错语句所说的“unindent”,但在其它代码行中,找不到同样是 1 个空格表示缩进的地方,所以报错了。

# 默认参数应该放在位置参数后
def func4(x, y = 1, z):
  print(x, y, z)
# 报错:SyntaxError: non-default argument follows default argument
# (语法错误:非默认参数被放到了默认参数后面)

这里的语法错误细节中的关键词是“argument”,意思是“参数”。“default argument” 表示“默认参数”。当你看到“argument”,就要去检查函数定义过程中,参数有没有出错啦。

# 全局作用域不能访问局部变量
def func5():
  x = 1

print(x)
# 报错:NameError: name 'x' is not defined
# (变量名错误:变量名 'x' 没有被定义)

局部变量被函数这堵“围墙”隔得严严实实。在函数外,不用 global 语句,是无法访问函数内的局部变量的。

你可能感兴趣的:(Python,python,开发语言,后端)