Python函数相关知识
1. 函数的概念
- 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段。
- 函数能提高应用的模块性,和代码的重复利用率。Python提供了许多内建函数,比如print()。但也可以自己创建函数,这被叫做用户自定义函数。
1.1 定义一个函数
定义一个由自己想要功能的函数,以下是简单的规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号**()**。
- 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None。
- 注意:函数名必须符合标识符的规范(可以包含字母、数字、下划线但是不能以数字开头)
# 定义一个函数
# 函数中保存的代码不会立即执行,需要调用函数代码才会执行
# 语法
# def functionname( parameters ):
# "函数_文档字符串"
# function_suite
# return [expression]
def test():
print("My name is liuyu!")
return
test() # test()调用函数
1.2 函数调用
定义一个函数只给了函数一个名称,指定了函数里包含的参数,和代码块结构。
这个函数的基本结构完成以后,你可以通过另一个函数调用执行,也可以直接从Python提示符执行。
如下实例调用了test()函数:
def test(a):
print(a)
return
test("My name ls liuyu!") # test()调用函数,把实参"My name ls liuyu!"传递给形参a进行打印
2. 函数的参数
2.1 形参和实参
- 形参(形式参数) 定义形参就相当于在函数内部声明了变量,但是并不是赋值
- 实参(实际参数)指定了形参,那么在调用函数时必须传递实参,实参将会赋值给对应的形参,简单来说有几个形参就要有几个实参。
# 定义一个函数 来求任意2个数的和
# 函数的参数
# 在定义函数的时候,可以在函数后面的()中定义数量不等的形参。多个形参之间使用,隔开
def s(a,b):
# a = None
# b = None
# print(a+b)
# print('a =',a)
# print('b =',b)
print(a,'+',b,'=',a+b)
s(10,20)
2.2 函数的传递方式
- 定义形参时,可以为形参指定默认值。指定了默认值以后,如果用户传递了参数则默认值不会生效。如果用户没有传递,则默认值就会生效
- 位置参数:位置参数就是将对应位置的实参赋值给对应位置的形参
- 关键字参数 : 关键字参数可以不按照形参定义的顺序去传递,而根据参数名进行传递
- 混合使用位置参数和关键字参数的时候必须将位置参数写到关键字参数前面去
def test(a=5, b=10, c=20):
print('a =', a)
print('b =', b)
print('c =', c)
test()
# 返回值
# a = 5
# b = 10
# c = 20
test(30,40,50) # 一一对应,30传递给a,40传递给b,50传递给c
# 返回值
# a = 30
# b = 40
# c = 50
test(55,c=80) # 位置参数和关键字参数可以混合使用
2.3 参数传递对象
在 python 中,类型属于对象,变量是没有类型的:
可更改(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 函数的参数传递:
- 不可变类型:类似 c++ 的值传递,如 整数、字符串、元组。如fun(a),传递的只是a的值,没有影响a对象本身。比如在 fun(a)内部修改 a 的值,只是修改另一个复制的对象,不会影响 a 本身。
- 可变类型:类似 c++ 的引用传递,如 列表,字典。如 fun(la),则是将 la 真正的传过去,修改后fun外部的la也会受影响
python 中一切都是对象,严格意义我们不能说值传递还是引用传递,我们应该说传不可变对象和传可变对象。
python 传不可变对象实例
def test(a):
a = 10
b=5
test(b)
print(b) # 返回值:实例中有 int 对象 5,指向它的变量是 b,在传递给 test 函数时,按传值的方式赋值了变量 b,a 和 b 都指向了同一个 Int 对象,在 a=10 时,则新生成一个 int 值对象 10,并让 a 指向它。
传可变对象实例
def test(mylist):
mylist.append([1, 2, 3, 4])
print("函数内取值: ", mylist)
return
# 调用changeme函数
mylist = [10, 20, 30]
test(mylist)
print("函数外取值: ", mylist)
# 实例中传入函数的和在末尾添加新内容的对象用的是同一个引用,故输出结果如下:
#函数内取值: [10, 20, 30, [1, 2, 3, 4]]
#函数外取值: [10, 20, 30, [1, 2, 3, 4]]
2.4 不定长参数
- 定义函数时,可以在形参前面加一个*,这样这个形参可以获取到所有的实参,它会将所有的实参保存到一个元组中
# 定义一个函数求任意个数的和
# 在定义函数的时候,可以在形参前面加上一个*,这样这个形参可以获取到所有的形参
# 它会将所有实参保存到一个元组中
def s(*a):
# 定义一个变量 保存结果
result = 0
# 遍历元组,将元组中的元素进行累加
for r in a:
result += r
print(result)
s(1,2,3,12,560) # 返回值:578
- 带*号的形参只能有一个,可以和其他参数配合使用。
def fn(*a, b, c):
print('a =', a, type(a))
print('b =', b, type(b))
print('c =', c, type(c))
fn(1, 2, 3, 4, b=10, c=20)
# 返回值:
# a = (1, 2, 3, 4) <class 'tuple'>
# b = 10 <class 'int'>
# c = 20 <class 'int'>
- *形参只能接受位置参数,不能接受关键字参数。
def fn1(*a, b, c):
print('a =', a, type(a))
print('b =', b, type(b))
print('c =', c, type(c))
fn1(a=1,10,20)
# 返回值:
# C:\ProgramData\Anaconda3\python.exe D:/PyCharm_project/Module/test2.py
# File "D:/PyCharm_project/Module/test2.py", line 5
# fn1(a=1,10,20) ^
# SyntaxError: positional argument follows keyword argument
# 不定长参数不一定都要写在后面,但是注意 带*的参数后面的所有参数,必须以关键字形式来传递
def fn1(a, *b, c):
print('a =', a, type(a))
print('b =', b, type(b))
print('c =', c, type(c))
fn1(1,10,20,30,c=40)
# 返回值:
# a = 1 <class 'int'>
# b = (10, 20, 30) <class 'tuple'>
# c = 40 <class 'int'>
- **形参可以接收其他的关键字参数,它会将这些参数统一保存到字典当中。字典的key就是参数的名字,字典的value就是参数的值
- **形参只有一个,并且必须写在所有参数的后面
def fn3(a,b,**c):
print('a =', a)
print('b =', b)
print('c =', c)
fn3(1,2,e=2,f=1,g=3)
# 返回值:
# a = 1
# b = 2
# c = {'e': 2, 'f': 1, 'g': 3}
def fn3(**a,b,c):
print('a =', a)
print('b =', b)
print('c =', c)
fn3(e=2,f=1,g=3,1,2)
# 返回值:
# File "D:/PyCharm_project/Module/test2.py", line 1
# def fn3(**a,b,c): ^
# SyntaxError: invalid syntax
2.5 参数的解包
- 传递实参时,也可以在序列类型的参数前添加星号,这样它会自动的将序列中元素依次作为参数传递
- 要求序列中的元素的个数必须和形参的个数一致
def fn(a,b,c):
print('a =', a)
print('b =', b)
print('c =', c)
# 创建一个元组
t = (20,30,40)
fn(*t)
返回值:
# a = 20
# b = 30
# c = 40
# 传递实参时候,也可以在序列类型的参数前面添加*号,这样它会自动的将序列三种的元素依次作为参数传递
def fn(a,b,c):
print('a =', a)
print('b =', b)
print('c =', c)
# 创建一个字典
# 可以通过**来对字典进行解包
d = {'a':1,'b':2,'c':3}
fn(**d)
# 返回值:
# a = 1
# b = 2
# c = 3
3. 函数的返回值
- 返回值就是函数执行以后返回的结果
- 通过return来指定函数的返回值
def sum(arg1, arg2):
# 返回2个参数的和.
total = arg1 + arg2
print("函数内 : ", total)
return total
# 调用sum函数
total = sum(10, 20)
# 返回值:
# 函数内 : 30
- return后面可以跟任意对象,返回值甚至可以是一个函数
# return后面可以跟任意对象,甚至可以是一个函数
def fn():
def fn2():
print('hello')
return fn2
r = fn()
print(r)
print(fn())
r()
# 返回值:
# <function fn.<locals>.fn2 at 0x0000022413334378>
# <function fn.<locals>.fn2 at 0x0000022413334400>
# hello
- return语句[表达式]退出函数,选择性地向调用方返回一个表达式。不带参数值的return语句返回None。
# 如果仅仅写一个return,或者不写return,则相当于return None
def fn2():
return
r = fn2()
print(r)
# 返回值:
# None
def fn3():
print('hello')
return
print('123')
r = fn3()
print(r)
# 返回值:
# hello
# None
# 在函数中 return后面的代码都不会执行,return一旦执行函数自动结束
def fn4():
for b in range(5):
if b == 3:
# break
return # 用来结束函数
print(b)
print('循环执行完毕')
fn4()
# 返回值:当b等于3的时候执行return,结束循环
def fn5():
return 100
# fn5和fn5()的区别
# fn5是函数对象
# fn5()是在调用函数
print(fn5)
print(fn5())
# 返回值:
# <function fn5 at 0x000001A5BE4B42F0>
# 100
4. 文档字符串
- help()是Python中内置函数,通过help()函数可以查询Python中函数的用法
- 在定义函数时,可以在函数内部编写文档字符串,文档字符串就是对函数的说明
- 快捷方式:ctrl+鼠标左键点击函数
help(print)
def print(self, *args, sep=' ', end='\n', file=None): # known special case of print
"""
print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
Prints the values to a stream, or to sys.stdout by default.
Optional keyword arguments:
file: a file-like object (stream); defaults to the current sys.stdout.
sep: string inserted between values, default a space.
end: string appended after the last value, default a newline.
flush: whether to forcibly flush the stream.
"""
pass
5. 函数的作用域
- 作用域(scope)
- 作用域指的是变量生效的区域
- 在Python中一共有两种作用域
- 全局作用域
- 全局作用域在程序执行时创建,在程序执行结束时销毁
- 所有函数以外的区域都是全局作用域
- 在全局作用域中定义的变量,都是全局变量,全局变量可以在程序的任意位置进行访问
- 函数作用域
- 函数作用域在函数调用时创建,在调用结束时销毁
- 函数每调用一次就会产生一个新的函数作用域
- 在函数作用域中定义的变量,都是局部变量,它只能在函数内部被访问
b = 50
def fn():
# a定义在了函数的内部,所以它的作用域就是函数内部,函数外部是无法访问的
a = 10
print('函数内部:','a = ',a)
print('函数内部:', 'b = ', b)
fn()
# print('函数外部:','a = ',a) #NameError: name 'a' is not defined
print('函数外部:','b = ',b)
# 返回值:
# 函数内部: a = 10
# 函数内部: b = 50
# 函数外部: b = 50
def fn2():
a = 30
def fn3():
a = 40
print('fn3中','a = ',a)
fn3()
print('fn2中','a = ',a)
fn2()
# 返回值:
# fn3中 a = 40
# fn2中 a = 30
def fn2():
def fn3():
global a
a = 40
print('fn3中','a = ',a)
fn3()
print('fn2中','a = ',a)
fn2()
# 返回值:
# fn3中 a = 40
# fn2中 a = 40
6. 命名空间
- 命名空间实际上就是一个字典,是一个专门用来存储变量的字典
- locals()用来获取当前作用域的命名空间
- 如果在全局作用域中调用locals()则获取全局命名空间,如果在函数作用域中调用locals()则获取函数命名空间
- 返回值是一个字典
# 命名空间实际上就是一个字典 是一个专门用来存储变量的字典
# locals() 用来获取当前作用域的命名空间 返回值是一个字典
a = 20
def fn3():
global a
a = 50
print('函数内部:','a =',a)
s = locals()
fn3()
print(s)
print(a)
print(s['a'])
# 返回值:
# 函数内部: a = 50
# {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x000001AC2ABC6128>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/PyCharm_project/Module/test2.py', '__cached__': None, 'a': 50, 'fn3': <function fn3 at 0x000001AC2AEB42F0>, 's': {...}}
# 50
# 50
def fn4():
global_s = globals()
global_s['a'] = 30
print(global_s)
fn4()
print(a)
返回值:
# {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000284DF6B6128>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'D:/PyCharm_project/Module/test2.py', '__cached__': None, 'fn4': <function fn4 at 0x00000284E14942F0>, 'a': 30}
# 30
7. 递归函数
- 递归是解决问题的一种方式,它的整体思想,是将一个大问题分解为一个个的小问题,直到问题无法分解时,在去解决问题
- 递归式函数有2个条件
- 基线条件 问题可以被分解为最小问题,当满足基线条件时,递归就不执行了
- 递归条件 可以将问题继续分解的条件
# 用递归的方式求任意数的阶乘
# 10!= 10 * 9!
# 9! = 9 * 8!
# 8! = 8 * 7!
# ....
# 1! = 1
def fn2(n):
# 参数 n要求阶乘的数字
# 1.基线条件
if n == 1:
# 1的阶乘就是1 直接返回结果
return 1
# 2.递归条件
return n * fn2(n-1)
print(fn2(10))
# 返回值:3628800
# 创建一个函数来为任意数字做任意幂运算
# 10**6 = 10 * 10**5
# 10**5 = 10 * 10**4
# 10**4 = 10 * 10**3
# 10**3 = 10 * 10**2
# ......
# 10**1 = 10
def fn(n,i):
# 参数 n 要做幂运算的数字 i 要做幂运算的次数
# 1. 基线条件
if i == 1:
return n
# 2. 递归条件
return n * fn(n,i-1)
print(fn(5,5))
print(5**5)
# 返回值:
# 3125
# 3125
# 定义一个函数用来检测一个任意字符是否是回文字符如果是返回True,如果不是返回False
# 回文字符串,字符串从前往后念和从后往前念是一样的
# 例如 abcba
# abcdefgfedcba
# 先检查第一个字符和在最后一个字符是否一致,如果不一致一定不是回文字符串
# 如果一致就要检查剩余的部分是不是回文字符串
# bcdefgfedcb 是不是回文
# cdefgfedc
# defgfed
# efgfe
# fgf
# g
def fn2(s):
# 参数s就是要检查的字符串
# 1.基线条件
# 字符串的长度小于2 则字符串一定是个回文
if len(s)<2:
return True
# # 第一个字符和最后一个字符不相同,则一定不是回文字符串
# elif s[0] != s[-1]:
# return False
# 2.递归条件
return s[0] == s[-1] and fn2(s[1:-1])
print(fn2('abcdefgfedcba'))
# s = 'cgc'
# print(s[1:-1])
8. 练习
汉诺塔游戏,现在有ABC三根柱子。要求:将A柱所有的圆盘放到C柱。在移动的过程中可以借助B柱。并且规定大圆盘不能放小圆盘上面,每次只能移动一个盘子。用递归的方式来解决汉诺塔问题。
# 我们对柱子编号为a, b, c,将所有圆盘从a移到c可以描述为:
# 如果a只有一个圆盘,可以直接移动到c;
# 如果a有N个圆盘,可以看成a有1个圆盘(底盘) + (N-1)个圆盘,首先需要把 (N-1) 个圆盘移动到 b,然后,将 a的最后一个圆盘移动到c,再将b的(N-1)个圆盘移动到c。
def move(n, a, b, c):
# 如果a柱子上面只有一个盘子,则直接移到c柱子上面去并输出路径,结束递归
if n == 1:
print(a, '-->', c)
return
# 表示的是将n-1的盘子从a柱子上面移到b柱子上面去
move(n - 1, a, c, b)
# 输出最下面个盘子移从a移到c的路径
print(a, '-->', c)
# 将b柱子上面的n-1个盘子移动到c柱子上面
move(n - 1, b, a, c)
move(4, 'A', 'B', 'C')
# 返回值:
# A --> B
# A --> C
# B --> C
# A --> B
# C --> A
# C --> B
# A --> B
# A --> C
# B --> C
# B --> A
# C --> A
# B --> C
# A --> B
# A --> C
# B --> C