第4章 自定义函数、类和作用域
4.1 Python的自定义函数
前面我们学习了Python系统中的函数,这节主要介绍用户自定义函数的使用。
1.函数的定义
函数是组织好的,可重复使用的,用来实现相对单一功能或相关联功能的代码段。
在Python 3中,无论是命名函数,还是匿名函数,都是语句和表达式的集合。使用函数的过程称为调用函数。
以下是定义一个函数的简单规则:
(1)函数代码块以“def”关键词开头,后接函数标识符名称和圆括号“()”。
(2)任何传入参数和自变量都必须放在圆括号内,圆括号之间也可以用于定义参数,称为形式参数。
(3)函数内容以冒号“:”开始,并且以空格缩进。
(4)函数的第一行语句可以选择性地使用注释字符串文档,这个文档用于存放函数说明。
(5)使用“return [表达式]”结束函数,选择性地返回一个值给调用方。不带表达式的“return”相当于返回None值。
下面是一个定义函数“def”语法。
def function_name([参数表]): ''’函数介绍文档。''' 函数命令块 return [表达式参数表]
函数调用是指在程序中执行函数。在函数调用中输入的参数称为实参,如range(12)中的12。
看示例4-1中的stockname(n)函数定义。
#文件名:示例4-1.py #数字股票代码转换字符串股票代码 def stockname(n): ''' 函数说明 数字股票代码转换字符串股票代码 stockname(n) 参数: n整型 返回:字符串 ''' s=str(n) #把数字转为字符串 s=s.strip() #删除字符串前后空格 if(len(s)<6 and len(s)>0): #如果字符串长度为1-5的数,前面就用0补够6位长度 s=s.zfill(6)+'.SZ' #深圳股后缀加.SZ if len(s)==6: #上海股票一般是100000以上的数字 if s[0:1]=='0': #第一位为0,是深圳股票代码 s=s+'.SZ' #深圳股后缀加.SZ else: #否则是上海股票代码 s=s+'.SH' #上海股后缀加.SH return s print(stockname.__doc__) print(stockname(776)) print(stockname(600030))
在Python中有一个奇妙的特性,即文档字符串“DocStrings”,用它可以为模块、类、函数等添加说明性的文字,使程序更易读易懂,更重要的是可以通过Python自带的标准方法将这些描述性文字进行信息输出。
上面提到的自带的标准方法就是__doc__,需要注意,前后各有两个连在一起的下画线。
命令输出结果:
函数说明 数字股票代码转换字符串股票代码 stockname(n) 参数: n整型 返回:字符串 000776.SZ 600030.SH
2.函数命名规则
函数命名的规则同变量命名的规则,区分大小写,不能使用Python关键字作为函数名。
函数的命名一定要考虑其所完成的功能和语境,不要认为这是在浪费时间。当然,如果程序很短,又仅供自己使用,就无需考虑这些了。如果项目较大,而且文件较多,则最好花费些时间在函数命名上。这样一方面易于他人查看代码,另一方面便于自己维护。
为了更好地阅读程序,一般对函数命名有如下要求。
(1)简单明了,即根据上下文给动词和介词加上名词,如使用playMusic(file)而不是play(file)。不要为了过度的简洁而影响函数名称的清晰和准确性,也可以使用下画线连接词组的方式,如set_volume(volume),这样更方便通过函数名理解函数的功能。
(2)避免歧义,即要考虑函数的命名是否存在多种解释,如在displayName中display是名词还是动词,不够清晰。如果命名不清晰,则需要重新命名消除歧义。再如,add就可以使用append这类词语替换。
(3)保持一致性,即在应用和库中使用相同的术语来描述同一个概念。避免在一个函数中使用screen(),却在另外一个函数中使用display()。
(4)使用容易读懂的名称。最好是使用一段英文,或者英文短语,有时也可以使用拼音,最好不要中文和英文混合,这样读起来很费劲。
(5)在必要时加前缀。前缀可以代表变量作用域的范围,也可以代表返回值的类型或分类。
例如:
def strGetUserId():
其中,前缀“str”表示返回值为字符串类型,不然别人会误以为返回整型数据。
3.函数形式参数表
Python3的函数定义非常简单,但灵活度却非常大。除了正常定义的必选参数外,还可以使用默认参数、可变参数和关键字参数,这就使得函数定义的接口不但可以处理复杂的参数,还可以简化调用者的代码。
函数参数的数据类型分为不可变数据和可变数据,其中不可变数据包括Number(数字)、String(字符串)、Tuple(元组),可变数据包括List(列表)、Dictionary(字典)、Set(集合)、对象数据。
不可变数据类型参数传递的只是值,没有影响传递参数变量的本身。
可变数据类型参数传递的是对象指针,如果修改了变量参数数据,传递参数的变量数据就会修改。因此,我们要对引入的数据进行复制备份并对数据副本进行处理,这样结果才是返回处理后的数据副本指针。
见示例4-2。
#函数演示 i=20 f=19.86 dic = {i:2*i for i in range(10)} l=[i for i in range(10)] print('dic= ', dic) print('l= ', l) def fa(x, y): x=x*y y=x return y def fb(x): x.clear() return x def fc(x): y=x.copy() #使用copy()函数不会破坏原始数据 y.clear() return y a=fa(i, f) print('a= ', a) print('i= ', i) print('f= ', f) print(’使用copy()函数,不会破坏原始数据。') b=fc(dic) print('b= ', b) print('dic= ', dic) c=fc(l) print('c= ', c) print('l= ', l) print(’不使用copy()函数,会破坏原始数据。') d=fb(dic) print('d= ', d) print('dic= ', dic) e=fb(l) print('e= ', e) print('l= ', l)
程序运行结果:
dic= {0: 0, 1: 2, 2: 4, 3: 6, 4: 8, 5: 10, 6: 12, 7: 14, 8: 16, 9: 18} l= [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] a= 397.2 i= 20 f= 19.86 使用copy()函数,不会破坏原始数据。 b= {} dic= {0: 0, 1: 2, 2: 4, 3: 6, 4: 8, 5: 10, 6: 12, 7: 14, 8: 16, 9: 18} c= [] l= [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 不使用copy()函数,会破坏原始数据。 d= {} dic= {} e= [] l= []
1)默认参数
默认参数是预先赋值,有初值的参数。默认参数可以简化函数的调用。在设置默认参数时,必选参数在前,默认参数在后,否则Python 3的解释器就会报错。
当函数有多个参数时,把变化大的参数放前面,变化小的参数放后面作为默认参数。
见示例4-3。
#函数默认参数演示 def mypow(x, y=2): return x**y a=mypow(4,3) b=mypow(4) print('a=mypow(4,3)= ', a) print('b=mypow(4)= ', b)
程序运行结果:
a=mypow(4,3)= 64 b=mypow(4)= 16
2)可变参数
可变参数就是在参数前面加了一个“*”号。在调用该函数时,就可以传入0个或任意个参数,这些可变参数会自动组装为一个元组(tuple)。
*args是可变参数,args接收的是一个元组。
元组无法直接修改,我们可以将它转换为一个列表,再来处理。请看一个利用可变参数来装配排序后列表的代码,见示例4-4。
#函数*kargs参数演示 def test(*k): return k def shortall(*kargs): ''' 多参数字符串排序 shortall(*kargs) 返回字符串列表 ''' #过滤非字符串 l=list(kargs).copy() l.sort() return l s=test('ABC', '222', '1111', 'xixi', 'hahahah') print('s= ', s) print('type(s)= ', type(s)) #1.无参数 s1=shortall() print('s1=', s1) #2.字符串参数 s2=shortall('ABC', '222', '1111', 'xixi', 'hahahah') print('s2=', s2) #3.数值参数 s3=shortall(88,9,1,5,32423,999) print('s3=', s3)
程序运行结果:
s= ('ABC', '222', '1111', 'xixi', 'hahahah') type(s)= <class 'tuple'> s1= [] s2= ['1111', '222', 'ABC', 'hahahah', 'xixi'] s3= [1, 5, 9, 88, 999, 32423]
3)关键字参数
关键字参数允许传入0个或任意多个含参数名的参数,这些关键字参数在函数内部会自动组装为一个字典(dict)。
**kw是关键字参数,kw接收的是一个字典。
见示例4-5。
#函数**kargs参数演示 def myset(**kw): ''' **kw是关键字参数 mayset(**kw) 返回参数内容 ''' d=kw return d def detail(name=None, **kargs): ''' detail(name=None, **kargs)-> str name is a str.return a str like'name, key1:value1, key2:value2' 这个函数特定的功能 ''' data = [] for x, y in kargs.items(): data.extend([', ', str(x), ':', str(y)]) info = ''.join(data) return '%s%s'%(name, info) a=myset(name='hhh', age=19, a=98.5, b=97, c=98.8) print('a= ', a) b=detail(name='hhh', age=19, a=98.5, b=97, c=98.8) print('b= ', b)
程序运行结果:
a= {'name': 'hhh', 'age': 19, 'a': 98.5, 'b': 97, 'c': 98.8} b= hhh, age:19, a:98.5, b:97, c:98.8
4)参数组合
在Python中,定义函数可以用必选参数、默认参数、可变参数和关键字参数,这4种参数还可以一起使用,或者只用其中几种。但是需要注意,参数定义的顺序必须是必选参数、默认参数、可变参数和关键字参数。
使用函数参数的方式:
(1)按位置匹配,如func(name)。
(2)按关键字匹配,如func(key=value)。
(3)按输入参数匹配,如
元组收集:func(name, arg1, arg2)。
字典收集:func(name, key1=value1, key1=value2)。
(4)按参数定义顺序。
4.函数返回值
(1)return语句的作用是结束函数调用并返回值。
return语句可以不带参数也可以带多个参数。
不带参数就是结束函数运行,返回一个None作为返回值,类型是NoneType。其与return, return None等效,都是返回None。
带参数可以返回调用函数的语句,并且给变量表赋值。
一个函数中可以有任意多个return语句,其中任何一个return语句执行,该函数运行结束。
(2)隐含返回值,指在整个函数体内没有return语句时,那么在函数运行结束时就会隐含返回一个None值作为返回值,类型是NoneType。其与return, return None等效,都是返回None。
请看示例4-6。
#函数返回参数演示 #1.无return函数 def func1(x): x=x+1 a=func1(10) print('a= ', a) print('type(a)= ', type(a)) #2.有return函数,无返回参数 def func2(x): x=x+1 return #3.有return函数,返回参数None def func3(x): x=x+1 return None a=func1(10) print('a= ', a) print('type(a)= ', type(a)) b=func2(20) print('b= ', b) print('type(b)= ', type(b)) c=func3(40) print('c= ', c) print('type(c)= ', type(c)) #4.多个返回参数 def func4(x): return x, x*2, x**2 d, e, f=func4(5) print('d= ', d) print('e= ', e) print('f= ', f)
程序运行结果:
a= None type(a)= <class 'NoneType'> a= None type(a)= <class 'NoneType'> b= None type(b)= <class 'NoneType'> c= None type(c)= <class 'NoneType'> d= 5 e= 10 f= 25
5.匿名函数
匿名函数表达式是基于数学中的λ演算而得名的,直接对应于其中的匿名函数表达式抽象,是一个匿名函数,即没有函数名的函数。
Python也支持匿名函数,这些函数可以使用λ(λ是希腊字母中的第十一个字母,英文为lambda)关键字创建。
当我们在传入函数时,有些时候不需要显式地定义函数,直接传入匿名函数更方便。匿名函数只能有一个表达式,不用写return语句,返回值就是该表达式的结果。由于匿名函数没有名字,所以不必担心函数名冲突。此外,匿名函数也是一个函数对象,可以被赋值给一个变量,再利用变量来调用该函数。
见示例4-7。
#1.lambda匿名函数 f = lambda a, b, c:a+b+c print(f(1,2,3)) #2.匿名函数及输入参数(lambda a, b=2:a*b)(i, j) i=3 j=5 l=[x for x in range(1,(lambda a, b=2:a*b)(i, j))] print(l) #3.a = lambda *z:z #*z返回的是一个元组 f2 = lambda *z:z #*z返回的是一个元组 b=f2(1,3,6,9) print('b= ', b) #4.lambda **Arg: Arg #arg返回的是一个字典 f3=lambda **Arg: Arg #arg返回的是一个字典 c=f3(name='hhh', age=19, a=98.5, b=97, c=98.8) print('c= ', c) #5.lambda嵌套到普通函数中,lambda函数本身作为return的值 def addx(n): n+=n return(lambda x: n+n)(n) d=addx(3) print('d= ', d)
程序运行结果:
6 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] b= (1, 3, 6, 9) c= {'name': 'hhh', 'age': 19, 'a': 98.5, 'b': 97, 'c': 98.8} d= 12
6.__main__关键字
Python 3用“if __name__ == '__main__':”语句说明程序的起始入口。
通过此语句可以判断:当前运行的模块是否是直接被Python解释器调用执行的,因为只有被Python解释器直接调用执行的模块文件才能获取程序参数。
getopt是一个包装方法,用以读取main函数后面的参数。
getopt.getopt(args, options[, long_options])有如下三个参数:
args就是Python命令行“python demo_main.py -n小白 -w大家好!”后面的参数“demo_main.py -n小白 -w大家好!”,通常是sys.argv数组。不过我们一般会去除第一个元素,因为sys.argv的第一个元素就是文件名本身“demo_main.py”,所以一般的写法是sys.argv[1:]。
options是一个字符串,描述了需要解析哪些参数。如果一个参数不需要参数值,就只写参数名,如h;如果一个参数需要传入参数值,就在后面加冒号“:”,如n:。所以本案例中hn:w:的意思是有三个参数,分别是-h, -n和-w,其中-h无需传入参数值,而-n和-w需要传入参数值。
long_options是一个字符串数组,也表示需要解析哪些参数,它是相对options而言的。在linux中,我们经常会看到一个命令参数有多种写法,最常见的就是帮助参数。帮助参数有-h和--help两种写法,前一种是options,后一种就是long_options。
假如我们有一个--help,那么在long_options中就是['help']。如果一个参数需要传入参数,如--name 'Good’在long_options中就是['name=']。是的,就是多了一个等于号“=”。
getopt.getopt返回一个元组(opts, args),其中opts就是我们解析出来的参数,而args则是剩余没有解析的参数。opts是元组数组,每个元组就相当于一对键值“key-value”,其中key就是我们的参数名(键),而value就是参数值。
见示例4-8。
#main函数示例 import getopt import sys def say(s1, s2): print(’你好,我叫’, s1, ', ', s2) if __name__ == '__main__': opts, args = getopt.getopt(sys.argv[1:], 'hn:w:', ['name=', 'word=', 'help']) name = 'No Name' word = 'Hello' for key, value in opts: if key in ['-h', '--help']: print(’一个向人打招呼的程序’) print(’参数:') print('-h\t显示帮助’) print('-n\t你的姓名’) print('-w\t想要说的话’) sys.exit(0) if key in ['-n', '--name']: name = value if key in ['-w', '--word']: word = value say(name, word)
在Spyder中运行的结果:
你好,我叫No Name , Hello
因为程序中的变量name和word使用的是程序默认初值,所以这个程序需要在Windows系统的cmd窗口来运行,输入下面的命令。
Python demo_main.py -n小白 -w大家好!
运行结果如图4-1。
图4-1 cmd窗口的运行结果