Python入门基础第十三课--抽象(一)

    1.前言

    这章的内容可能有点多,也有可能有些难以理解。这一章,我们会介绍到:抽象、函数定义、参数、作用域、递归、函数式编程。其中有的是新的概念,有的是原来的旧的概念。不论是新的还是旧的,都需要我们去及时关注和整理,也需要我们去掌握。难理解的概念我会说的很详细,请大家不要嫌麻烦,认真理解。为后面重要内容的学习奠定一定的基础。下面就开始我们的学习!

    2.抽象与结构

    我们首先来看一个例子,是我们熟悉的斐波那契数列的程序实现:

#Fibonacci number
#author youzi
#coding:utf-8
fibs=[0,1]
num=int(raw_input("How many Fibonacci number do you want: "))

for i in range(num-2):
    fibs.append(fibs[-2]+fibs[-1])
print fibs

    在这里,我稍微地设计的人性化了一点。用户可以输入自己的数字作为动态范围的长度使用,实现方式当然是采用我们熟悉的for循环,一目了然。再来看下面这样的设计:

num=raw_input('How many numbers do you want? ')
print fibs(num)

    这种程序的设计更加抽象一些,但是很清楚。读入数值,然后打印出结果。事实上计算斐波那契数列是由一种抽象的方式来完成的:只需要告诉计算机去做就好,不同特别说明应该怎么做,名为fibs的函数将会被创建,然后在需要的地方调用它就可以咯。如果这个函数要被调用很多次,这么做的话会很省力!

    抽象的这种形式可以让人们很轻松的读懂计算机程序,有时候你只需要关注关键的几步就能得到你想要的结果,而不是里面实现的每一步我都需要去额外的特别关注,那岂不是很麻烦,失去了抽象设计的意义。

    3.创建函数

    在前面的入门基础第三课里面,我们简单的介绍了一下函数的内容,函数可以被创建,也可以被调用。我们也讲了函数调用的方法,下面再来看一些用法。

    还是前面的斐波那契数列创建例子,我们将它封装到一个具体的函数里面:

def fibs(num):
    result=[0,1]
    for i in range(num-2):
        result.append(result[-2]+result[-1])
    return result

print fibs(10) 

    有了这样的函数以后,我们再想生成斐波那契数列的时候就可以直接调用fibs函数,像fibs(10)、fibs(15)这样的调用就可以叨叨我们的目的,这也是将具体的实现过程封装到了函数里面,也是抽象形式的一种体现。需要注意的是这里的函数名字和里面的变量都是随自己取的,但是函数结尾的return语句是不可或缺的,用来从函数中返回值的。

   3.1文档化函数

    假如你为了实现某个特定的功能编写了一个比较难以理解的函数,你想让用户在调用或者查看的时候能够清楚了理解它是什么功能,你就需要给这个函数编写一定的介绍文档,我们称为文档字符串,具体实现方式如下所示:
def square(x):
    'Calculates the square of the number x.'
     return x*x

    然后你就可以去调用它了,文档字符串可以按如下方式访问:

>>>square.__doc__
'Calculates the square of the number x.'

    如果你觉得这样方式有些麻烦,还有个很好用的内建函数help,是很有用的。在shell里面去使用它,就可以得到关于函数,包括它的文档字符串的信息。

    3.2参数

    3.2.1参数从何而来

    函数被创建好以后,传入的值是从哪里来的呢?一般来讲不用太关注这些问题,能保证函数在被提供给可接受参数的时候正常工作就行,参数的传入要是不正确的话会导致程序失败。

    函数通过它的参数获取到值,那么这些值我们可以改变吗?参数只是变量而已,你在函数内为参数赋予新的值不会改变外部任何变量的值。来看下面这些例子:                   

>>> def try_to_change(n):n='Mr. Gumby'
... 
>>> name='Ms .Entity'
>>> try_to_change(name)
>>> name
'Ms .Entity'

    再来看看具体的工作方式:

>>> name='Mrs. Entity'
>>> n=name
>>> n='Mr. Gumby'
>>> name
'Mrs. Entity'

    结果是显而易见的,我们只是将name参数的值传给了n,并没有改变name参数的值,当在函数内部把参数重新绑定的时候,函数外的变量不会受到影响。

    前面说过,字符串、数字和元组是不能被修改的我们只能用新的值去覆盖它们。但是除了上面说过的,不要忘了还有列表,当我们利用列表作为参数的时候回发生什么,我们来看看:

>>> def change(n): n[0]='Mr.Gumby'
... 
>>> names=['youzi','Alice']
>>> change(names)
>>> names
['Mr.Gumby', 'Alice']

    哎呦,怎么和前面说的不一样,参数被改变了。这就是这个例子和前面说的例子重要的区别。我们来看看它的具体实现方式:

>>> names=['youzi','Alice']
>>> n=names
>>> n[0]='Mr.Gumby'
>>> names
['Mr.Gumby', 'Alice']

    当两个变量同时指向一个列表的时候,它们确实是同时引用一个列表。如果要避免这种情况,可以复制一个列表的副本。来看看:

