Python学习笔记:第十站 水晶球不调用不动

Python学习笔记

文章目录

  • Python学习笔记
  • 第十站 水晶球不调用不动
    • 1. 函数的创建和调用
    • 2. 函数的参数传递
    • 3. 函数的返回值
    • 4. 函数的参数定义
    • 5. 变量的作用域
    • 6. 递归函数
    • 7. 本章作业

课程笔记参考B站视频: Python全栈开发教程。


第十站 水晶球不调用不动

这个水晶球就是“函数”。

1. 函数的创建和调用

函数就是执行特定任和以完成特定功能的一段代码。函数的优点包括:复用代码、隐藏实现细节、提高可维护性、提高可读性便于调试等。函数的创建和调用格式如下:

'''函数的创建格式'''
def 函数名([形式参数]):
   函数体
   [return xxx]

'''函数的调用格式'''
函数名([实际参数])

注:要先定义函数,才能进行调用

下面是代码示例:

def cal(a,b):
   c=a+b
   return c

result = cal(10, 20)
print(result)
  • 运行结果
30

2. 函数的参数传递

函数调用的参数传递主要有两种方式:

  1. 按照位置传递实参:根据 形参对应的位置 进行实参传递,顺序不可改变。
  2. 按照关键字传递实参:根据 形参名称 进行实参传递,顺序可以改变。

下面是代码示例:

def cal(a,b):
   c=a+b
   return c

print('位置传递:', cal(10, 20))
print('关键字传递:', cal(b=20,a=100)) # 左侧的变量名称称为关键字参数
  • 运行结果
30
120

下面以一个程序来举例,说明函数调用时参数传递的内存分析图。

def fun(arg1, arg2):
   print('arg1=', id(arg1), arg1)
   print('arg2=', id(arg2), arg2)
   arg1 = 100
   arg2.append(10)
   print('arg1=', id(arg1), arg1)
   print('arg2=', id(arg2), arg2)
   # return

n1 = 11
n2 = [22, 33, 44]
print('调用函数前:', id(n1), n1)
print('调用函数前:', id(n2), n2)
print('---调用函数中----')
fun(n1, n2)
print('----------------')
print('调用函数后:', id(n1), n1)
print('调用函数后:', id(n2), n2)

上面这个程序在运行的过程中,其内存分析图如下:

最终其运行结果为:

调用函数前: 140718152341472 11
调用函数前: 2535859027008 [22, 33, 44]
---调用函数中----
arg1= 140718152341472 11
arg2= 2535859027008 [22, 33, 44]
arg1= 140718152344320 100
arg2= 2535859027008 [22, 33, 44, 10]
----------------
调用函数后: 140718152341472 11
调用函数后: 2535859027008 [22, 33, 44, 10]

即,在函数调用过程中,进行参数的传递:

  1. 如果是不可变对象,在函数体的修改不会影响实参的值。
  2. 如果是可变对象,在函数体的修改会影响实参的值。

3. 函数的返回值

函数的返回值有如下情况:

  1. 若函数没有返回值,return可以省略不写。
  2. 若函数的返回值只有1个,直接返回其类型。
  3. 若函数的返回值有多个,返回的结果为元组。

下面是代码示例:

def fun(num):
   odd = [] # 定义空列表
   even = []
   for i in num:
       if i%2:
           odd.append(i)
       else:
           even.append(i)
   return odd, even

result = fun(range(-1,-10,-1))
print(result)
print('奇数列表为:', result[0])
print('偶数列表为:', result[1])
  • 运行结果
([-1, -3, -5, -7, -9], [-2, -4, -6, -8])
奇数列表为: [-1, -3, -5, -7, -9]
偶数列表为: [-2, -4, -6, -8]

4. 函数的参数定义

函数的参数定义除了可以直接声明变量,还有三种特殊方式:

  1. 默认值参数:函数定义时给形参设置默认值,只有与默认值不符的时候才需要传递实参。
  2. 个数可变的位置参数:适用于无法确定要传递的位置实参的个数时,使用*定义,类型为元组
  3. 个数可变的关键字参数:适用于无法确定要传递的关键字实参的个数时,使用**进行定义,类型为字典

注:对于第二种和第三种参数,只能定义一个。
注:若同时都有定义,第二种必须放在第三种之前(两个星号的必须在前面)。

下面是代码示例:

def fun1(a, b=10):  # 默认值参数
   print(a, b)
   return

def fun2(*args):    # 可变的位置参数
   print(args)

def fun3(**args):   # 可变的位置参数
   print(args)

def fun4(*args1, **args2):      # 不会报错
   pass
