第六章:抽象--函数与库

6.1 懒惰即美德:函数

只需要告诉计算机去做即可,不用特别说明应该怎么做,创建一个函数,在需要的时候直接调用以节省时间

6.2 抽象和结构

抽象可以节省很多工作,实际上它的作用还要更大,它是使得计算机程序可以让人读懂的关键
程序应该是抽象的,也是易懂的

6.3 创建函数

函数是可以被调用的(可能带有参数,也就是放在圆括号中的值)。它执行某种行为并且返回一个值。一般来说,内建的callable函数可以用来判断函数是否可被调用;
函数callable在python3.0中不再使用,需要使用hasattr(func,__call__)代替。
创建函数是组织程序的关键,那么如何定义函数?用def语句即可

6.3.1 文档化函数:加入注释

如果想要给函数写文档,让其他使用该函数的人也能理解的话,可以加入注释(以#开头)。另一个方式就是直接写上字符串。这类字符串在其他地方可能会非常有用,比如在def语句后面。如果在函数的开头写下字符串,他就会作为函数的一部分进行存储,这成为文档字符串。
内建的help函数,非常有用,可以在交互式解释器中使用它,就可以得到关于函数,包括它的文档字符串的信息

6.3.2 并非真正函数的函数

数学意义上的函数,总在计算其参数之后返回点什么,python的有些函数却并不返回任何东西
python的函数就是函数,即便从学术上讲并不是函数。没有return语句,或者虽然有return语句但是return语句后面没有任何的返回值。
可以看到,第二个print语句被跳过了,并没有输出,那么x的作用是什么?看一看到print x输出的是None。所以所有的函数的确都返回了东西,但不需要他们返回值的时候就返回None!

6.4 参数魔法

6.4.1 值从哪里来

函数被定义之后,所操作的值是从哪里来的呢?
注意:卸载def语句中的函数名后面的变量通常叫做函数的形参,而调用函数的时候提供的值是实参,或者称为参数

6.4.2 我能改变参数吗

参数值是变量而已,所以他们的行为其实和你预想的一样。在函数内为参数赋予新值不会改变外部任何变量的值:
在try_to_change内,参数n获得了新值,但是他没有影响到name变量,n实际上是个完全不同的变量,具体工作流程如下
结果显而易见:当变量n的值改变的时候,变量name不变。同样,当在函数内部把参数重新绑定的时候(赋值),函数外的变量不会受到影响。
注意:参数存储在局部作用域内。
字符串(以及数字和元组)是不可变的,即无法被修改(即只能用新的值覆盖)。所以他们做参数的时候也就无需多做介绍。但是如果键可变的数据结构如列表用作参数的时候会发生什么?
本例中,参数被改变了!
当两个变量同时引用一个列表的时候,它们的确是同时引用一个列表。
如果想避免这种情况,可以复制一个列表的副本。当在序列做切片的时候,返回的切片总是一个副本。
因此,如果你复制了整个列表的列表,将会得到一个副本:
现在n和names包含两个独立(不同)的列表,其值相等
如果现在改变n,就不会影响到names
注意:函数的局部名称,包括参数在内。并不和外面的的函数名称(全局的)冲突

1.为什么要修改参数?

使用函数改变数据结构(比如列表或者字典)是一种将程序抽象化的好方法
抽象的要点就是隐藏更新时琐碎的细节,这个过程可以用函数来实现
如下:

2.如果我的参数不可变?

在某些语言中,重新绑定参数并且使这些改变影响到函数变量是比较平常的,但是在python中是不可能的!
在python中,函数只能修改参数对象本身,但是如果你的参数不可变(比如是数字),又该怎么办呢?
不好意思,没有办法!!!
如果真的先改变参数,那么可以使用一些小技巧,即将值放置在列表中:

6.4.3 关键字参数和默认值(重点)

目前为止,我们使用的参数都叫做位置参数,因为它们的位置很重要,事实上比它们的名字更加重要!
本节引入的功能可以回避位置这个问题,当你的程序越来越大的时候,你就会发现它们的作用也越来越大。
对比一下的两个函数:
有的时候,尤其是参数比较多的时候,参数的顺序是很难记的,为了让事情简单一些,可以提供参数的名字:
可以看出,这样一来,顺序就完全没有影响了,但是参数名和值一定要对应
这类实用参数名提供的参数叫做关键字参数。它的主要作用在于可以明确每个参数的作用,使得阐述的翻译更加清晰。
关键字参数最厉害的在于可以在函数中给参数提供默认值。
例:
注意:除非完全清楚程序的功能和阐述的翻译,否则应该尽量避免同时使用未知参数和关键字参数。一般来说,只有在强制要求的参数个数比可修改的具有默认值的参数个数少的时候,才使用上面提到的参数书写方法。
例如:hello参数可能需要名字作为参数,但是也允许用户自定义名字、问候语、标点。
可以看到,前面的都没有问题,都给出了“name”参数的值,最后一句没有参数,产生了报错。如果为name也赋予默认值,name最后一个语句就不会产生异常报错。

6.4.4 收集参数

有时候需要让用户听任意数量的参数,比如在名字存储中,用户想要一次存储多个名字
试着像下面一样定义函数:
上面的函数包裹的参数前面加了一个星号(*)
可以看出,当只有一个参数的时候,结果作为元组打印出来。多个参数的时候,也是作为元组输出。
参数前的星号将所有值放置在同一个元组中。可以说是将这些值收集起来,然后使用。
让我们再写一个函数:
没问题!所以星号(*)的作用就是“手机其余的位置参数”,如果不提供任何供手机的元素,params就是空元组。
那么能不能处理关键字参数呢?
更改一下试试
例1:
看来双星号(**)可行,返回的是字典,而不是元组。
下面联合使用这些功能:
例2:
与我们的期望相符。

6.4.5 参数搜集的逆过程

将参数收集为元组和字典已经可以实现,而如果使用*和**的话,也可以执行相反的操作。
例:
可以看到,在with_stars中,在定义和调用函数时都是用了星号。而在without_stars中,两处都没有用,但是结果是一样的。所以,星号只在定义函数(允许使用不定数目的参数)或者调用(“分割”字典或者序列)时才有用。
提示:使用拼接操作符“传递”参数很有用,因为这样一来就不用关心参数的个数之类的问题
例如:

6.4.6 练习使用参数

例1:
例2:
例3:

6.5 作用域(难点)

什么是变量?你可以将它们当做是值得名字。在执行x=1赋值语句后,名称x应用到值1这就像用字典一样,键引用值,当然,变量和所对应的值用的是个“不可见”的字典。内建的vars()函数可以返回这个字典:
注意:一般来说,vars返回的字典是不能修改的。
这类“不可见字典”叫做命名空间或者作用域。那么到底有多少个命名空间?除了全局作用于外,每个函数调用都会创建一个新的作用域:
可以看出,这里的foo函数改变了变量x,但是最后输出的x的值并没有变。这是因为当调用foo的时候,新的命名空间就被创建了,它作用域foo内的代码块。赋值语句x=42只在内部作用域(局部命名空间)起作用,它并不影响外部(全局)作用域中的x。函数内的变量被称为局部变量。参数的工作原理类似于局部变量,所以用全局变量的名字作为参数名并没有问题。
那么如果想要在函数内部访问全局变量该怎么办?而且只想读取变量的值(也就是并不想改变全局变量的值),这样还是有实现方法的,可以使用globals函数。
一种特殊的情况,当局部变量和想要引用的全局变量的名称一样的时候,使用globals()函数就可以解决这种问题
例:
接下来,讨论重新绑定全局变量(使变量引用其他新值)。如果在函数内部将值赋予一个变量,它就会自动成为局部变量--除非告知python将其声明为全局变量,那么如何做才能告知python这是一个全局变量呢?
注意:只有在需要的时候才使用全局变量。他们会使代码变得混乱和不灵活,局部变量可以让代码变得更加抽象,因为他们是在函数中“隐藏”的。

嵌套作用域:
python的函数是可以嵌套的,也就是说可以将一个函数放在另一个里面
例:
嵌套一般来说并不是那么有用,但是他有一个很突出的应用,例如需要用一个函数创建另一个,也就意味着可以像下面这样书写函数:
上面的例子,一个函数嵌套在另一个函数里面,外层函数返回昵称函数,也就是函数本身被返回了,但并没有被调用。重要的是返回的函数还可以访问他的定义所在的作用域。换句话说,它带着它的环境(和局部变量)。
每次调用外层函数,它内部的函数都被重新绑定,factor变量每次都有一个新的值。由于python的嵌套作用域,来自multi外部作用于的这个变量,稍后会被内层函数访问
类似于multiByFactor函数存储子封闭作用于的行为叫做“闭包”。

6.6 递归

函数可以调用其他函数,也可以调用自身。
递归:就是引用,调用自身。
无穷递归:类似于while True开始的无限循环,中间没有break或者return语句
有用的递归:1.当函数直接返回值时有基本实例(最小可能性问题)2.递归实例:包括一个或者多个问题较小部分的递归调用。
这里的关键就是将问题分解为小部分,递归不能永远进行下去,因为他总是以最小可能性问题结束,而这些问题又存储在基本实例中。

6.6.1 两个经典:阶乘和幂

计算n的阶乘:
方法一:使用for循环
方法二:递归调用
计算幂指函数:
方法一:循环
方法二:递归

6.6.2 另一个经典:二分法查找

实现方法:

6.7 函数式编程:reduce、filter、map、lambda

例1:
例2:
例3:

6.8 小结

1.抽象:抽象是隐藏多余细节的艺术
2.函数定义:函数使用def语句定义,它们是由语句组成的块,可以从“外部世界”获取值(参数),也可以返回一个或者多个值作为运算的结果。
3.参数:函数从参数中得到需要的信息,也就是函数调用书设置的变量。python中有两类参数:未知参数和关键字参数。参数在给定默认值时是可选的。
4.作用域:变量存储在作用域(也叫作命名空间)中,python中主要有全局作用域和局部作用域。作用域可以嵌套
5.递归:函数可以调用自身,这种方式叫做递归。一切用递归实现的功能都可以用循环来实现,但有时候递归函数更易读。
6.函数式编程:lambda函数与reduce函数。

啊!这一章终于结束了,折腾了3天了!




posted @ 2017-09-25 21:31  flyme123  阅读(167)  评论(1编辑  收藏  举报