此处将抽象和结构、自定义函数、参数的使用、作用域、递归放在一起学习,看起来很怪是不是?

但实际上这几者之间是有紧密联系的,不然Python基础教程(第三版)的作者为什么会把它们放在一起哪?手动滑稽

好了,不说废话了,不乱想了,上硬货!!!

1、抽象和结构

抽象的目的是节省人力,实际上,抽象虽然看起来更高,但实际上抽象是程序能被人们更好地理解的关键所在。

page=download_page()
freqs=compute_frequence(page)
for word ,freq in freqs:
print(word, freq)

这些代码,一看上去就知道要干什么,但具体如何去做,你什么也没说,只是让计算机去下载网页并计算使用频率。至于这些操作的细节,将在其他地方(函数的定义)中给出,之看上去非常易懂,更好理解。

2、自定义函数

函数执行特定的一些操作序列,或许还会返回一个值,你可以调用这个函数(有时候需要提供一些参数,放在函数的参数列表中)。

有的时候,你想调用某个对象,但这可能是非法的,你可以通过callable函数来判断这个对象是否可以被调用。

>>> x=1
>>> callable(x)#x无法被调用
False

函数是结构化编程的核心。但如何去定义函数哪?,使用 def 语句!

最简单的自定义函数示例:

>>> def hello(name):
return 'Hello,'+name+'!' >>> print(hello('jiameng'))
Hello,jiameng!

再稍微复杂一点:

>>> def fibs(num):
result=[0,1]
for i in range(num-2):
result.append(result[-2]+result[-1])#f(n)=f(n-1)+f(n-2),此处获取的是数列,因此要把新的加上,而且最后连个的索引是-1和-2
return result >>> fibs(8)
[0, 1, 1, 2, 3, 5, 8, 13]
>>> fibs(20)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181] #解释一下上边的程序:获取斐波那契数列,获取的列表为result ,

2.1给函数编写文档

有的时候编写的函数并不是那么的简单易懂,这个时候就需要给函数增加一些解释性的语句,以方便理解和使用。

