Bootstrap

从0开始学Python-day8

Python函数

1. 定义一个函数

可以重复执行、可以重复调用的语句块

用于封装语句块, 提高代码的重用性。

函数是面向过程编程的最小单位

1.1 定义函数:def 语句

  • 语法

    def 函数名(形式参数列表):
        语句块
  • 说明

    1. 函数名是一个变量,不要轻易对其赋值

    2. 函数有自己的名字空间,在函数外部不可以访问函数内部的变量,在函数内部可以访问函数外部的变量,但不能轻易对其改变

    3. 函数的形参列表如果不需要传入参数,形式参数列表可以为空

    4. 任何传入参数和自变量必须放在圆括号中间,圆括号之间可以用于定义参数。

    5. 函数内容以冒号 : 起始,并且缩进。

    6. return [表达式] 结束函数,选择性地返回一个值给调用方,不带表达式的 return 相当于返回 None。

  • 示例

    # 定义一个函数,用 say_hello 变量绑定
    def say_hello():
        print("hello world!")
        print("hello tarena!")
        print("hello everyone!")
        
    # 定义一个函数,传入两个参数,让这个函数把最大的值打印到终端
    def mymax(a, b):
        if a > b:
            print("最大值是", a)
        else:
            print("最大值是", b)
#函数创建
#无参数函数的创建
def attack():
    print("hello world")
​
attack()
#有参数函数的创建
def attack2(count):
    for i in range(count):
        print(i)
​
attack2(3)

1.2 函数的调用

  • 语法

    函数名(实际调用传递参数)
  • 说明

    • 函数调用是一个表达式

    • 如果函数内没有return 语句,函数执行完毕后返回 None 对象

  • 示例

    # 调用
    say_hello()  # 调用一次
    say_hello()  # 调用第二次
    ​
    # 调用
    mymax(100, 200)
    mymax(999, 1)
    mymax('abc', 'cba')
    ​

    1.3 return 语句

  • 语法

    return [表达式]

    注: [] 代表 内部的内容可以省略

  • 作用

    用于函数的内部,结束当前函数的执行,返回到调用此函数的地方,同时返回一个对象的引用关系

  • 说明

    1. return 语句后面的表达式可以省略,省略后相当于 return None

    2. 如果函数内部没有 return 语句, 则函数执行完毕后返回None, 相当于在最后一条语句后有一条return None

  • 示例

    def say_hello():
        print("hello aaa")
        print("hello bbb")
        return 1 + 2
        print("hello ccc")
    ​
    r = say_hello()
    print(r)   # 3
    def fun01():
        print("hello world")
        return 100#后面写条件或者循环语句可以打印
        print(" gh")# 这行不会打印
    ​
    re=fun01()
    print(re)

2. 函数参数

2.1 函数的调用传参

  • 位置传参

    实际参数传递时,实参和形参按位置来依次对应

  • 关键字传参

    实际参数传递时,实参和形参 按名称依次对应

注: 位置传参要先于关键字传参

示例

def myfun1(a, b, c):
    print('a=', a)
    print('b=', b)
    print('c=', c)
​
## 位置传参
myfun1(1, 2, 3)
## 关键字传参
myfun1(c=33, a=11, b=22)
## 位置传参要先于关键字传参
myfun1(111, c=333, b=222)   # 正确
​
# 序列实参:使用星号将序列拆分后,与形参进行对应
list01 = [7,8,9]
myfun01(*list01)
​
# 字典实参:使用双星号将字典拆分后,依次与形参对应
dict01 = {"c":3,"b":2,"a":1}
myfun01(**dict01)

2.2 函数的形式参数定义方法

函数的缺省参数(默认参数)

语法

def 函数名(形参名1=默认实参1, 形参名2=默认实参2, ... ):
     语句块

说明

缺省参数即默认实参,必须自右向左依次存在(即,如果一个参数有缺省参数,则其右侧的所有参数都必须有缺省参数)

示例

def myadd4(a, b, c=0, d=0):    
    print(a)
    print(b)
    print(c)
    print(d)
    return a + b + c + d
print(myadd4(1, 2))
print(myadd4(1, 2, 3))
print(myadd4(1, 2, 3, 4))

