第17讲~第19讲:函数:python的乐高积木
一 函数的基本内容
python编程语言中最重要的三大块内容:函数、对象、模块
1 函数的基本概念
- 编程语言中对函数的定义是:函数是逻辑结构化和过程化的一种编程方法。
- 实际应用时是把一组语句的集合通过函数名进行封装得到的代码块。
- 它仅在被调用的时候才会运行。用户在使用函数的过程中可以将数据(参数)传递到函数中,函数运行结束后可以把数据作为结果返回。
2 函数的定义
- 规则:
- 函数代码块以 def 关键词开头,后接函数标识符名称和圆括号()。
- 任何传入参数和自变量必须放在圆括号中间。圆括号之间可以用于定义参数。
- 函数的第一行语句可以选择性地使用文档字符串—用于存放函数说明。
- 函数内容以冒号起始,并且缩进。
- return [表达式] 结束函数,选择性地返回一个值给调用方。不带表达式的return相当于返回 None
- 语法:
-
def functionname( parameters ): "函数_文档字符串" function_suite return [expression]
-
- 关于return的返回值:
- 作用:返回函数中全部逻辑的执行结果并结束函数的执行
- 返回值存在以下情况:
- 没有显式定义,返回None对象
- 定义了一个返回值,返回定义的值
- 定义了多个函数返回值,返回一个以所有返回值为元素的元组
- 任意对象,字符串、函数等均可以作为函数的返回值
3 函数的调用
- 语法:函数名(传入的参数)
4 使用函数的好处
- 提高代码的重用性,避免代码的重复冗余
- 保持一致性,相同的过程执行相同的逻辑
- 可扩展性好,函数中的代码块可根据需要灵活扩展
- 可维护性高,一旦需要改变逻辑时只需要修改函数中的代码块,一处修改后调用的地方都同步更新
5 函数文档
- 定义:在函数内部的第1行开始,使用三引号作为帮助文档的标记字符
- 功能:对函数的内容、参数、意义以及返回值等内容进行介绍,方便其它人理解自己的代码并维护
- 特点:函数文档跟注释一样不会被打印出来
- 查看:
- 函数名.__doc__
- help(函数名)
- print.__doc__
6 函数与过程
- 函数有返回值,而过程是简单的、特殊的、没有返回值
- python中严格来说只有函数、没有过程
- python中函数如果有返回值的化,就会返回相应的结果,没有的话返回一个None对象
二 函数的参数 https://www.cnblogs.com/linupython/p/6609217.html
1 基本概念
- 定义函数的目的是便于在程序的其他地方调用,而对于很多函数或执行逻辑来讲,不同的输入往往会导致截然不同的输出,因此函数是需要能接收输入的参数的。
2 参数分类
- 形参:即形式参数,在定义函数时引入的参数,只有在函数被调用时才会存在并为其分配内存空间
- 实参:调用函数时实际传入的参数,即执行函数定义的逻辑时给定的输入,占用内存空间。调用函数时实参的值会根据一定的规则传递给形参来执行函数中定义的逻辑过程。
3 参数传递方法
- 位置参数传入:
- 内容:位置参数传入是一种最简单的参数传入方式,传入是实参与形参的相对位置一对一对应。
- 注意:位置参数传入时实参的个数与形参的个数必须严格一致,多一个或少一个都会报错
- 关键字参数传入:
- 内容:在调用函数传入参数时,直接使用形参的名字对其进行赋值。
- 特点:传入实参的顺序与形参定义的顺序可以不一致,传入时通过参数的名字来对号入座(相对位置已经失效)。
- 如果把位置参数和关键字参数混合使用,会按照位置参数调用来进行处理,或者说各自为政,互不干扰,但前提是位置参数不能放在关键字参数后面
- 默认参数传入 :
- 内容:在定义函数时还可以直接对引入的形参进行赋值来作为参数的默认值
- 特点:这种情况下在调用函数时是否需要传入实参已经不重要了,如果不传入实参则使用则使用对应形参的默认值,否则则传入实参执行函数(覆盖参数的默认值)
- 默认参数传入方式接收位置调用和关键字调用,主要用于以下场景:
- (1) 为软件的安装预设默认安装路径
- (2) 设定某些应用的默认参数,如数据的默认连接端口为3306
- 参数组参数传入(收集参数/可变参数)
- 如果已经有了收集参数,还需要其它参数的话,一般把这些参数设置成默认参数,不容易出错
- 例如:
>>> def test(*params,z=8): ... print("参数的长度是:",len(params)) ... print("第二个参数是:",params[1],z) ... >>> test(12,24,34,6,3) 参数的长度是: 5 第二个参数是: 24 8
- 例如:
- 内容:参数组参数传入用于传递非固定长度的参数,实参会被转换为元组或字典传递给形参。
- 参数组被转换为元组:
- 内容:在定义函数的形参时,如果形参以一个星号*开头然后接上其它的有效字符串,并且调用函数时实参以位置参数方式传入(只此一种),实参会被转换为元组执行函数
- 语法:
def func1(*args): … #省略代码块 return … #省略return的值 a = func1(x, y, z) #x,y,z表示实参
- 参数组被转换为字典:
- 内容:在定义函数的形参时,如果形参以两个星号**开头然后接上其它的有效字符串,并且调用函数时实参以关键字参数方式传入(只此一种),实参会被转换为字典执行函数
- 语法:
def func2(**args): … #省略代码块 return … #省略return的值 a = func1(x=1, y=2, z=3) #x,y,z表示实参
- 注意:参数组参数传入方式与其他参数传入方式混合使用时,参数组传入的参数需要尽量放在后面,位置调用参数也不能跟在关键字参数后面。
- 如果已经有了收集参数,还需要其它参数的话,一般把这些参数设置成默认参数,不容易出错
- 混合参数传入
- 内容:混合参数传入即将上述的几种参数传入方式混合使用,此时位置调用不在遵循位置的严格对应关系,或者说传入参数的方式更符合上述哪种传入方式就按照哪种方式来传入参数。
三 函数变量的作用域
1 作用域的概念: 作用域是指对某一变量和方法具有访问权限的代码空间
2 变量分类
- 全局变量(global variable):在函数外部(子程序)定义,定义后在程序的全局范围内均有效且可被访问到,即它的作用域是程序全局。(全局变量的使用一定要特别小心,可以随意访问,但不要轻易在函数内部修改)
- 局部变量(local variable):在函数内部定义,仅在函数内部效且可被访问到,即作用域是函数内部,除非通过global关键字将变量的作用域修改为全局。
- 总结:全局变量和局部变量各自的作用域决定了默认情况下在函数内部可任意调用访问全局变量,但反之在函数外部范围内不可调用访问在该函数内部定义的局部变量,除非在函数内部定义局部变量时加上global关键字。(被定义的函数是通过栈进行存储的,函数调用完成后,保存函数内部数据的栈会被销毁,局部变量也就不存在了)
3 全局变量和局部变量之间的相互转换和调用的规则:
- (1)默认情况下可在函数内部调用全局变量并对其重新定义后赋值(注意不包括增删改等修改操作),但该值仅在函数内部有效,不影响函数外部该全局变量的属性和值。即局部变量和全局变量的属性不变,在函数(子程序)内部局部变量生效,函数外部全局变量生效,唯一的联系是全局变量和局部变量共用了一个变量名称,但本质上是两码事。
- 注意,它们的存储空间是不一样的!!!
-
def discounts(price,rate): final_price = price * rate old_price = 88 #这里试图修改全局变量 print("修改后的局部变量old_price的值是:",old_price) return final_price old_price = float(input("请输入原价:")) rate = float(input("请输入折扣率:")) new_price = discounts(old_price,rate) print("修改后的全局变量old_price的值是:",old_price) print("打折后的价格是:",new_price) print("这里试图打印局部变变量final_price的值:",final_price)
- 上例中的old_price就是一个典型的例子,discounts内的变量old_price和discounts外部的变量old_price虽然名字相同,但它们所指向的地址和内容都是不一样的
- (2)函数内部定义局部变量时,可通过global关键字将变量的作用域提升扩大到全局范围,即把局部变量转换为全局变量。但建议尽量避免使用global,原因是一旦使用,对稍微复杂些的程序而言会引起变量的混乱,给程序调试带来干扰。
- (3)字符串、整数数据类型在函数内部修改(重新赋值)不覆盖全局变量的值,除此之外复杂的数据类型局部(如列表、字典、集合)变量的修改(请一定注意是修改,增删改等操作,不包括重新定义后赋值)会覆盖全局变量的值,具体原因还有待深究。
4 建议不到万不得已不要使用全局变量,简洁的概括为:|Yn"J=oHI
- a) 代码可读性变差
- b) 代码安全性降低
以下关于全局变量的危言耸听是转来的,大家不妨也看下:
- a) 它会造成不必要的常量频繁使用,特别当这个常量没有用宏定义“正名”时,代码阅读起来将万分吃力。
- b) 它会导致软件分层的不合理,全局变量相当于一条快捷通道,它容易使程序员模糊了“设备层”和“应用层”之间的边界。写出来的底层程序容易自作多情地关注起上层的应用。这在软件系统的构建初期的确效率很高,功能调试进度一日千里,但到了后期往往bug一堆,处处“补丁”,雷区遍布。说是度日如年举步维艰也不为过。
- c) 由于软件的分层不合理,到了后期维护,哪怕仅是增加修改删除小功能,往往要从上到下掘地三尺地修改,涉及大多数模块,而原有的代码注释却忘了更新修改,这个时候,交给后来维护者的系统会越来越像一个“泥潭”,注释的唯一作用只是使泥潭上方再加一些迷烟瘴气。
- d) 全局变量大量使用,少不了有些变量流连忘返于中断与主回圈程序之间。这个时候如果处理不当,系统的bug就是随机出现的,无规律的,这时候初步显示出病入膏肓的特征来了,没有大牛来力挽狂澜,注定慢性死亡。
- e) 无需多言,如果您的系统中大量使用全局变量,那么您已经成功得到一个畸形的系统,它处于一个神秘的稳定状态!你看着这台机器,机器也看着你,相对无言,心中发毛。你不确定它什么时候会崩溃,也不晓得下一次投诉什么时候会到来。
四 课后作业
0 请问这个函数有多少个参数?
def MyFun((x, y), (a, b)): return x * y - a * b
答:如果你回答两个,那么恭喜你错啦,答案是 0,因为类似于这样的写法是错误的!
我们分析下,函数的参数需要的是变量,而这里你试图用“元组”的形式来传递是不可行的。
我想你如果这么写,你应该是要表达这么个意思:
>>> def MyFun(x, y): return x[0] * x[1] - y[0] * y[1] >>> MyFun((3, 4), (1, 2)) 10
1 编写一个函数 power() 模拟内建函数 pow(),即 power(x, y) 为计算并返回 x 的 y 次幂的值。
嗯,我哭了,这么简单的程序我都写错了,我好方,正确代码如下:
def power(x,y): i = 0 result = 1 while(i<y): result *= x i += 1 print(f"i={i},result = {result}") return result """ def power(x,y): result = 1 for i in range(y): result *= x print(f"i={i},result = {result}") return result """ print("该函数的功能是:求幂次方运算") a = int(input("请输入基值:")) b = int(input("请输入次方值:")) z = power(a,b) print(f"计算结果是:{z}")
补充:
def power(x,y): return x ** y
拓展,也是我的错误代码(函数定义部分):
""" (一)(二)两个函数实现的功能是一样的,不过(一)函数用了for循环,函数(二)用了while循环, 它们都完成了如下操作: (1)先计算x的i次幂,得到结果z, (2)再计算x的z次幂运算,得到结果w (3)最后计算w的平方 函数运算过程更像是对上一次计算结果的迭代过程 """ (一)def power(x,y): i=1 result = x for i in range(y): print(i,range(y)) result *= result # 每次计算的都是result的平方,而不是x的幂次方 print(result) return result (二)def power(x,y): i=0 while(i<y): # 如果把while换成if该代码根本没有实现循环,其只是进行了一次判断 x *= x print(f"i = {i},x = {x}") i += 1 return x
2 编写一个函数,利用欧几里得算法求最大公约数,例如 gcd(x, y) 返回值为参数 x 和参数 y 的最大公约数
1 # 欧几里得算法求最大公约数 2 3 # !/usr/bin/env python 4 # coding -*- utf:8 -*- 5 6 def gcd(x,y): 7 while y: 8 t = x % y 9 x = y 10 y = t 11 return x 12 13 print(gcd(28,14))
3 编写一个将十进制转换为二进制的函数,要求采用“除2取余”的方式,结果与调用 bin() 一样返回字符串形式
def Dec2Bin(dec): temp = [] result = '' while dec: quo = dec % 2 dec = dec // 2 temp.append(quo) while temp: result += str(temp.pop()) return result print(Dec2Bin(32))