《Python基础教程》第6章 抽象

第6章 抽象

本章会介绍如何将代码组织成函数,并会详细介绍函数的参数、作用域,以及递归在程序中的用途。

懒惰即美德(略)

抽象和结构(略)

创建函数

如下是一个简单的定义函数的例子:

def hello(name):
    print "Hello,", name, "!"
记录函数

如果在函数的开头写下字符串,字符串会被当作函数的一部分进行存储:

def  square(x):
    'Calculate the square of x'
    return x*x

文档字符串可以按照如下方式访问:

square.__doc__

内建函数help在命令行中非常有用,通过如下方式就可以得到关于函数的具体信息:

help(square)
无返回值的函数

虽然没有现实return内容,但是所有函数都返回了东西,当不需要它们返回值的时候它们返回None。

参数详解

值从哪里来

定义函数时候的参数叫做形式参数,调用函数时的参数叫做实际参数。

函数封装的例子
def try_to_change(n):
    n = 'Hello, Entity!'
name  = 'Hello, Gumby!'
try_to_change(name)
print name

$ python hello.py
Hello, Gumby!


看如上例子,我们发现,调用try_to_change后,值在内部发生了改变,但是并没有影响到外部的name。那么再看下面这个例子:

def change(n):
    n[0] = 'Mr.Gumby'
names = ['Mr.Entity', 'Mrs.Lucy']
change(names)
print names

$ python hello.py
['Mr.Gumby', 'Mrs.Lucy']

这里我们可以看到,列表在函数内的改变对外面的列表产生了影响,和前一个例子的区别就在于:第一个例子中,字符串是不可变的,函数内部进行赋值表示指向了一个新的字符串,而第二个例子则并没有改变之前的列表对象,只是改变之前列表的其中一个元素。如果要改变列表你可以直接将列表指向新的对象或者干脆指向None试试:

def change(n):
    n = None
names = ['Mr.Entity', 'Mrs.Lucy']
change(names)
print names

$ python hello.py
['Mr.Entity', 'Mrs.Lucy']

如下用例子告诉你:将代码抽象函数的好处(个人觉得例子还不错,费些周章看看最好手动实现一下还是值得的)。

现在嘉定要写一个根据firstname、middlename、lastname来查找联系人完整名字的程序。复习下前面学习到的知识,用最原始的方法构建一个程序:

contact_book = {}
contact_book["first"] = {}
contact_book["middle"] = {}
contact_book["last"] = {}
me = "Magnus Lie Hetland"

contact_book["first"]["Magnus"] = me
contact_book["middle"]["Lie"] = me
contact_book["last"]["Hetland"] = me

print contact_book['middle']['Lie']

$ python hello.py
Magnus Lie Hetland

如上代码可以实现功能,但是当需要添加多个用户到contact_book的时候,代码的冗长可想而知。

我们可以对所涉及的步骤进行简单的抽象:

contact_book = {} #定义姓名字典
init(contact_book) // #初始化结构
store(contact_book, "Magnus Lie Hetland") #存储单姓名记录
lookup(contact_book, 'middle', 'Lie') //查询符合条件的记录

如下是对关键代码的实现:

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

def lookup(book, label, value):
    return book[label].get(value)

def store(book, full_name):
    names = full_name.split(' ')
    if len(names) == 2: names.append(1, ' ')
    labels = ['first', 'middle', 'last']
    for name, label in zip(names, labels):
        people = lookup(book, label, name)
        if people:
            people.append(full_name)
        else:
            book[label][name] = [full_name]

contact_book = {}
init(contact_book)
store(contact_book, "Magnus Lie Hetland")
store(contact_book, "Magnus Baz Hetland")
print lookup(contact_book, 'middle', 'Lie')
print lookup(contact_book, 'first', 'Magnus')


$ python hello.py
['Magnus Lie Hetland']
['Magnus Lie Hetland', 'Magnus Baz Hetland']

上述代码实现过程中可能有两点需要注意的,一个是book[label].get(value)直接写作book[label][value]是会报错的,因为可能键不存在;第二个是zip的运用,在构思程序的时候,需要比较名字的各个部分在book['first']、book['middle']、book['last']是否存在,自己的代码实现可能会很丑陋,使用zip使这一处理变得简单了。

原书在该部分讨论的一个重点是,为什么要改变参数,如上例我们大概已经知道了,某些数据结构就是用来存储和被改变的;另外一些时候,传递进来的参数是不能改变的,这时候我们可以构建满足需求的结果返回出去即可。

关键字参数和默认值