>>> names
['Mr.Gumby', 'Alice']
>>> n=names[:] #制作一个names的副本
>>> n is names #同一性
False
>>> n==names #值相等
True

    在这样的程序执行完以后,n和names是完全独立的两个列表,但是两个的值是相等的。参数n相当于是names的一个副本,如果现在去和上面一样修改n就不会影响到names。具体的实现大家可以仿照上面自己实现。

    使用函数改变数据结构是这一种将程序抽象化的好方法,抽象的要点就是隐藏更新时候繁琐的细节,这个过程可以用函数来实现,来看看下面的例子,我们利用这样函数来初始化数据结构。

def init(data):
     data['first']={}
     data['middle']={}
     data['last']={}

    然后利用这样方式来使用它:

>>>storage={}
>>>init(storage)
>>>storage
{'middle':{}, 'last':{}, 'first':{}}

    我们可以很清楚的看到,函数承包了初始化数据结构的工作,让代码更易读。

    假如我们碰到不可变的参数该怎么办?不好意思咩,没有办法,这个时候你应该从函数中返回所有你需要的值。但是如果你真的想改变一下参数,可以用一下下面的小技巧。

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

    看咯,这样就能返回新的值,就是改变了参数。

    3.2.2关键字参数和位置参数

    目前为止我们使用过的参数都是位置参数,因为它们的位置很重要,事实上比它们的名字更加重要。还有一类是关键字参数,我们可以使用关键字参数为函数提供默认值,很方便。位置参数和关键字参数是可以联合使用的,但是使用的时候把位置参数放在最前面就可以了,如果不这样做,解释器就不会知道它们到底谁是谁,也就是不知道它们应该处的位置在哪里。我们直接来看一些例子,会更加明了。

>>> def hello_1(greeting,name): print '%s, %s!' %(greeting,name) #一般一号问候函数
... 
>>> def hello_2(greeting,name): print '%s, %s!' %(name,greeting) #一般二号问候函数
... 
>>> hello_1('Hello','world') #函数测试输出
Hello, world!
>>> hello_2('Hello','world') #函数测试输出
world, Hello!
>>> hello_1(name='world',greeting='Hello') #关键字参数输出
Hello, world!
>>> hello_2(greeting='Hello',name='world') #不同的关键字参数输出
world, Hello!
>>> def hello_3(greeting='Hello',name='world'): print '%s, %s' %(greeting,name) #关键字参数提供默认值的三号问候函数
... 
>>> hello_3() #不给任何参数的默认参数输出
Hello, world
>>> hello_3('Greetings') #只含位置参数的输出
Greetings, world
>>> hello_3('Greetings','universe') #位置参数输出
Greetings, universe
>>> hello_3(name='Gumby') #只提供name参数,默认greeting参数的输出
Hello, Gumby

    3.2.3参数收集

    有时候让用户一次性提供任意数量的参数是很有用的,我们来看一看例子就会明了。

>>> def print_params(*params): print params #利用*来进行参数收集
... 
>>> print_params('Testing')
('Testing',)
>>> print_params(1,2,3)
(1, 2, 3)
>>> def print_params3(**params): print params #利用**来进行参数收集
... 
>>> print_params3(x=1,y=2,z=3)
{'y': 2, 'x': 1, 'z': 3}
    对比上面两种参数收集的方式和最后测试打印结果的区别,你应该不难看出两者的差别。所以,没有问题。星号的意思就将所有的位置参数放置在同一个元组里面,可以说是将这些值收集起来,然后在需要的地方使用。而两个星号是用来处理关键字的收集操作。

    3.2.4参数收集的逆过程

    我们说*和**的方法可以用来收集参数,那么能不能用来进行逆过程的操作呢?逆过程我们说是这样的--不是收集参数,而是分配它们在“另一端”。不是在定义的时候使用,而是在调用的时候使用。我们来看看:

>>> def add(x,y): return x+y
... 
>>> params=(1,2)
>>> add(*params)
3
>>> add(params)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: add() takes exactly 2 arguments (1 given)

  上面这个例子还不明显的话,再来看:

>>> params={'name':'Youzi','greeting':'Hello'}
>>> hello_3(**params)
Hello, Youzi

    4.参数使用练习

#coding:utf-8
#author:youzi
def story(**kwds):
    return 'Once upon a time, there was a %(job)s called %(name)s. ' %kwds

def power(x,y,*others):
    if others:
        print 'Received reduntant parameters: ', others
    return pow(x,y)


def interval(start,stop=None,step=1):
    'Imitates range() for step>0'
    if stop is None:
        start, stop=0,start
    result=[]
    i=start
    while i<stop:
        result.append(i)
        i+=step
    return result

print story(job='king',name='Gumby')
print story(name='youzi',job='brave king')
print pow(2,3)
print power(2,3,'Hello,World')
print interval(10)
print interval(1,5)
print interval(3,12,4)
print power(*interval(3,7))
    上面的程序都是很好理解的,自己动手实践,得出结果详细分析。你会有所收获。
    抽象这部分内容还没完,下一章节的内容我们将继续讲述。


猜你喜欢

转载自blog.csdn.net/qq_34454366/article/details/80251881