Python基础教程第六章学习笔记——抽象

来源:互联网 发布:python 网站 编辑:程序博客网 时间:2024/05/18 22:56

6 抽象

介绍如何将语句组织成函数,告诉计算机如何做事(只告诉一次就可以)
还会介绍参数(parameter)和作用域(scope)概念
递归的概念及在程序中的用途

6.1 懒惰即美德

一段代码可能要在多处使用,就可以把这段代码定义为函数,需要的时候直接调用就可以——抽象
fibs = [0, 1]
for i in range(8):
    fibs.append(fib[-2] + fibs[-1])
>>> fibs
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
抽象一点:
num = raw_input('How many numbers do you wants? '
print fibs(num)                                                                       #调用自定义的fibs函数,抽象了代码。需要时直接调用就OK

6.2 抽象与结构

程序应该是非常抽象的,就像“下载网页、计算频率,打印每个单词的频率”一样易懂:
page = download_page()
freps = compute_frequencies(page)
for word, frep in freps:
    print word, frep
#读完就知道程序做什么了,具体细节会在其他地方写出——在独立的函数定义中

6.3 创建函数

函数可以调用:它执行某种行为并返回一个值;一般内建的callable函数可以用来判断函数是否可调用:
>>> import math 
>>> x = 1
>>> y = math.sqrt
>>> callable(x)
False
>>> callable(y)
True
使用def语句即可定义函数
def hello(name):
    return 'Hello, ' + name + '!'
此时就可以调用hello函数:
>>>print hello('world')
Hello, world!
>>> print hello('Gumby')
Hello, Gumby!
#定义斐波那契函数
def fibs(num):
result = [0, 1]
    for i in range(num-2):
        result.append(result[-2] + result[-1])
    return result
#此时直接调用函数就可求斐波那契序列
>>> fibs(10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

6.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函数非常有用。在交互式解释器中使用它,就可得到关于函数,包括他的文档字符串的信息:
>>> help(square)
help on function square in module_main_:

square(x)
    Calxulates the square of the number x

6.3.2 并非真正函数的函数

有的函数并不会返回值,不同于学术意义上的函数
def test():
    print 'This is printed'
    return
    print 'This is not'
>>> x = test()
This is printed
>>> x
>>>
>>> print x
None

6.4 参数魔法—函数参数的用法有时有些不可思议

6.4.1 值从哪里来—一般不用担心这些

6.4.2 我能改变参数吗

在函数内为参数赋予新值不会改变外部任何变量的值:
>>>def try_to_change(n)
    n = 'Mr . Gumby'
>>> name = 'Mr. Entity'
>>> try_to_change(name)
>>> name
'Mr. Entity'
字符串及数字、元组是不可变的,即无法修改。考虑将可变的数据结构如列表作为参数会发生什么:
def change(n):
    n[0] = 'Mr. Gumby'
>>> names = ['Mrs. Entity', 'Mrs. Thing']
>>> change(names)
>>> names
['Mr. Gumby', 'Mrs. Thing']
现在参数在外部也改变了,因为两个变量同时引用一个列表,一个发生发改变另一个随之改变
为避免这种情况,就可以建立一个列表副本,副本改变就不会影响到原列表:n = names[:]

1 为什么我想要修改参数—使用参数改变数据结构(如列表、字典)是将程序抽象化的好方法

#编写一个存储名字,并能用名字、中间名或姓查找联系人的程序
storage = {}
storage['first'] = {}
storage['middle'] = {}
storage['last'] = {}                                       #初始化数据结构storage。storage 是:{'first':{}, 'middle':{}, 'last':{}}
me = 'Magnus Lie Hetland'
storage['first']['Magnus'] = [me]
storage['middle']['Lie'] = [me]
storage['last']['Hetland'] = [me]                 #storage是:{'first':{'Magnus': 'Magnus Lie Hetland'}, 'middle':{'Lie': 'Magnus Lie Hetland'}, 'last':{'Hetland': 'Magnus Lie Hetland'}}
>>> storage['middle']['Lie']
['Magnus Lie Hetland']
my_sister = 'Anne Lie Hetland'
storage['first'].setdefault('Anne', []).append(my_sister)
storage['middle'].setdefault('Lie',[]).append(my_sister)
storage['last'].setdefault('Hetland'. []).append(my_sister)   #storage是:{'first':{'Magnus':'Magnus Lie Hetland', 'Anne':'Anne Lie Hetland'}, 'middle':{'Lie':['Magnus Lie                                                                                                                                        Hetland','Anne Lie Hetland']},'last:{'Hetland':['Magnus Lie Hetland', 'Anne Lie Hetland']}}
>>> storage['first']['Anne']
['Anne Lie Hetland']
>>> storage['middle']['Lie']
['Magnus Lie Hetland', 'Anne Lie Hetland']
#将人名加入到列表的步骤枯燥,尤其加入很多姓名相同的人,因为需要扩展已经存储了那些名字的列表
如果要写个大程序来这样更新列表,那程序会很快变得臃肿笨拙
抽象的要点是要隐藏繁琐的细节,可用函数来实现。
#定义初始化数据结构函数
def init(data):
    data['first'] = {}
    data['middle'] ={} 
    data['last'] = {}
>>> storage = {}
>>> init(storage)
>>> storage
{'middle':{},'last':{},'first':{}}
#获取名字函数
def lookup(data, label, name):
    return data[label].get(name)
>>> lookup(storage, 'middle', 'Lie')
['Magnus Lie Hetland']
#定义扩充数据结构的函数
def store(data, full_name):
    names = full_name.split()
    if len(names) == 2:
        names.insert(1, '')
    labels = 'first', 'middle', 'last'
for label, name in zip(labels, names):
    people = lookup(data, label, name)
    if people:
        people.append(full_name)
    else:
        data[label][name] = full_name
>>> Mynames = {}
>>> init(Mynames)
>>> store(Mynames, 'Magnus Lie Hetland')
>>> lookup(Mynames,'middle', 'Lie')
['Magnus Lie Hetland']
>>> store(Mynames, 'Robin Hood')
>>> store(Mynames, 'Robin locksley')
>>> lookup(Mynames, 'first', 'Robin')
['Robin Hood', 'Robin Lockley']
>>> store(Mynames, 'Mr. Gumby')
>>> lookup(Mynames, 'middle', '')
['Robin Hood', 'Robin Lockley', 'Mr. Gumby']

2 如果我的参数不可变呢——Python中函数只能修改参数对象本身

参数不可变时,应从函数中返回所有你需要的值(若值多于一个,以元组的形式返回):
def inc(x):
    return x + 1
>>> foo = 10
>>> print inc(foo)
11
>>> foo
10
所以想使foo发生改变,就要从函数中返回它
>>> foo = 10
>>> foo = inc(foo)
>>> foo
11
真想改变参数,可使用一点小技巧,将值放置在列表中:
def inc(x):
    x[0] = x[0] + 1
>>> foo = [10]
>>> inc(foo)
>>> foo
[11]

6.4.3 关键字参数和默认值

之前使用的参数都是位置参数,此节将引入关键字参数:程序规模越大,其作用越大
def hello_1(greeting, name):
    print '%s, %s!' % (greeting,name)
>>> hello_1('Hello','world')
Hello, world!
>>> hello_1('world','Hello')
world, Hello!
如果使用关键字参数,即使位置改变,输出也不会发生改变:
>>> hello_1(name='world',greeting='Hello')                           #关键字参数主要作用在于可明确每个参数的作用
Hello, world!
如果函数中参数太多,容易搞混其顺序及对应的含义,使用关键字参数就完全没有这个问题了
>>> store('Mr. Brainsample', 10, 20, 13, 5)
>>>store(patient = 'Mr. Brainsample', hour = 10, minute=20, day=13, month=5)
关键字参数最厉害还在于可以在函数中给参数提供默认值:
def hello_2(greeting='Hello',name='world'):
    print '%s, %s!' %(greeting,name)
>>> hello_2()
'Hello, world!'
>>> hello_2('Greeting')
'Greeting, world!'
>>> hello_2('Greeting', 'universe')
'Greeting,universe!'
>>> hello_2(name='Gumby')
'Hello, Gumby!'
关键字参数和位置参数可以联合使用,但应完全清楚程序的功能和参数的意义,否则应尽量避免混合使用
def hello_3(name, greeting='Hello', punctuation='!')
    print '%s, %s%s' % (greeting,name,punctuation)
>>> hello_3('Mars')
Hello, Mars!
>>> hello_3('Mars','howdy','...')
howdy, Mars...
>>> hello_3('Mars', punctuation='.')
Hello, Mars.
>>> hello_3()
TypeError:......

6.4.4 收集参数——用户可提供任意数量的参数——将参数收集为元组或字典

如之前定义的store函数,可存储多个名字就好了
>>> store(data, name1, name2, name3) 
用户可给函数提供任意多的参数,实现起来并不难:
def print_params(*params):
    print params
>>> print_params('Testing')
('Testing',)
>>> print_params(1, 2, 3)
(1, 2, 3)
所以参数前的星号(*)将所有值放置在同一元组中——将值收集起来,然后使用。
def print_params_2(title, *params):
    print title
    print params
>>> print_params_2('Params:', 1, 2, 3)
Params:
(1, 2, 3)
所以星号(*)的意思就是“收集其余位置的参数”,如果不提供任何供收集的元素,params就是个空元组:
>>> print_params_2('Params: ')
Params:
()
但是星号(*)不可以用来处理关键字参数,需要另外一个能处理关键字参数的“收集”的操作——'**'
def print_params_3(**params):
    print params
>>> print_params_3(x=1,y=2,z=3)
{'y':2, 'x':1, 'z':3}                                           #返回的是字典而不是元组
联合使用:
def print_params_4(x, y, z=3, *pospar, **keypar):
    print x, y, z
    print pospar
    print keypaer
>>> print_params_4(2, 3, 4, 5, 6, 7, foo=1, bar=2)
2 3 4
(5, 6, 7)
{'foo': 1, 'bar': 2}
>>> print_params_4(1,2)
1 2 3
()
{}
#实现多个名字同时存储:
def store(data, *full_names):
    for full_name in full_names:
        names = full_name.split()
        if len(names) == 2: names.insert(1,'')
        labels = 'first', 'middle', 'last'
        for label, name in zip(labels,names):
            people = lookup(data, label, name)
            if people:
                people.append(full_name)
            else:
                data[label][name] = [full_name]

6.4.5 反转过程——使用*和**也可以执行收集的相反操作(逆过程)

def add(x, y):
    return x + y
>>> params = (1,2)
>>> add(*params)                 *不是收集参数,而是分配它们到另一端——*在调用函数时使用而不是定义时使用
3
def hello_3(greeting='Hello',name='world')
    print '%s, %s' % (greeting,name)
params = {'greeting': 'Well met', 'name': 'Sir Robin'}
>>> hello_3(**params)
Well met, Sir Robin
#在定义和调用中都使用*或**,与在定义和调用中都不用*和**得到同样的效果。所以*或**只有在定义或调用单独使用时才有用
def withstars(**kwds):
    print kwd['name'], 'is', kwd['age'], 'years old'
def withoutstars(kwds):
    print kwd['name'], 'is', kwd['age'], 'years old'
>>> args = {'names': 'Mr Gumby', 'age': 42}
>>> withstars(**args)
Mr Gumby is 42 years old
>>> withoutstars(args)
Mr Gumby is 42 years old
使用拼接(Splicing)操作符“传递”参数很有用,因为不用关心参数的个数之类的问题:
def foo(x, y, z, m=0, n=0):
    print x, y, z, m, n
def call_foo(*arg, **kwds):
    print 'Calling foo!'
    foo(*arg, **kwds)

6.4.6 练习使用参数

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 redundant parameters:', others
    return pow(x, y)
def iterval(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')
Once upon a time, there was a king called Gumby.
>>> print story(name='Sir Robin', job='brave knight')
Once upon a time, there was a brave knight called Sir Robin.
>>> params = {'job':'language', 'name':'Python'}
>>> story(**params)
Once upon a time, there was a language called Python
>>> del params['job']
>>> print story(job='stroke of genius', **params)
Once upon a time, there was a stroke of genius called Python.

>>> power(2, 3)
8
>>> power(3, 2)
9
>>> power(y=3, x=2)
8
>>> params=(5,)*2
>>> power(*params)
3125
>>> power(3, 3, 'Hello, world)
Received redundant parameters: ('Hello, world',)
27


>>> iterval(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> iterval(1, 5)
[1, 2, 3, 4]
>>> iterval(3, 12, 4)
[3, 7, 11]
>>> power(*iterval(3,7))
Received redundant parameters:(5, 6)
81

6.5 作用域

x = 1  变量和所对应的值用的是个不可见的字典:x引用1.
这类不可见字典叫做命名空间或作用域,每个函数调用都会创建一个新的作用域:
def foo():  x=42
>>> x = 1
>>> foo()
>>> x
1
最终x的值还是1,因为调用函数foo时,新的命名空间被创建,它作用于foo内的代码块,并不影响全局中的x。函数内的变量被称为局部变量,所以用全局变量名做局部变量名没有问题。
def output(x): print x
>>> x= 1
>>> y = 2
>>> output(y)
2
需要在函数内访问全局变量怎么办?如果只想读取变量的值(不想重新绑定变量),一般没问题。
>>> def combine(parameter): print parameter + external
>>> external = 'berry'
>>> combine('Shrub')
Shrubberry
但是这样引用全局变量容易引发很多错误,所以还要慎重使用。
屏蔽的问题:如果局部变量或参数的名字和想访问的全局变量名相同,全局变量会被局部变量屏蔽,从而不能访问。
此时可使用globals函数获取全局变量值——返回全局变量的字典
def combine(parameter):
    print parameter + globals()['parameter']
>>> parameter = 'berry'
>>> combine('Shrub')
Shrubberry
重新绑定全局变量——向Python声明其为全局变量,否则其自动成为局部变量:
>>> x = 1
def change_global():
    global x
    x = x+1
>>> change_global()
>>> x
2            
嵌套作用域——讲一个函数放入另一个里面
def multiplier(factor):
    def multiplyByFactor(number):
        return number*factor
    return multiplyByFactor
>>> double = multiplier(2)
>>> double(5)
10
>>> multiplier(5)(4)
20

6.6 递归——函数可调用自身

6.6.1 两个经典:阶乘和幂

阶乘,用循环来编写:
def factorial(n):
    result = n
    for i in range(1,n):
        result *= i
    return result
递归的版本:
1的阶乘是1
大于1的阶乘是n*(n-1)的阶乘
def iterfactorial(n):
    if n == 1:
        return 1
    else:
        return n*iterfactorial(n-1)
计算幂,pow函数或**运算符:数自乘n-1次的结果
改为循环的编写:
def power(x, n):
    result = 1
    for i in range(n):
        result *= x
    return result
改为递归版本:
对任意数字x,power(x,0)  =1
对任意大于0的数来说,power(x,n)=x*power(x,n-1)
def iterpower(x, n):
    if n == 0:
        return 1
    else:
        return x*iterpower(x,n-1)

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

查找1-100中的一个数
若上下限相同就是要猜的数字
否则找两个数字的中点(上下限的平均值),查找数字在左侧或右侧,继续查找数字在的那部分。——要查找的数字小于中间数字,则数字在左侧,反之在右侧。
def search(sequence, number, lower, upper):
    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)

函数到处放——函数型编程有一些很有用的函数:map、filter和reduce
map:使用map函数将序列中的元素全部传递给一个函数:
>>> map(str, range(10))  #等同于[str[i] for i in range(10)]
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
filter函数可基于一个返回布尔值的函数对元素进行过滤:
>>> def func(x):
        return x.isalnum()
>>> seq = ['foo', 'x41', '?!', '***']
>>> filter(func,seq)
['foo', 'x41']
使用列表推导式也可以得出:
>>> [x for x in seq if x.isalnum()]
['foo', 'x41']
还可以使用lambda表达式的特性,可以创建短小的函数。
>>> filter(lambda x: x.isalnum(), seq)
['foo','x41']
reduce函数会将序列的前两个元素与给定的函数联合使用,并将它们的返回值和第三个元素继续联合使用,直到整个序列都处理完毕,并得到一个最终结果。
>>> numbers = [72, 101, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]
>>> reduce(lambda x, y: x+y, numbers)
1161

6.7 小结—抽象的常见知识及函数的特殊知识

抽象:隐藏多于细节的艺术。定义处理细节的函数,可让程序更加抽象
函数定义:使用def语句定义,由语句组成的块
参数:提供函数需要的信息——函数调用时设定的变量:位置参数和关键字参数,参数给定默认值时是可选的。
作用域:变量存储在作用域:全局作用域和局部作用域,作用域可嵌套。
递归:函数调用自身,一切用递归实现的功能都可以用循环来实现。
函数型编程:lambda表达式及map、filter和reduce函数

6.7.1 本章新函数

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]])               #调用函数,可以提供参数
阅读全文
0 0
原创粉丝点击