函数
函数
编程中的函数在英文中也有很多不同的叫法。在BASIC中叫做subroutine(子过程或子程序),在Pascal中叫做procedure(过程)和function,在C中只有function,在Java里面叫做method。
定义: 函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数名即可
特性:
- 减少重复代码
- 使程序变的可扩展
- 使程序变得易维护
语法定义
def sayhi():#函数名 print("Hello, I'm Elsa!") sayhi() #调用函数 不加括号,打印的是内存地址
可以带参数
#下面这段代码 a,b = 5,8 c = a**b print(c) #改成用函数写 def calc(x,y): res = x**y return res #返回函数执行结果 c = calc(a,b) #结果赋值给c变量 print(c)
参数可以让你的函数更灵活,不只能做死的动作,还可以根据调用时传参的不同来决定函数内部的执行流程
函数定义完成后,没有通过函数名调用,就不会执行
函数调用,名字加括号
函数参数
形参变量
只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只在函数内部有效。函数调用结束返回主调用函数后则不能再使用该形参变量
实参
可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使参数获得确定值
默认参数
看如下代码
def stu_register(name,age,country,course): print("----注册学生信息------") print("姓名:",name) print("age:",age) print("国籍:",country) print("课程:",course) stu_register("王山炮",22,"CN","python_devops") stu_register("张叫春",21,"CN","linux") stu_register("刘老根",25,"CN","linux")
发现 country 这个参数 基本都 是"CN", 就像我们在网站上注册用户,像国籍这种信息,你不填写,默认就是 中国, 这就是通过默认参数实现的,把country变成默认参数非常简单
def stu_register(name,age,course,country="CN"):
这样,这个参数在调用时不指定,那默认就是CN,指定了的话,就用你指定的值。
把country变成默认参数后,同时把它的位置移到了最后面
关键参数
正常情况下,给函数传参数要按顺序,不想按顺序就可以用关键参数,只需指定参数名即可(指定了参数名的参数就叫关键参数),但记住一个要求就是,关键参数必须放在位置参数(以位置顺序确定对应关系的参数)之后
调用可以这样
stu_register("王山炮",course='PY', age=22,country='JP' )
但绝不可以这样
stu_register("王山炮",course='PY',22,country='JP' )
当然这样也不行
stu_register("王山炮",22,age=25,country='JP' ) # 相当于给age赋值2次,会报错!
非固定参数
若你的函数在定义时不确定用户想传入多少个参数,就可以使用非固定参数
如果参数中出现 *args ,传递的参数就可以不再是固定的个数,传过来的所有参数打包成一个元组
方式一: *args
def stu_register(name,age,*args): # *args 会把多传入的参数变成一个元组形式 print(name,age,args) stu_register("Alex",22)
输出:
Alex 22 () #后面这个()就是args,只是因为没传值,所以为空 stu_register("Jack",32,"CN","Python") #输出 # Jack 32 ('CN', 'Python')
方式二:也可以在传参数的时候,直接在实参前加*,后面以列表或者元组形式。
stu_register("Jack",32,*["CN","Python"]) #
stu_register("Jack",32,*("CN","Python")) 以元组形式
**kwargs
接收非关键字参数(未定义的关键字)
def stu_register(name,age,*args,**kwargs): # *kwargs 会把多传入的参数变成一个dict形式 print(name,age,args,kwargs) stu_register("Alex",22)
输出:
Alex 22 () {}#后面这个{}就是kwargs,只是因为没传值,所以为空 stu_register("Jack",32,"CN","Python",sex="Male",province="ShanDong") #输出 # Jack 32 ('CN', 'Python') {'province': 'ShanDong', 'sex': 'Male'}
返回值
函数外部的代码要想获取函数的执行结果,就可以在函数里用return语句把结果返回
def stu_register(name, age, course='PY' ,country='CN'): print("----注册学生信息------") print("姓名:", name) print("age:", age) print("国籍:", country) print("课程:", course) if age > 22: return False else: return True registriation_status = stu_register("王山炮",22,course="PY全栈开发",country='JP') #返回True or False 给registriation_status
if registriation_status: print("注册成功") else: print("too old to be a student.")
注意
- 函数在执行过程中只要遇到return语句,就会停止执行并返回结果,so 也可以理解为 return 语句代表着函数的结束
- 如果未在函数中指定return,那这个函数的返回值为None
全局与局部变量
name = "小姑娘" def change_name(name): print("before change:",name) name = "小仙女" print("after change", name) change_name(name) print("在外面看看name改了么?",name)
输出
before change: 小姑娘
after change 小仙女
不用传name 值到函数里,也可以在函数里调用外面的变量
name = "小姑娘" def change_name(): name = "小仙女" print("after change", name) change_name() print("在外面看看name改了么?", name)
输出:
after change 小仙女
在外面看看name改了么? 小姑娘
但就是不能改!
- 在函数中定义的变量称为局部变量,在程序的一开始定义的变量称为全局变量。
- 全局变量作用域是整个程序,局部变量作用域是定义该变量的函数。
- 当全局变量与局部变量同名时,在定义局部变量的函数内,局部变量起作用;在其它地方全局变量起作用。
- 在函数内部,可以引用全局变量(函数内部无同名局部变量),但不能修改全局。相当于在局部创建了一个
如果全局个局部有同名变量,函数查找的顺序是由内而外的,优先函数自己内部的。
作用域
作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。
在python中,一个函数就是一个作用域。
所有的局部变量,放置在其作用域中。
定义完成后,作用域已经生成,以后调用,作用域链向上查找 (以后不论函数名传递到哪里,只要函数一执行,还会回来它定义的地方往上找)
一个函数想要执行就是在它名字后面加括号
在函数里修改全局变量
name = "小姑娘" def change_name(): global name name = "小仙女" print("after change", name) change_name() print("在外面看看name改了么?", name)
输出:
after change 小仙女
在外面看看name改了么? 小仙女
global name
的作用就是要在函数里声明全局变量name ,意味着最上面的name = "小姑娘"
即使不写,程序最后面的print也可以打印name
通常一般情况下建议不要改全局变量
对于字典 、列表、集合、对象、类在函数中可以修改。因为不能改的是他们在整体,对于整理里面的个体是可以修改的。元组本身不可以修改,但是里面深的可以修改。(他们本身的内存地址是不能改的,但是内部本身的个体可以改)
不可以修改的字符串、数字、true和false。
嵌套函数
def func1(): print("小仙女") def func2(): print("小姑娘") func1()
输出
小仙女
def func1(): print("小仙女") def func2(): print("小姑娘") func2() func1()
输出
小仙女
小姑娘
函数内部可以再次定义函数
函数执行需要被调用
age = 19 def func1(): age = 73 print(age) def func2(): age = 84 print(age) func2() func1()
输出
73
84
age = 19 def func1(): age = 73 print(age) def func2(): print(age) func2() func1()
输出
73
73
由内向往一层层找变量
age = 19 def func1(): age = 73 def func2(): print(age) func2() func1()
输出
73
age = 19 def func1(): def func2(): print(age) age = 73 func2() func1()
输出
73
age = 73 在func1()局部变量中已经存在,再调用func2时,在func1找到73
age = 19 def func1(): def func2(): print(age) func2() age = 73 func1()
输出
NameError: free variable 'age' referenced before assignment in enclosing scope
func2是往上找19,但func1调用时,有两个值
age = 19 def func1(): global age def func2(): print(age) func2() age = 73 func1() print(age)
输出
19
73
age = 19 def func1(): global age def func2(): print(age) age = 73 func2() func1() print(age)
输出
73
73
匿名函数(lambda)
匿名函数就是不需要显式的指定函数名(匿名函数不需要名字)
匿名函数最多能处理三元运算
def calc(x,y): if x < y: return x*y else: return x/y func = lambda x,y: x*y if x < y else x/y # 声明一个匿名函数,并将此函数赋变量名func print(calc(16,8)) print(func(16,8))
输出
2.0
2.0
实现一个从0~9的列表,使其每个元素自乘。
# 不用匿名函数 data = list(range(10)) print(data) for index,i in enumerate(data): data[index] = i*i print(data)
#or
# 用map函数
# data = list(range(10))
# print(data)
# def func2(n):
# return n*n
# print(list(map(func2, data)))
输出
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 加入lambda函数 data = list(range(10)) print(data) print(list(map(lambda n: n*n, data)))
输出
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
匿名函数主要是和其它函数搭配使用
作用:节省代码量,看着高级
res = map(lambda x:x**2,[1,5,7,4,8])
for i in res:
print(i)
输出
1
25
49
16
64
高阶函数
变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
def add(x,y,f):
return f(x) + f(y)
res = add(3,-6,abs)
print(res)
只需满足以下任意一个条件,即是高阶函数
接受一个或多个函数作为输入
return 返回另外一个函数
递归
在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。
查看递归限值
import sys print(sys.getrecursionlimit())
改递归限值
import sys sys.setrecursionlimit(1500) # 将递归限值改成1500
第一层函数没执行完,就又调用第二层,以此往下,之前调用的函数还没有结束。每调用一次函数,函数里的变量、数据、代码都会占用内存,函数没结束,数据就一直在内存中,无限执行下去,内存就会撑爆。
本质上函数的调用是通过栈实现的
栈有些像手枪的弹夹,一层一层。
栈满会出现栈溢出。
python不存在栈
# 实现数字二分,仅保留整数部分 def calc(n): v = int(n/2) # 仅保留整数部分 print(v) if v == 0: return "done" calc(v) calc(10)
输出
5
2
1
0
1
2
5
递归特性:
必须有一个明确的结束条件(不能无限调用)
每次进入更深一层递归时,问题规模相比上次递归都应有所减少
递归效率不高,递归层次过多会导致栈溢出(在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出)
堆栈扫盲http://www.cnblogs.com/lln7777/archive/2012/03/14/2396164.html
递归用途
大部分用于数学、算法
# 阶乘 n! = 1 * 2 * 3 *......* n or n! =n * (n-1)! def factorial(n): if n == 1: return 1 return n * factorial(n-1) print(factorial(4))
输出
24
递归函数实际应用案例,二分查找
我们首先引入这样一个问题:如果规定某一科目成绩分数范围:[0,100],现在小明知道自己的成绩,他让你猜他的成绩,如果猜的高了或者低了都会告诉你,用最少的次数猜出他的成绩,你会如何设定方案?(排除运气成分和你对小明平时成绩的了解程度)
①最笨的方法当然就是从0开始猜,一直猜到100分,考虑这样来猜的最少次数:1(运气嘎嘎好),100(运气嘎嘎背);
②其实在我们根本不知道对方水平的条件下,我们每一次的猜测都想尽量将不需要猜的部分去除掉,而又对小明不了解,不知道其水平到底如何,那么我们考虑将分数均分,
将分数区间一分为2,我们第一次猜的分数将会是50,当回答是低了的时候,我们将其分数区域从【0,100】确定到【51,100】;当回答高了的时候,我们将分数区域确定到【0,49】。这样一下子就减少了多余的50次猜想(从0数到49)(或者是从51到100)。
③那么我们假设当猜完50分之后答案是低了,那么我们需要在【51,100】分的区间内继续猜小明的分数,同理,我们继续折半,第二次我们将猜75分,当回答是低了的时候,我们将其分数区域从【51,100】确定到【76,100】;当回答高了的时候,我们将分数区域确定到【51,74】。这样一下子就减少了多余的猜想(从51数到74)(或者是从76到100)。
④就此继续下去,直到回复是正确为止,这样考虑显然是最优的
data = [1, 3, 6, 7, 9, 12, 14, 16, 17, 18, 20, 21, 22, 23, 30, 32, 33, 35] def binary_search(dataset,find_num): print(dataset) if len(dataset) >1: mid = int(len(dataset)/2) if dataset[mid] == find_num: #find it print("找到数字",dataset[mid]) elif dataset[mid] > find_num :# 找的数在mid左面 print("\033[31;1m找的数在mid[%s]左面\033[0m" % dataset[mid]) return binary_search(dataset[0:mid], find_num) else:# 找的数在mid右面 print("\033[32;1m找的数在mid[%s]右面\033[0m" % dataset[mid]) return binary_search(dataset[mid+1:],find_num) else: if dataset[0] == find_num: #find it print("找到数字啦",dataset[0]) else: print("没的分了,要找的数字[%s]不在列表里" % find_num) binary_search(data,66)
输出
[1, 3, 6, 7, 9, 12, 14, 16, 17, 18, 20, 21, 22, 23, 30, 32, 33, 35]
找的数在mid[18]右面
[20, 21, 22, 23, 30, 32, 33, 35]
找的数在mid[30]右面
[32, 33, 35]
找的数在mid[33]右面
[35]
没的分了,要找的数字[66]不在列表里
尾递归
phthon中没有尾递归优化(就不存在效率低下)
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。
当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。
尾递归例子
def calc(n): print(n - 1) if n > -50: return calc(n-1
我们之前求的阶乘是尾递归么?
def factorial(n): if n == 0: #是0的时候,就运算完了 return 1 return n * factorial(n-1) # 每次递归相乘,n值都较之前小1 d = factorial(4) print(d)
上面的这种递归计算最终的return操作是乘法操作。所以不是尾递归。因为每个活跃期的返回值都依赖于用n乘以下一个活跃期的返回值,因此每次调用产生的栈帧将不得不保存在栈上直到下一个子调用的返回值确定。
内置函数
Python的len为什么你可以直接用?肯定是解释器启动时就定义好了
内置参数详解 https://docs.python.org/3/library/functions.html?highlight=built#ascii
几个***钻古怪的内置方法用法提醒
#compile
f = open("函数递归.py")
data =compile(f.read(),'','exec')
exec(data)
#print
msg = "又回到最初的起点"
f = open("tofile","w")
print(msg,"记忆中你青涩的脸",sep="|",end="",file=f)
# #slice
# a = range(20)
# pattern = slice(3,8,2)
# for i in a[pattern]: #等于a[3:8:2]
# print(i)
#
#
#memoryview
#usage:
#>>> memoryview(b'abcd')
#<memory at 0x104069648>
#在进行切片并赋值数据时,不需要重新copy原列表数据,可以直接映射原数据内存,
import time
for n in (100000, 200000, 300000, 400000):
data = b'x'*n
start = time.time()
b = data
while b:
b = b[1:]
print('bytes', n, time.time()-start)
for n in (100000, 200000, 300000, 400000):
data = b'x'*n
start = time.time()
b = memoryview(data)
while b:
b = b[1:]
print('memoryview', n, time.time()-start)
几个内置方法用法提醒
练习题
修改个人信息程序
在一个文件里存多个人的个人信息,如以下
username password age position department
alex abc123 24 Engineer IT
rain df2@432 25 Teacher Teching
....
1.输入用户名密码,正确后登录系统 ,打印
1. 修改个人信息
2. 打印个人信息
3. 修改密码
2.每个选项写一个方法
3.登录时输错3次退出程序
练习题答案
def print_personal_info(account_dic, username): """ print user info :param account_dic: all account's data :param username: username :return: None """ person_data = account_dic[username] info = ''' ------------------ Name: %s Age : %s Job : %s Dept: %s ------------------ ''' % (person_data[2], person_data[3], person_data[4], person_data[5] ) print(info) def save_back_to_file(account_dic): # 存入文件信息 """ 把account dic 转成字符串格式 ,写回文件 :param account_dic: :return: """ f.seek(0) # 回到文件头 f.truncate() # 清空原文件 for k in account_dic: row = ",".join(account_dic[k]) f.write("%s\n" % row) f.flush() def change_personal_info(account_dic, username): """ change user info ,思路如下 1. 把这个人的每个信息打印出来, 让其选择改哪个字段,用户选择了的数字,正好是字段的索引,这样直接 把字段找出来改掉就可以了 2. 改完后,还要把这个新数据重新写回到account.txt,由于改完后的新数据 是dict类型,还需把dict转成字符串后,再写回硬盘 :param account_dic: all account's data :param username: username :return: None """ person_data = account_dic[username] print("person data:", person_data) column_names = ['Username', 'Password', 'Name', 'Age', 'Job', 'Dept', 'Phone'] for index, k in enumerate(person_data): if index > 1: # 0 is username and 1 is password print("%s. %s: %s" % (index, column_names[index], k)) choice = input("[select column id to change]:").strip() if choice.isdigit(): choice = int(choice) if choice > 0 and choice < len(person_data): # index不能超出列表长度边界 column_data = person_data[choice] # 拿到要修改的数据 print("current value>:", column_data) new_val = input("new value>:").strip() if new_val: # 不为空 person_data[choice] = new_val print(person_data) save_back_to_file(account_dic) # 改完写回文件 else: print("不能为空。。。") def change_password(account_dic,username): password = input("请输入新密码:") account_dic[username][1] = password save_back_to_file(account_dic) print("密码修改完成") account_file = "修改个人信息_函数" f = open(account_file, "r+",encoding="utf-8") raw_data = f.readlines() accounts = {} # 把账户数据从文件里读出来,变成dict,这样后面就好查询了 for line in raw_data: line = line.strip() if not line.startswith("#"): items = line.split(",") accounts[items[0]] = items menu = ''' 1. 打印个人信息 2. 修改个人信息 3. 修改密码 ''' count = 0 while count < 3: username = input("Username:").strip() password = input("Password:").strip() if username in accounts: if password == accounts[username][1]: # print("welcome %s ".center(50, '-') % username) while True: # 使用户可以一直停留在这一层 print(menu) user_choice = input(">>>").strip() if user_choice.isdigit(): user_choice = int(user_choice) if user_choice == 1: print_personal_info(accounts, username) elif user_choice == 2: change_personal_info(accounts, username) elif user_choice ==3: change_password(accounts,username) elif user_choice == 'q': exit("bye.") else: print("Wrong username or password!") else: print("Username does not exist.") count += 1 else: print("Too many attempts.")