程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长

+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

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

发布于2019-08-06 09:53     阅读(509)     评论(0)     点赞(3)     收藏(5)


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

但实际上这几者之间是有紧密联系的,不然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)]等效
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
>>> 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]]) 调用函数(还提供要传递给函数的参数)

 

 

 

 



所属网站分类: 技术文章 > 博客

作者:hehrie83489

链接:https://www.pythonheidong.com/blog/article/7258/1cd4c37e5effa49369fd/

来源:python黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

3 0
收藏该文
已收藏

评论内容:(最多支持255个字符)