错误示例

>>> def myadd(a, b=0, c):   # 报错
...    pass
​
形参的定义
  • 位置形参

  • 星号元组形参(*args)

  • 命名关键字形参

  • 双星号字典形参(**kwargs)

1)位置形参
  • 语法:

    def 函数名(形参名1, 形参名2, ...):
        pass
2)星号元组形参
  • 语法

    def 函数名(*元组形参名):
        pass
  • 作用

    收集多余的位置实参

    元组形参名一般命名为args

  • 示例

    def myfunc2(*args):
        print("len(args)=", len(args))
        print('args=', args)
        
    myfunc2()           # args=()
    myfunc2(1, 2, 3)    # args=(1, 2, 3)
    ​
    def myfunc3(a, b, *args):
        print(a, b, args)
    ​
    myfunc3(1, 2)        # 1-->a, 2-->b, ()--->args
    myfunc3(1, 2, 3, 4)  # # 1-->a, 2-->b, (3, 4)--->args
3)命名关键字形参
  • 语法

    def 函数名(*, 命名关键字形参1, 命名关键字形参2, ...):
        pass
    # 或者
    def 函数名(*args, 命名关键字形参1, 命名关键字形参2, ...):
        pass
  • 作用

    强制,所有的参数都必须用关键字传参

  • 示例

    def myfunc4(a, b,*args, c, d):
        print(a, b, c, d)
    ​
    myfunc4(1, 2, d=4, c=3)   # 正确,c,d 必须关键字传参
    myfunc4(1, 2, 3, 4)   # 错误
4)双星号字典形参
  • 语法

    def 函数名(**kwargs):
        pass
  • 作用

    收集多余的关键字传参

    字典形参名最多有一个,

    字典形参名 一般命名为 kwargs

  • 示例

    def myfunc5(**kwargs):
        print(kwargs)
    ​
    # {'name': 'tarena', 'age': 18}-->kwargs
    myfunc5(name='tarena', age=18) 
    ​

函数的形参定义方法说明

  • 位置形参,星号元组形参,命名关键字参数,双星号字典形参,缺省参数可以混合使用。

  • 函数的形参定义自左至右的顺序为:位置形参,星号元组形参,命名关键字参数,双星号字典形参

示例:

def fn(a, b, *args, c, d, **kwargs):
    print(a)
    print(b)
    print(*args)
    print(c)
    print(d)
    print(kwargs)
​
fn(100, 200, 300, 400, c='C',name='tarena',d='D')
#形式参数-形参
#默认形参-默认参数不能放在前面
def fun01(a,b="NONE",c=0):#def fun01(b="NONE",a,c=0):错误写法
    print(a)
    print(b)
    print(c)
​
fun01(1)
#位置形参
#星号元组形参:将位置实参合并为一个元组
#通用写法:args
def fun01(*args):
    print(*args)
​
#fun01(1,2,3)
​
#命名关键字形参
#强制,所有的参数都必须用关键字传参
def fun04(*args,a,b,c):
    print(args)
    print(a)
    print(b)
    print(c)
​
fun01(1,2,3,a=4,b=5,c=6)
​
​
#双星号字典形参:收集所有的传入数据,打包成字典的形式
#通用写法**kwargs-约定俗成
def fun02(**kwargs):
    print(kwargs)
​
fun02(1,2,3)
​
def fun03(*args,**kwargs)
    print(args)
    print(kwargs)#混合使用
​
fun03(1,2,3,a=1,b=2)

思考:

print() # 函数是如何定义的呢:
def myprint(*args, sep=' ', end='\n'):
    pass
#函数参数
#实际参数(调用参数)-实参
#位置参数
#序列实参
#关键字参数
#字典实参
def fun01(a,b,c):
    print(a)
    print(b)
    print(c)
​
#fun01(1,2,3)#位置传参交换顺序传参也不一样
fun01(a=11,c=33,b=22)#关键字传参交换顺序也可以正确传参
fun01(1,c=4,b=5)#混合传参
#位置传参需要放到关键字传参之前
#fun01(c=2,2,3)
​
#序列实参:用*将序列拆分后,与形参形成对应,传入函数
list01=[1,2,3]
fun01(*list01)
#字典实参:将字典拆分传入参数-键名对应
dict01=dict(a=1,b=2,c=3)
print(dict01)
fun01(**dict01)
def sum4():
    print(a)
    print(b)