前面一小节的例子中我们使用参数都是和位置关联的,调用的时候不能错乱了位置。Python还提供一种称作关键字参数的用法,除了不用记忆参数顺序外,还可以设置默认值:

def hello(greeting="Hello", name="World"):
    print greeting + ',', name

hello()
hello(name='MingZe')
hello(name="MingZe", greeting="Nice to meet you")

$ python hello.py
Hello, World
Hello, MingZe
Nice to meet you, MingZe    

收集参数

可以让用户提供任意数量的参数(所谓联合参数)也是Python函数的一个功能,看如下示例:

def print_params(*params):
    print params

print_params()
print_params("Hello")
print_params(1, 2, 3)

$ python hello.py
()
('Hello',)
(1, 2, 3)

还可以将普通的参数和联合参数混合使用:

def print_params(title, *params):
    print title, params

print_params("Params:", 1, 2, 3)

$ python hello.py
Params: (1, 2, 3)

要将关键字参数(key=value的形式)也收录到params中,需要使用**params,注意返回的结果是字典:

$ python hello.py
{'y': 2, 'x': 1, 'z': 3}

将几种参数方式混合起来使用:

def print_params(x, y=3, z=4, *pospar, **keypar):
    print x, y, z
    print pospar
    print keypar

print_params(1, 2, 3, 4, 5, 6, 7, foo=1, bar = 2)

$ python hello.py
1 2 3
(4, 5, 6, 7)
{'foo': 1, 'bar': 2}

学习过了联合参数的使用以后,再回到上面想过的存储个人姓名的程序,就可以通过 *full_names 来一次存储多个姓名了。

反转过程

在上节中我们在函数定义中使用了 *params,然后实际调用的时候传递多个参数,比如1,2,3,参数会被收集到params中; **params和x=1, y=2同理。

反过来看,如果函数定义的是如下形式 def func(x,y),那么调用的时候就可以通过 params=(x, y) func(params), 同样对于关键字参数也是如此,定义def func(params), 调用params={x:1,y:2} func(*params),如下例所示:

def foo(x, y, z, m=0, n=0):
    print x, y, z, m, n

args = (1,2,3)
kwds = {"m":4, "n": 5}
foo(*args, **kwds)


$ python hello.py
1 2 3 4 5
练习使用参数

该书使用了一个综合性稍强的练习题,任务中自己多体味吧。

作用域

我们通过执行形如x=1的赋值语句后,就能通过过x直接访问到值是因为python中做了形如 scope['x'] = 1的操作,这里的scope就是作用域。

作用于分为全局作用域和局部作用域,每个执行函数内部都创建一个局部作用域,看如下例子:

def foo(n):
    n = 42
num = 1
foo(num)
print num

$ python hello.py
1

在函数中我们修改了参数的值,但在函数外面进行访问却仍旧是原来的值,这是因为n作为数字类型,会在函数内被新创建为局部作用域内的值,跟外部传入的不是同一个。

如果要对值进行操作,只要将修改后的值作为函数的返回值返回即可。

在函数内想要访问全局作用域内的值也很简单:

external = 'Brozu'
def foo(param):
    return param + external
print foo("Shayne")

$ python hello.py
ShayneBrozu

递归

递归简单地说就是函数调用自身的程序结构,通常用来解决了一些能够由大化小,规律一致的问题。

两个经典:阶乘和幂

n的阶乘数学表示为 n * (n - 1) * (n - 2) ... * 2 * 1, 函数递归的表现形式如下:

def factorial(n):
    if n == 1:
        return 1
    else:
        return n * factorial(n - 1)
print factorial(10)

$ python hello.py
3628800

另一个常用的例子是幂,我们要计算x的n次幂可能会是这样的:

def  power(x, n):
    if n == 0:
        return 1
    else:
        return x * power(x, n - 1)

print power(10, 3)

$ python hello.py
1000

当然你也可以用循环实现上述代码,甚至效率更加高,但是编程很重要一点是要追求代码的易读,尤其当程序代码量大的时候尤其重要。

另一个经典:二元查找

假定要你查找某个数字是否在某序列中,你可以通过不断取中间值来和查找值进行比较来得到最终的结果(当然要先排序)。如下是一个实现例子:

def  search(sequence, number, left, right):
    midd = (left + right) / 2
    if left == right:
        assert number == sequence[midd]
        return midd
    elif sequence[midd] > number:
        return search(sequence, number, left, midd - 1)
    else:
        return search(sequence, number, midd, right)

seq = [34, 67, 15, 28, 89, 44, 9, 66]
seq.sort()
print search(seq, 67, 0, len(seq))


$ python hello.py
6

你可能感兴趣的:(《Python基础教程》第6章 抽象)