# def fun5(**args1, *args2):   # 会报错,SyntaxError: invalid syntax
#     pass

print('---------默认值参数---------')
print('传递1个参数:', fun1(100))
print('传递2个参数:', fun1(100, 20))
print('-----个数可变的位置参数------')
fun2(10)
fun2(10, 20)
fun2(10, 20, 30)
print('-----个数可变的关键字参数-----')
fun3(a=10)
fun3(a=10, b=20)
fun3(a=10, b=20, c=30)
  • 运行结果
---------默认值参数---------
100 10
传递1个参数: None
100 20
传递2个参数: None
-----个数可变的位置参数------
(10,)
(10, 20)
(10, 20, 30)
-----个数可变的关键字参数-----
{'a': 10}
{'a': 10, 'b': 20}
{'a': 10, 'b': 20, 'c': 30}

同时从上面的代码结果中可以看出,一条语句包含函数时,会首先运行函数体,再运行该条语句。

最后总结一下函数的参数类型:

分类 参数的类型 备注
函数的调用 位置实参
将元组、列表、集合的每个元素都转换成位置实参 使用*
关键字实参
将字典的所有键值对都转换成关键字实参 使用**
函数的定义 默认值形参
关键字形参 使用*
个数可变的位置形参 使用*
个数可变的关键字形参 使用**

下面是函数的调用代码示例:

def fun(a, b, c):
   print('a =', a, ' b =', b, ' c =', c)

print('-----函数调用实参的不同方式------')
print('位置实参:', end='')
fun(10, 20, 30)         # 位置实参
print('元组、列表、集合转换成位置实参:', end='')
lst = {40, 50, 60}      # 元组、列表、集合都可以
fun(*lst)               # 调用时将每个元素都转换成 位置实参
print('关键字实参:', end='')
fun(a=70, b=80, c=90)   # 关键字实参
print('字典转换成关键字实参:', end='')
dic = {'a':100, 'b':110, 'c':130}
fun(**dic)              # 调用时将所有键值对都转换成 关键字实参
  • 运行结果
-----函数调用实参的不同方式------
位置实参:a = 10  b = 20  c = 30
元组、列表、集合转换成位置实参:a = 40  b = 50  c = 60
关键字实参:a = 70  b = 80  c = 90
字典转换成关键字实参:a = 100  b = 110  c = 130

下面是函数的定义代码示例:

def fun1(a, b=10):  # a为位置形参,b为默认值形参
   print('a =', a, ' b =', b)

def fun2(*args):    # 个数可变的位置形参
   print(args)

def fun3(**args):   # 个数可变的关键字形参
   print(args)

def fun4(a,b,c):
   print('a =', a, ' b =', b, ' c =', c)

def fun5(a,*,b,c):  # 星号后面都必须使用关键字实参
   print('a =', a, ' b =', b, ' c =', c)

print('-------演示fun1()---------')
print('-------演示fun2()---------')
fun2(10, 20, 30)
print('-------演示fun3()---------')
fun3(a=10, b=20)
print('-------演示fun4()---------')
fun4(10, 20, 30)        # 位置实参
fun4(a=40, b=50, c=60)  # 关键字实参
fun4(40, 50, c=60)      # 前两个位置实参,后一个关键字实参
print('-------演示fun5()---------')
# fun5(40, 50, c=60)   # 会报错说少一个关键字实参
fun5(40, c=50, b=60)
  • 运行结果
-------演示fun1()---------
-------演示fun2()---------
(10, 20, 30)
-------演示fun3()---------
{'a': 10, 'b': 20}
-------演示fun4()---------
a = 10  b = 20  c = 30
a = 40  b = 50  c = 60
a = 40  b = 50  c = 60
-------演示fun5()---------
a = 40  b = 60  c = 50

函数定义参数时的顺序(从前到后):位置形参 > 关键字形参、可变数量位置形参 > 可变数量关键字形参。若有一个单独的星号(表明其后面都是关键字形参),则可变数量位置形参一定要提前到该星号之前。

5. 变量的作用域

变量的作用域指的是程序代码能访问该变量的区域,在作用域之外调用变量会报错。根据变量的有效范围可分为:

  1. 局部变量:在函数内定义并使用的变量,只在函数内部有效,局部变量使用global声明,这个变量
    就会就成全局变量。
  2. 全局变量:函数体外定义的变量,可作用于函数内外。

在下面这段代码中,c称为局部变量,变量a, b作为函数的形参,作用范围也是函数内部,也就是局部变量。于是在函数体外部调用a, b, c就会报错。