a=float(input(""))
a=float(input(""))
a=float(input(""))
a=float(input(""))

2.3 可变不可变

可更改(mutable)与不可更改(immutable)对象

在 python 中,strings, tuples, 和 numbers 是不可更改的对象,而 list,dict 等则是可以修改的对象。

  • 不可变类型:变量赋值 a=5 后再赋值 a=10,这里实际是新生成一个 int 值对象 10,再让 a 指向它,而 5 被丢弃,不是改变 a 的值,相当于新生成了 a。

  • 可变类型:变量赋值 la=[1,2,3,4] 后再赋值 la[2]=5 则是将 list la 的第三个元素值更改,本身la没有动,只是其内部的一部分值被修改了。

python 函数的参数传递:

  • 不可变类型:值传递: 如整数、字符串、元组。如 fun(a),传递的只是 a 的值,没有影响 a 对象本身。如果在 fun(a) 内部修改 a 的值,则是新生成一个 a 的对象。

  • 可变类型:引用传递: 如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后 fun 外部的 la 也会受影响

python 中一切都是对象(后面会讲),严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。

传不可变对象实例

通过内置的 id() 函数来查看内存地址变化:

def change(a):
   print(id(a))
a=10
print(id(a))#1428793408
a=1
print(id(a))#1428793264
​
#总结:可以看见在调用函数前后,形参和实参指向的是同一个对象(对象 id 相同),在函数内部修改形参后,形参指向的是不同的id。

传可变对象实例

可变对象在函数里修改了参数,那么在调用这个函数的函数里,原始的参数也被改变了。

def changeme( mylist ):
   mylist.append(40)
   print (mylist)
​
mylist = [10,20,30]
changeme(mylist)#[10, 20, 30, 40]
print (mylist)#[10, 20, 30, 40]
#总结:传入函数的和在末尾添加新内容的对象用的是同一个引用

3. 返回值

函数可以使用return语句来返回一个或多个值。

如果没有明确的return语句,函数将默认返回None

#没有return
def fn1():
   print("调用了函数")
re=fn1()
print(re)#None
​
#返回一个值
def fn2(x):
   return  x+100
re2=fn2(100)
print(re2)#200
​
#返回多个值
def fn3(x):
   return x*x,x%3
re3=fn3(100)
print(re3)#(10000, 1)
x,y=fn3(100)
print(x,y)#10000 1

4. 匿名函数

在Python中,匿名函数通常使用lambda关键字来创建。匿名函数也被称为lambda函数,它是一种简单的、一行的函数,常用于临时需要一个小函数的地方。匿名函数的语法如下:

  • 语法

    lambda  [函数的参数列表]: 表达式
  • 作用

    • 创建一个匿名函数对象

    • lambda是关键字,表示你正在定义一个匿名函数。

    • 同 def 类似,但不提供函数名

    • [函数的参数列表]是函数的参数,可以有零个或多个参数,参数之间用逗号分隔。

    • : 表达式是函数的返回值,通常是一个表达式,匿名函数会计算这个表达式并返回结果。

  • 说明

    lambda 表达式 的创建函数只能包含一个表达式

  • 示例

    def myadd(x, y):
        return x + y
    ​
    print('1 + 2 =', myadd(1, 2))  # 3
    ​
    # myadd 函数可以改写成 
    myadd2 = lambda x, y: x + y
    print('3 + 4 =', myadd2(3, 4))  # 7
    ​
    square = lambda x: x * x
    print(square(5))  # 输出: 25
    ​
#匿名函数
def myadd(x,y):
    return x+y
​
re=myadd(3,4)
print(re)
​
myadd2=lambda x,y:x+y#匿名函数
​
re=myadd(3,4)
print(re)

5. 变量作用域

5.1 什么是变量作用域

一个变量声明以后,在哪里能够被访问使用,就是这个变量"起作用"的区域:也就是这个变量的作用域

一般来说,变量的作用域,是在函数内部和外部的区域 来体现,因此常常与函数有关

