没有学不会的python
函数是什么?
老调常谈,还是那老一套,学习一个东西前,先搞懂是什么,再来学习怎么用。
函数函数,如果你是刚经历过高考肯定很熟悉,数学中就经常出现这个名词,比如什么sin函数,cos函数之类的。哈哈,心疼一会高考生。
函数是什么呢?其实函数严格来说,可以分为数学函数以及计算机函数,数学函数嘛,大家都是有文化的人,应该都知道,且我讲的是编程,数学函数跟这个关系不大,这里就略过了。我们主要讲计算机函数。
计算机函数是什么?
官方的解释是这样的:
函数是指一段在一起的、可以做某一件事儿的程序。也叫做子程序、(OOP中)方法。
其实这段解释已经很直白了,对于初学者来说,困惑的点就是子程序这个词。在写代码的过程中,往往由于业务逻辑比较复杂,各种数据交互流程比较繁琐,出于数据安全、易于理解、松耦合、强内聚等特征的考虑,我们会把程序划分成多个模块,每个模块又划分多个类和多个函数。由于上述现象的出现,一个大的程序模块就有很多小的模块组成,然后在大的模块中会调用小的模块以实现某个功能点,此时小的模块就成为了子模块,也叫做子程序。
简单说吧,子程序就是一个实现特定功能的程序块,通常被主程序调用。
嗯,现在把子程序讲清楚了,那么这个跟函数有什么关系?其实吧,子程序换一种说法,也可以称作是函数。在不同的语言中,有时也称为方法,但在python中,如果子程序是处于模块中的就称作函数,如果是处于类中的,就称作方法。由于我这个系列里还没讲到面向对象,所以,我们忽略掉类的方法这个说法,现在暂且认为,子程序就是函数。
做一个比较形象的例子:
假设上述人的一天是主函数,那么吃饭上班睡觉就是子函数,只有在主函数中调用了子函数,才能组成人的一天。
函数有什么作用?
既然函数存在,那么就有它存在的道理。它的作用不仅有,而且特别重要。下面就随便列几个,更多的我就不说了,因为如果你没有编程基础的话,很多特性说了也理解不了,等于白说。
- 高内聚、低耦合---这个是编程语言中的一个非常重要的特征,尤其是面向对象语言中。高内聚指的是,实现同样目的的代码应该尽量放在一块,不要松散。低耦合指的是,函数与函数之间尽量解耦,不要处处关联,这样才不会出现一发而触动全身的情况。即不会因为改了某个函数的一句话,导致其它函数也不能用了。
- 易拓展---需求是跟着市场和甲方走的,产品要改需求,程序员就得加班,如果程序的代码结构很好,那么我们就可以只改需要改的函数,其它的不动,比如增加功能模块,增加参数。
- 可重复使用---当把某个功能代码高度集中在函数里面时,此函数就不依赖于其它函数而存在,因此,任何需要实现该功能的函数都可以通过调用这个函数来获取该功能。
- 易于理解---通过函数名称以及文档描述和注释,可以让自己以外的人更好的参与进来,而函数的存在,对于这种分工合作是个很好的表现形式,大家都不需要知道函数怎么实现的,只需要调用就可以了。
还有更多,以后你就会慢慢发现了。
如何定义函数?
函数的定义很简单,看下面:
def function_name(prama1,prama2): passdef的意思就是声明后面的语句块是一个函数,function_name就是函数名称,param1、param2就是参数。到了这里,我有必要再说一下,因为面对着没有基础的同学,难免要多说一点,避免他们走弯路。我要说的是函数名称不是写死的function_name,上面的只是一种表现形式。就好比大家都有名字,但是我们大家都不叫名字,有的叫刘亦菲,有的叫马云。函数名称应该是根据所实现的功能来定的,参数名称也类似。
这里说一下什么叫做参数,参数可以看作是一个因变量,只有传入了参数,才能使函数产生不同的结果。参数不是函数必须的,可以构造一个不需要参数的函数,但是这个函数总会产生相同的结果。
下面看一下函数的示例:
def my_sum(param1, param2): return param1 + param2 def my_diff(param1, param2): return param1 - param2完了吗?那肯定不是,哪有这么简单。结合我自己的编程经验,还有以下的功力要传授给你们。
函数名称要有实际意义,切记假大空,更忌讳的是取一个毫无关系的名字
比如:我想定义一个扫描字符串的每个字符并输出的函数。有下面三个写法:
def scan_str(content): for s in content: print(s) def scan(content): for s in content: print(s) def a(content): for s in content: print(s)第一个函数最优,从名字就看得出来就是扫描字符串。第二个次之,从名字看到出来是扫描,但是扫描啥不知道,扫描文件还是扫描病毒还是其他的?这就是范围过广,也就是假大空。第三个写出来是要被骂的,而且是往死里骂的那种,从函数名字根本看不出来是什么意思。你想象一下啊,如果一个几万行代码含有几百个函数的程序,全部名字都是abcd这样的名字,你会不会看疯掉?
函数应该要加上文档说明,复杂的语句要加上注释说明
这么做的原因是,一来方便日后自己查看代码,二来是方便别人接手你的代码。添加文档说明的方式如下:
def scan_str(content): """ 扫描字符串的每个字符并输出 :param content: 待扫描的内容 :return: 不返回任何结果 """ for s in content: print(s)就是在函数声明下面,真正的代码实现逻辑上面,输入三次双引号就会自动生成一个待填充的文档说明结构,含有功能描述,参数描述以及返回值描述。未填充前的代码是这样的:
def scan_str(content): """ :param content: :return: """ for s in content: print(s)函数的代码块不易过长,一般维持在15行以内为佳
代码语句块过长说明我们的功能划分的还不够细致,过于短说明我们过于精简,一般维持在15行以内为佳。当然这不是硬性标准,它不会报任何异常。只是这个是默认的python pep8国际编码规范,很多大公司都会有代码规范考核的,从一开始掌握这些对我们有好处。 是
函数的参数值和传参
上面有简单讲了参数是什么。但这还远远不够,python中的参数,是非常灵活且有趣的。目前来说,可分为四类,分别是必须参数、可选参数、位置参数、关键词参数。下面就这些一个个来说。
必须参数
必须参数就是必须要传递的参数,如果不传递就调用函数会报TypeError。比如我如果这样调用函数,就会报错:
def scan_str(content): """ 扫描字符串的每个字符并输出 :param content: 待扫描的内容 :return: 不返回任何结果 """ for s in content: print(s) scan_str()由于scan_str有一个content参数,这个是必须参数,如果你不传递就调用这个函数,会爆出如下异常:
Traceback (most recent call last): File "D:/code/python/blog;, line 11, in <module> scan_str() TypeError: scan_str() missing 1 required positional argument: 'content'这个意思是说,缺少了一个必须的参数content,也即是我们调用它的时候必须要传递一个参数。嗯,好现在我们知道必须要传递参数了,但还有一点,传递的参数数据类必须要正确,不信你看下面这个例子:
def scan_str(content): """ 扫描字符串的每个字符并输出 :param content: 待扫描的内容 :return: 不返回任何结果 """ for s in content: print(s) scan_str(100)我们知道这个函数是扫描字符串并输出每个字符,我们期望传入的参数是字符串,但是我们这里却传入了一个整型参数100,它同样会抛出TypeError异常,抛出的异常如下:
Traceback (most recent call last): File "D:/code/python/blog;, line 11, in <module> scan_str(100) File "D:/code/python/blog;, line 7, in scan_str for s in content: TypeError: 'int' object is not iterable意思是,整型对象是不可迭代的。因此,我们要注意传递的参数类型要正确,或者在代码块里面添加一个参数类型检查函数。比如:
def scan_str(content): """ 扫描字符串的每个字符并输出 :param content: 待扫描的内容 :return: 不返回任何结果 """ if isinstance(content, str): for s in content: print(s) else: print("你传入的参数不是字符串类型") exit(0) scan_str(100)这里,我们用isinstance函数来检测传入的参数是不是字符串类型。isinstance是python标准库里面的函数,我们可以直接拿来用。必须参数要注意的就是这么多。
默认参数
默认参数就是含有默认值的参数,也叫做可选参数。为什么呢?因为默认参数有默认值了,即使我们不传递参数,它也是有值的。当然如果默认值不符合我们的需求,我们可以同样可以传递一个新的值把它覆盖掉。假如我们有如下一个函数:
def scan_str(content="default"): """ 扫描字符串的每个字符并输出 :param content: 待扫描的内容 :return: 不返回任何结果 """ if isinstance(content, str): for s in content: print(s) else: print("你传入的参数不是字符串类型") exit(0) scan_str() # 输出默认值 scan_str("cover default value") # 覆盖默认值与前面不同的是,这里的content有一个默认的值,所以我们可以不带任何参数的调用此函数,此时函数不会报错并且会输出default。同样的,我们要是想输出其它字符串,只需要传递一个参数值覆盖掉默认值即可。
但是有一点要注意的是,默认值必须是不可变类型。比如上面的是字符串类型,就是不可变类型,如果是可变类型的话,比如列表,此时,默认值会随着函数的调用发生改变,和我们最初预期的默认值会不一样。
看下例子:
def print_default_list(my_list=[1, 2, 3]): """ 输出默认数组的内容 :param my_list: 默认数组 :return: """ print(my_list) my_li(4) # 连续两次调用了此函数,会发现输出的列表内容发生了变化 print_default_list() print_default_list()运行结果:
[1, 2, 3] [1, 2, 3, 4]按照我们的预期my_list应该是【1,2,3】才对,因为4是输出之后才添加上去的。但如果有学习过我们前面讲到复合类型的文章了解过不变类型和可变类型的区别的同学,应该明白它们的区别。因为my_list是列表,它是可变对象,也即它其实引用了一个内存地址块,无论我们调用多少次,这个地址都是不变的,但是因为我们添加了4,导致该内存块又添加了一个新的内容,所以my_list的内存地址是没有变的,只是内存保存的数据变化了。因此,我们第二次调用此函数的时候,发现默认值已经被修改了。所以记住:
默认值一定要是不可变类型,否则会出大事的。
位置参数
位置参数一般是在我们不确定该函数应该要声明多少个参数,或者要声明的参数实在太多的时候,用来占位使用的。
示例代码:
def print_args(*args): """ 输出位置参数 :param args: :return: """ for item in args: print(item) print_args(1, 2, 3, 4) print_args("hello ", "world")上述代码就是循环输出所有传递进去函数的参数,我们可以看到,第一次调用的时候传递了4个,第二次调用的时候传递了2个。这就是不一定要传输多少个参数,传多少都行,根据业务需求来定。位置参数一般使用*args来表示。
深度剖析一下为什么会这样,其实这涉及到装箱和拆箱的说法。即当我们调用print_args(1,2,3,4)的时候,我们看上去好像是传递了4个参数,其实不是,在传递的过程中,python帮我们装箱了,即传递过去的其实是一个元组(1,2,3,4),这也是为什么可以迭代输出的原因。我们可以通过代码来观察一下:
def print_args(*args): """ 输出位置参数 :param args: :return: """ print(type(args)) print(args) print_args(1, 2, 3, 4)输出如下结果:
<class 'tuple'> (1, 2, 3, 4)可以观察到,结果确实和我们预期的一样,args的类型是元组。装箱的过程可以通过下图来理解:
关键词参数
关键词参数即是我们常说的字典,就是key=value这样形式的,明白吗?因为有键,所以叫关键词参数。关键词参数一般用**kwargs表示,前面是两个*号。关键词参数的用法如下:
def print_kwargs(**kwargs): """ 输出关键词参数 :param args: :return: """ print(type(kwargs)) print(kwargs) for k, v in kwargs.items(): print("{0}:{1}".format(k, v)) print_kwargs(one=1, two=2)通过代码可以看到,即使函数没有定义one、two这两个参数,但是我们依然可以以键值对的形式传进去,原因是python对它进行了装箱操作,它传递给函数的时候是这样的one=1,two=2,然后经过python的处理,函数接收的时候已经是这样了{"one":1,"two":2}。
我们看下输出结果:
<class 'dict'> {'one': 1, 'two': 2} one:1 two:2可以发现,关键词参数其实就是字典,对吧?装箱过程如图所示:
好了,碍于篇幅的问题,主要是我的脑力槽到底了,今天先讲到这里,这么多也够大家消化一些时间的了。加油。下一篇将继续将函数的综合应用、函数的返回值、lambda函数...感兴趣的朋友可以留意一下。
喜欢我的系列文章的话,可以关注我的主页也可以关注我的公众号。
如果你有什么问题想要反馈或者联系我,可以加我。