def fun(a, b):
   c = a+b  # c称为局部变量
   print(c)

# print(a) # 会报错

在下面这段代码中,name是全局变量,在函数体内部和外部都可以使用,所以会打印两次。(C/C++不是这样的!!)

def fun2():
   print(name)

name = '张三'
print(name)
fun2()
  • 运行结果
张三
张三

下面这段代码中,age本来作为局部变量,只能作用于函数体内部,但其使用 global声明,就成为全局变量,在函数体外部也可以调用。

def fun3():
   global note
   note = '去码头整点薯条'
   print(note)

fun3()
print(note)
  • 运行结果
去码头整点薯条
去码头整点薯条

6. 递归函数

如果在一个函数的函数体内调用了该函数本身,这个函数就称为递归函数。递归(递推、回归)由 递归调用 与 递归终止条件 两部分组成。进行递归时,每递归调用一次函数,都会在栈内存分配一个栈帧,当执行完本次函数,就会释放相应的空间只返回结果,如此循环直到完成整个递归。递归的优缺点如下:

缺点:占用内存多,效率低下。
优点:思路和代码简单。

下面是代码示例:

'''递归计算阶乘'''
def fac(n):
   if(n==1):
       return 1
   else:
       res = n*fac(n-1)
       return res

print('4的阶乘为:', fac(4))
  • 运行结果
4的阶乘为: 24

在调用递归函数的语句打上断点,进入调试模式,然后一直执行步入(step into),便可以在调试(debugger)窗口看到函数fac递推到回归的整个过程。

课堂作业:使用递归完成斐波那契数列的计算。
下面是代码示例:

'''
斐波那契数列:前两项均为1,此后的每一位都是前两项的和。
'''
'''下面这个函数体没有用到递归但是算出来了'''
'''函数体功能:输入序列长度,返回相应长度的斐波那契数列。'''
def fibo_sequ(mindex):
   sequ=[1]
   index = 1
   while index<mindex:
       if (index == 1):
           sequ.append(1)
       else:
           sequ.append(sequ[-1]+sequ[-2])
       index += 1
   return sequ
print(fibo_sequ(6))

'''使用递归,但是只能计算单个元素的值'''
'''函数体功能:使用递归计算单个索引下的斐波那契数列值'''
def fib(n):
   if(n==1):
       return 1
   elif(n==2):
       return 1
   else:
       return fib(n-1)+fib(n-2)
for i in range(1,7,1):
   print(fib(i), end=' ')
  • 运行结果
[1, 1, 2, 3, 5, 8]
1 1 2 3 5 8 

设置输出100个斐波那契数列的值,可以观察到使用递归的方法会非常的慢。并且从代码逻辑上说,显然使用递归会非常的浪费内存。

7. 本章作业

1. Mini计算器

说明:使用函数完成加、减、乘、除运算。

下面是代码示例:

def mini_cal(a, b, operation):
   if operation=='+':
       return a+b
   elif operation=='-':
       return a-b
   elif operation=='*':
       return a*b
   elif operation=='/':
       if b!=0:
           return a/b
       else:
           return '除数不能为0'
   else:
       return '运算符输入错误'
if __name__ == '__main__':
   a = int(input('请输入第一个整数:'))
   b = int(input('请输入第一个整数:'))
   oper = input('请输入运算符:')
   print(mini_cal(a, b, oper))
  • 运行结果
请输入第一个整数:10
请输入第一个整数:0
请输入运算符:/
除数不能为0

2. 猜数游戏

说明:使用函数完成猜数游戏。

下面是代码示例:

import random
def guess(num_rand, num_guess):
   if num_rand>num_guess:
       return '猜小了'
   elif num_rand<num_guess:
       return '猜大了'
   elif num_rand==num_guess:
       return '猜对了'
if __name__=='__main__':
   num_rand = random.randint(0,100)
   for i in range(7,0,-1):
       num_guess = int(input('请输入一个0~100的整数:'))
       res = guess(num_rand,num_guess)
       if res!='猜对了' and i>1:
           print(res + f',你还有 {i-1} 次机会')
       elif res!='猜对了' and i==1:
           print(res + ',机会已经用完了~')
       else:
           print(res)
           break
  • 运行结果
请输入一个0~100的整数:50
猜大了,你还有 6 次机会
请输入一个0~100的整数:25
猜小了,你还有 5 次机会
请输入一个0~100的整数:37
猜大了,你还有 4 次机会
请输入一个0~100的整数:31
猜小了,你还有 3 次机会
请输入一个0~100的整数:34
猜对了

你可能感兴趣的:(Python学习笔记,python,学习,开发语言)