5.2 局部变量和全局变量

  • 局部变量

    • 定义在函数内部的变量称为局部变量(函数的形参也是局部变量)

    • 函数内部的变量只能在函数内部或者函数内部的函数内部访问 ,函数外部不能访问

    • 局部变量在函数调用时才能够被创建,在函数调用之后会自动销毁

  • 全局变量

    • 定义在函数外部,模块内部的变量称为全局变量

    • 全局变量, 所有的函数都可以直接访问(取值,但函数内部不能直接将其赋值改变)

  • 局部变量示例

>>> def fn(a, b):
...     c = 100
...     print(a, b, c)    # a, b, c三个都是局部变量
>>> fn(1, 2)
>>> print(a, b, c)   # 报错, 因为a,b,c 在调用后就销毁了
  • 全局变量示例

a = 100  # 全局变量
def fx(b, c):   # b, c 局部变量
    d = 400     # d 局部变量
    print(a, b, c, d)
    
fx(200, 300)
print(a)  # 100
print(b)  # 报错, 因为此时 b 不存在了
  • 全局变量示例2

a = 100  # 全局变量
def fx(b):
    a = 666  # 创建局部变量,不是改变全局变量
    c = 300
    print(a, b, c)  # 优先访问局部变量
    
fx(200)  # 666 200 300
print(a)  # 100
​
#函数变量的作用域
​
#全局变量-访问顺序LEGB
a=100
def fun01():
    a=20
    def fun02():
    #局部作用域
    a=10
    print()
​
   fun02()
​
fun01()

5.3 局部作用域修改全局变量

  • 问题

## 如何用一个变量来记录一个函数调用的次数
count = 0
​
def hello(name):
    print('hello', name)
    count += 1  #  等同于 count = count + 1  # 如何让 此语句能改变全局变量而不是创建局部变量
​
hello('小张')
hello('小李')
​
print('您共调用hello函数', count, '次')  # 2 次
  • 作用

    告诉python 的解释执行器, global 语句声明的一个或多个变量, 这些变量是全局变量

  • 语法

    global 变量名1, 变量名2, ....#不可变类型
  • 示例

    # 如何用一个变量来记录一个函数调用的次数
    count = 0
    ​
    def hello(name):
        global count  # 声明 global 是全局变量
        print('hello', name)
        count += 1  #  等同于 count = count + 1
    ​
    hello('小张')
    hello('小李')
    hello('小魏')
    ​
    print('您共调用hello函数', count, '次')  # 3 次
  • global 说明

    • 全局变量如果要在函数内部被赋值,则必须经过全局声明 global

    • 默认全局变量在函数内部可以使用,但只能取值,不能赋值

    • 不能先声明为局部变量,再用 global 声明为全局变量,此做法不符合语法规则

  • 函数的形参已经是局部变量,不能用 global 声明为全局变量

错误示例

a = 100
b = 200
def fa(a):
    b = 20  # SyntaxError: name 'b' is assigned to before global declaration
    global b
    b = 222

5.4 局部作用域修改外部变量

在函数内部 提前用nonlocal声明 函数内部的某个变量为外部的变量

前提:必须是函数嵌套

说明:

(1)变量的查找顺序还是遵从:LEGB顺序

在 Python 中,LEGB 代表四种作用域的查找顺序:Local、Enclosing、Global 和 Built-in。

(2)局部作用域中若要修改外部函数嵌套作用域中的变量需要使用:nonlocal 语句

  • 格式:nonlocal 变量名

  • 作用:将局部作用域中变量声明为外部函数嵌套作用域中的变量。

LEGB顺序

Local (L):

  • 本地作用域,指当前函数内部的变量。

  • 当你在函数内部定义变量并尝试访问它时,Python 首先会在函数内部查找这个变量。

def outer_function():
    x = 10  # Local variable
    print(x)  # 查找顺序从 Local 开始

Enclosing (E):

  • 闭包函数外的函数作用域,指嵌套函数的外部函数中定义的变量。

  • 如果在当前函数内部找不到变量,Python 会查找外层(闭包)函数中的变量。

def outer_function():
    x = 10  # Enclosing variable
    def inner_function():
        print(x)  # 查找顺序从 Enclosing 开始
    inner_function()