一种方法是添加注释(以#字打头的语句)

另一种方法则是添加独立的字符串,放在函数开头的字符串称为文档字符串,将作为函数的一部分存储起来。

示例

>>> def square(n):
'Calcuates the square of the number n'
return n*n >>> square.__doc__#注意是双下划线
'Calcuates the square of the number n'
>>> help(square)#help是内置函数,可使用它来获取有关函数的信息
Help on function square in module __main__: square(n)
Calcuates the square of the number n

3、神奇的参数

好了,函数的使用和自定义也不过尔尔嘛,但一用上参数,要想理解参数的工作原理就很困难了!

3.1对参数的修改

函数通过参数获取一系列的值,但是参数可以进行修改吗?

参数也不过是变量而已,在函数内部给参数赋值对外部没有任何的影响,看一个例子:

>>> def change(name):
nmae='jiameng' >>> name='Mrs.jia'
>>> change(name)
>>> name
'Mrs.jia'

参数存储在局部作用域中,关于作用于的内容将在后续内容中继续学习。

对于字符串和元组来说,他们是不可变的,这意味着他们无法被修改(只能替换为新值)。所以它们做参数没有什么需要讨论的,但如果是可变的数据类型,例如列表哪?看一个例子:

>>> def change(n):
n[0]='Mr.jia' >>> names=['Mrs.Wang','Mrs.Li']
>>> change(names)
>>> names
['Mr.jia', 'Mrs.Li']

这个例子中,也是在函数中修改了参数,但结果却对外部数据产生了修改,这是为什么哪?实际上,这个例子和上一个示例存在着根本的区别。这个示例中,修改了变量关联的列表。为什么会这样哪?实际上,上面的示例等同于下方不使用函数的示例:

>>> names=['Mrs.Wang','Mrs.Li']
>>> n=names #此处假装传递名字作为参数
>>> n[0]='Mr.jia'#修改列表
>>> names
['Mr.jia', 'Mrs.Li']

这种情况就是将同一个列表赋值给两个变量时,这两个变量将同时指向这个列表。因此,修改任何一个都会导致这个列表发生变化。要避免这种情况,就要创建列表的副本。在对序列进行切片操作时,返回的切片都是副本,因此可以创建一个覆盖整个列表的切片,得到的就是列本的副本。下方示例就是对列表副本的操作:

>>> names=['Mrs.Wang','Mrs.Li']
>>> n=names[:]
>>> n
['Mrs.Wang', 'Mrs.Li']
>>> n[0]='Mr.jia'
>>> n
['Mr.jia', 'Mrs.Li']
>>> names
['Mrs.Wang', 'Mrs.Li']
>>>

使用函数对副本进行操作:

>>> change(nmaes[:])
>>> names
['Mrs.Wang', 'Mrs.Li']

这下好了,不会对元件产生影响。

3.2为什么要修改参数

好了,现在已经对基本的参数修改有了了解,但我们为什么要修改参数哪?这是个问题......

在提高程序的抽象程度方面,使用函数来修改数据结构(如列表或字典)是一种不错的方式。

但如果参数是不可变的(如数)哪?

那不好意思,在这种情况下,应从函数返回有需要的值(如果要返回多个值,可以以元组的形式返回它们)。

如果一定要返回参数,可以玩的花一点——比如将数放在列表中

>>> def inc(x):
x[0]=x[0]+1 >>> foo=[10]
>>> inc(foo)
>>> foo
[11]

3.3关键字参数和默认值

前面使用的参数都是位置参数,因为它们的位置至关重要——事实上比名称还关键,此处介绍的内容能够让你完全忽略位置。

>>> def hello_1(name,gretting):
print('{},{}!'.format(gretting,name)) >>> def hello_2(name,gretting):
print('{},{}!'.format(name,gretting)) >>> hello_1('jiameng','hello')
hello,jiameng!
>>> hello_2('hello','jiameng')
hello,jiameng!

上边的两个函数功能一样,区别只在于参数的位置不同而已。

有的时候参数数量很多,无法完全记住参数的顺序,这个时候极易出错,所以为了简化函数的调用,可以指定参数的名称。

>>> hello_1(gretting='hello',name='jiameng')
hello,jiameng!

上方代码对于函数的调用,在这里,参数的顺序无所谓,不过名称很重要。

像这样使用名称指定的参数称为关键字参数,主要优点是有助于澄清各个参数的作用,虽然输入可能会复杂一些,但每个参数的作用会更加明了。

但是,关键字参数最大的作用是可以指定默认值

>>> def hello_3(gretting='hello',name='jiameng'):
print('{},{}!'.format(gretting, name)) >>> hello_3()
hello,jiameng!

你可以像上方代码中函数一样指定默认值之后,调用函数时可以不提供它!根据需要可以一个参数值也不提供,也可以提供部分参数值或者全部参数值。

>>> def hello_3(gretting='hello',name='jiameng'):
print('{},{}!'.format(gretting, name)) >>> hello_3('Hello')
Hello,jiameng!
>>> hello_3('Hello','JiaMeng')
Hello,JiaMeng!

有的时候你甚至可以结合使用位置参数和关键字参数,但通常你不应这样做,除非你知道这样做的结果。一般而言,除非必不可少的参数很少,而带默认值的可选参数很多,否则不应结合使用位置参数和关键字参数。

>>> def hello_4(name,gretting='hello',punctuation='!'):
print('{},{}{}'.format(gretting,name,punctuation)) >>> hello_4('jiameng')
hello,jiameng!
>>> hello_4('jiameng','HELLO')
HELLO,jiameng!
>>> hello_4('jiameng','HELLO','!!!!')
HELLO,jiameng!!!!
>>> hello_4('jiameng',gretting='Top of the morning to ya')
Top of the morning to ya,jiameng!

这样使用是非常灵活的。

3.4收集参数

实际上我们之前就已经使用到了关于参数的收集,*params,是不是还有印象?

对的,这里就是使用了带星号的参数来进行对参数的收集。

不过这和之前赋值时带星号的变量收集多余的值不同的是,这里将收集到的值存放在元组而不是列表中去。下面看一下例子:

>>> def print_params(title,*params):
print(title)
print(params) >>> print_params('params','1,2,3,4,5,6')
params
('1,2,3,4,5,6',)
>>> print_params('Nothing:')#无参数可收集,将是一个空元组
Nothing:
()

同时与赋值一样,带星号的参数也可以放在其他位置,而不一定非要放在末尾,不过,这种情况下,你需要多做一些工作:使用名称来指定后续参数。

下边的代码给出了错误示例以及正确示例:

>>> def in_the_middle(x,*y,z):
print(x,y,z) >>> in_the_middle(1,2,3,4,5,6,7,8,9)
Traceback (most recent call last):
File "<pyshell#74>", line 1, in <module>
in_the_middle(1,2,3,4,5,6,7,8,9)
TypeError: in_the_middle() missing 1 required keyword-only argument: 'z' >>> in_the_middle(1,2,3,4,5,6,z=7,8,9)
SyntaxError: positional argument follows keyword argument >>> in_the_middle(1,2,3,4,5,6,7,8,z=9)
1 (2, 3, 4, 5, 6, 7, 8) 9

星号不会收集关键字参数,要收集关键字参数,可以使用两个星号。

>>> def print_params_3(**params):
print(params) >>> print_params_3(x=1,y=2,z=3)
{'x': 1, 'y': 2, 'z': 3}

如代码运行结果得到的不是一个元组,而是一个字典

综合运用获取参数的技术,我们给出了以下示例:

>>> def print_params(x,y,z=3,*pospar,**keypar):
print(x,y,z)
print(pospar)
print(keypar) >>> print_params(1,2,3,4,5,6,foo=1,bar=2)
1 2 3
(4, 5, 6)
{'foo': 1, 'bar': 2}

3.5分配参数 

前面介绍了如何将参数收集到元组和字典中,但同样使用两个运算符(*和**)同样可以执行相反的操作(将元组或者字典中的对象分配给参数)

>>> def add(x,y):
return x+y >>> params=[1,2]
>>> add(*params)
3

对于字典也同样适用

>>> def hello_3(gretting='hello',name='jiameng'):
print('{},{}!'.format(gretting, name)) >>> params={'name':'Mrs.Wang','gretting':"I'm glad to see you"}
>>> hello_3(**params)
I'm glad to see you,Mrs.Wang!

如果在定义和调用时都使用*或者**,将只传递元组或者字典,这样还不如不使用它们,这样还会省去很多麻烦。

因此,只有在定义函数(允许可变数量的参数)或调用函数(拆分字典或者序列)时使用,星号才会发挥作用。

>>> def with_start(**keds):
print(keds['name'],'is',keds['age'],'year old.') >>> def without_start(keds):
print(keds['name'],'is',keds['age'],'year old.') >>> args={'name':'jiameng','age':20}
>>> with_start(**args)
jiameng is 20 year old.
>>> with_start(args)
Traceback (most recent call last):
File "<pyshell#118>", line 1, in <module>
with_start(args)
TypeError: with_start() takes 0 positional arguments but 1 was given >>> without_start(args)
jiameng is 20 year old.

使用这些拆分运算符来传递参数很有用,因为这样无需担心参数个数的问题。这在调用超类的构造函数时很有用,这些具体的内容将在后续章节继续学习!!!

4、作用域

变量到底是什么?可以将变量视为指向值的名称。

执行赋值语句x=1,名称x指向值1,这几乎和使用字典一模一样,只是你使用的是看不见的“字典”,有一个名为vars的内置函数,它返回这个看不见的字典:

>>> x=1;
>>> scope=vars()
>>> scope['x']
1
>>> scope['x']+=1
>>> x
2

这种”看不见的字典“称为命名空间或者作用域。那有多少个命名空间哪?除全局作用域外,每一个函数调用都将创建一个。

在函数内部使用的变量称为局部变量

如果要在函数内部访问全局变量,如果只是简单地读取这个变量的值,而不重新关联他,通常不会有任何问题。

>>> def combine(parameter):
print(parameter+exteral) >>> exteral='berry'
>>> combine('shrub')
shrubberry
>>> exteral #像这样访问全局变量通常是bug的根源。一定要谨慎使用全局变量

变量还存在遮盖问题:及局部变量遮盖全局变量

重现关联全局变量(使其指向新值)是另一码事。在函数内部给变量赋值时,该变量默认是局部变量,除非你声明它是全局变量,那么如何告诉Python它是全局变量哪?很简单

>>> def change():
global x
x+=1 >>> change()
>>> x
2

是不是感觉很怪异,但事实上就是这样!!!

另一个就是作用于的嵌套问题,Python函数可以嵌套,嵌套通常作用不大,但有一个很突出的作用就是:使用一个函数来创建另一个函数,可以看一下下面这个例子:

>>> def multiplier(factor):
def multiplierByFactor(number):
return number*factor
return multiplierByFactor >>> double =multiplier(2)
>>> double(5)
25
>>> triple=multiplier(3)
>>> triple(3)
9
>>> multiplier(5)(4)
16 >>> multiplier(5)(4)
20

在这里,一个函数位于另一个函数内布,且外边的函数返回里边的函数。也就是说返回也个函数而不是调用它。重要的是,返回的函数能够访问其定义所在的作用域,换而言之,他携带者自己所在的环境(和相关的局部变量)。

像  multiplierByFactor  这样存储其所在作用于的函数称之为闭包

随之而来的就是作用于的问题,实际上作用域也是可以嵌套的,存储在

5、递归:递归的使用太多了,这里就不再详细讲述递归的定义等,此处给出二分查找的Python代码

>>> def search(sequence, number, lower=0, upper=None):
if upper==None:
upper=len(sequence)-1
if lower==upper:
assert number==sequence[upper]
return upper
else :
middle=(lower+upper)//2
if(number>sequence[middle]):
return search(sequence, number, middle+1, upper)
else :
return search(sequence, number, lower, middle) >>> list1=[1,3,5,7,9,11,23,25,59]
>>> search(list1,7)
3
>>>

>>> search(2)
  Traceback (most recent call last):
  File "<pyshell#187>", line 1, in <module>
     earch(2)
  TypeError: search() missing 1 required positional argument: 'number'

事实上这里仍有几个需要注意的点:

(1)、//,整除

(2)、如果查找不到怎么办?会出现什么样的结果?为什么会这样哪?

函数式编程:

至此,你可能已经熟悉使用函数来完成自己的任务。

Python提供了一些有助于进行这种函数式编程的函数,包括:map 、 filter和reduce。

你可以使用map将序列中所有元素传递给函数

你也可以使用filter个根据布尔函数的返回值来对元素进行过滤

你也可以使用reduce来将序列的前两个元素合二为一,再将结果与第三个元素合二为一,直至处理完整个序列并得到一个结果。

>>> list(map(str,range(10)))#与[str(i) for i in range(10)]等效
['', '', '', '', '', '', '', '', '', '']
>>> def func(x):
return x.isalnum() >>> seq=["foo","x42","?!","***"]
>>> list(filter(func,seq))
['foo', 'x42']
#就这个示例来言,使用列表推导更为简单
>>> [x for x in seq if x.isalnum()]
['foo', 'x42']

事实上,Python提供了一个名为lambda的内置函数,用来创建内嵌的简单的函数(主要供map 、filter、reduce来使用)

>>> number=[1,2,3,4,5,6,7,8,9,10,11,12,13]
>>> from functools import reduce
>>> reduce(lambda x,y:x+y,number)
91

小结:抽象、函数定义、参数、作用域、递归、函数式编程

新学函数:

函数 描述
map(func,seq[, seq, ...]) 对序列中所有元素执行函数
filter(func , seq) 返回一个列表,其中包含对其执行函数时结果为真的所有元素
reduce(func, seq[, initial]) 等价于func(func(func(seq[0], seq[1]), seq[2]) ,...)
sum(seq) 返回seq所有元素的和
apply(func[, args[, kwargs]]) 调用函数(还提供要传递给函数的参数)

 

Python学习5——抽象,涉及抽象和结构、函数的自定义、参数、作用域、递归的更多相关文章

  1. 【python学习笔记】6.抽象

    [python学习笔记]6.抽象 创建函数: 使用def语句定义函数,不用声明参数类型,和返回值类型 def function_name(param1, param2): 'this is docum ...

  2. Python学习--04条件控制与循环结构

    Python学习--04条件控制与循环结构 条件控制 在Python程序中,用if语句实现条件控制. 语法格式: if <条件判断1>: <执行1> elif <条件判断 ...

  3. Python学习系列(四)(列表及其函数)

    Python学习系列(四)(列表及其函数) Python学习系列(一)(基础入门) Python学习系列(二)(基础知识) Python学习系列(三)(字符串) 一.基本概念 1,列表是什么?     ...

  4. python学习7—函数定义、参数、递归、作用域、匿名函数以及函数式编程

    python学习7—函数定义.参数.递归.作用域.匿名函数以及函数式编程 1. 函数定义 def test(x) # discription y = 2 * x return y 返回一个值,则返回原 ...

  5. python学习(六) 抽象

    6.1 懒惰即美德 斐波那契数列: >>> fabs = [0, 1]>>> for i in range(8): fabs.append(fabs[-1] + f ...

  6. Python学习第十八篇——低耦合函数设计思想

    import json 2 def greet_user(filename): 3 try: 4 with open(filename) as f_obj: 5 username = json.loa ...

  7. python学习笔记:第七天(函数)

    Python3 函数 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率. 与C一样,Python提供了许多内建函数,比如print().同 ...

  8. PYTHON 学习笔记2 流程控制工具以及函数定义、匿名函数

    前言 在上一节的学习中.已经介绍了几种基本类型.包括字符串的定义,以及字符串中索引.切片.字符串拼接的使用方法.以及基本的整形数据运算.一些之前都没有了解过的运算符.比如 ** 乘方 //整数除法等. ...

  9. python学习笔记(十二)之函数

    牛刀小试: 定义一个无参函数 >>> def myFirstFunc(): ... print("Hello python") ... print("h ...

  10. python学习笔记(五)、抽象

    不知不觉已经快毕业一年了,想想2018年过的可真舒适!!!社会就像一锅水,不同地方温度不同,2018年的我就身处温水中,没有一丝想要进取之心. 1 抽象 抽象在程序中可谓是神来之笔,辣么什么是抽象呢? ...

随机推荐

  1. 微信小程序 使用字体图标 iconfont

    第一步:在阿里巴巴矢量图标库下载需要的图标 地址:https://www.iconfont.cn/ 添加至项目 第二步:打开在线代码 将在线代码复制 第三步:点击下载至本地下载图标 将下载的downl ...

  2. win10下交换CapLock和Esc按键

    win10下使用vim编辑时,需频繁用Esc键,可是Esc键在键盘左上角,位置遥远,操作不便.可以CapsLock键处在黄金位置,但是几乎无用,看过键盘发展历史,其实是是在发展过程中的意外而已,将两键 ...

  3. @Transactional 注解参数详解

    Transactional参数说明 参数名称 功能描述 readOnly 该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false.例如:@Transa ...

  4. hdu2476(区间dp+dp)

    String painter Time Limit: 5000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) T ...

  5. 2018-2019-2 网络对抗技术 20165202 Exp7 网络欺诈防范

    博客目录 一.实践目标 二.实践内容 简单应用SET工具建立冒名网站 (1分) ettercap DNS spoof (1分) 结合应用两种技术,用DNS spoof引导特定访问到冒名网站.(1.5分 ...

  6. SEQ!org.apache.hadoop.io.LongWritable

    [uhadoop@10-13-109-236 subdir26]$ $HADOOP_HOME/bin/hadoop fs -cat /data/flumeEvents/FlumeData.155980 ...

  7. Java设计模式:代理模式(转)

    代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.这里使用到编程中的一 ...

  8. ios 新建app iphone 、 ipad or universal ?

    很久没有关注这个新建app的  时候 选什么的问题了, 因为我们一般在公司 都是 已经建立好的app 直接 在那上面开发. 所以很久不建立新app 遇到新的app需要你自己去创建的时候 可能就会 有突 ...

  9. osg机械臂施工模拟

    线程 0x2278 已退出,返回值为 0 (0x0). =====IfcTreeWidget==slotObjectsSelected1IfcObjectAttributeExtraction === ...

  10. 本地git仓库推送到服务器自建的git仓库实现目录文件同步教程

    首先,先在服务器上安装git,如果有git的话就不用走这一步了 yum安装git [root@iZuf6fazwjb6lb3z82smzoZ ~]# cd src/ [root@iZuf6fazwjb ...