Global (G):

  • 全局作用域,指模块级别定义的变量。

  • 如果在本地和闭包函数中找不到变量,Python 会查找全局作用域的变量。

x = 20  # Global variable
def outer_function():
    print(x)  # 查找顺序从 Global 开始

Built-in (B):

  • 内建作用域,指 Python 预定义的变量、函数等,如 lensum 等。

  • 如果在以上三个作用域中都找不到变量,Python 会查找内建作用域。

def outer_function():
    print(len)  # 查找顺序从 Built-in 开始
nonlocal
"""
    外部嵌套作用域
"""
​
def func01():
    a = 10
    
    def func02():
        # 内部函数,可以访问外部嵌套变量
        # print(a)
        # 内部函数,如果修改外部嵌套变量,需要使用nonlocal语句声明
        nonlocal a
        a = 20
    func02()
    print(a)
    
func01()
#在函数内部修改全局变量
def fun03():
    global a
    a=200
    print(a)
​
fun03()
print(a)
​
#修改嵌套函数的变量
def fun01():
    a=20
    def fun02():
    #局部作用域
    nonlocal a
    a=100
​
   fun02()
   print(a)

6. 函数的内存分配

1、将函数的代码存储到代码区,函数体中的代码不执行。

2、调用函数时,在内存中开辟空间(栈帧),存储函数内部定义的变量。

3、函数调用后,栈帧立即被释放。

def func(a, b):
 a = 20
 b[0] = 20
a = 10
b = [100]
func(a, b)
print(a) # 10
print(b) # 20
​

(1) 不可变类型参数有:

  • 数值型(整数,浮点数)

  • 布尔值bool

  • None 空值

  • 字符串str

  • 元组tuple

(2) 可变类型参数有:

  • 列表 list

  • 字典 dict

  • 集合 set

(3) 传参说明:

  • 不可变类型的数据传参时,函数内部不会改变原数据的值。

  • 可变类型的数据传参时,函数内部可以改变原数据。

#函数内存分配
#传入的是不可变类型,那么函数内部变量是不能直接改变的
#传入的是可变的类型,函数内部可以直接修改
def fun01(data1,data2):
    data1=10
    data2[0]=100
​
a=20
b=[1,2,3]
fun01(a,b)
​
print(a)
print(b)#20 [100,2,3]

7. 函数自调用(递归)

  • 函数直接或间接的调用自身

讲故事:

  • 从前有座山,山上有座庙,庙里有个老和尚讲故事

    • 从前有座山,山上有座庙,庙里有个老和尚讲故事

      • 从前有座山,山上有座庙,庙里有个老和尚讲故事

示例:

# 函数直接的调用自身
def f():
    f()  # 调用自己
    # 此递归达最大深度出错
# f()
print("递归完成")
​
# 函数直接的调用自身
def fa():
    fb()
def fb():
    fa()
fa()
print("递归完成")

说明:

  • 递归一定要控制递归的层数,当符合某一条件时要终止递归调用

  • 几乎所有的递归都能用while循环来代替

递归的实现方法:

  • 先假设此函数已经实现

递归优缺点:

  • 优点:

    • 递归可以把问题简单化,让思路更为清晰,代码更简洁

  • 缺点:

    • 递归因系统环境影响大,当递归深度太大时,可能会得到不可预知的结果

限制层数递归示例:

递归函数示例 计算阶乘 factorial 迭代实现

def factorial(n):
    result = 1
    i = 1
    while i <= n:
        result *= i
        i+=1
    return result
​
print("factorial: ", factorial(5))

递归实现示例

    # file: factorial.py
    def factorial(n):
      if n == 1:
        return 1
      s = n * factorial(n-1)
      return s
    
    print("factorial: ", factorial(5))
#递归思想
#计算阶乘
#非递归的思想
def fun01(n):
    result=1
    i=1
    while<=n:
        result*=i
        i+=1
    return result
​
re=fun01(10)
print(re)
​
#递归的思想:递归边界 自调用 调用本身的函数
def fun01(n):
    if n==1:#递归边界 返回条件是什么
        return 1
    #调用自己
    result=n*fun01(n-1)
    return result
​
re=fun02(10)
print(re)

;