《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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 第5章 引用类型(返回首页) 本章内容 使用对象 创建并操作数组 理解基本的JavaScript类型 使用基本类型...
    大学一百阅读 3,205评论 0 4
  • 谁人不爱牡丹花,占尽人间好物华,疑是落川神女做,千娇百媚映朝霞,
    神女作阅读 253评论 0 1
  • 1.elegant ADJ (of people or their behaviour 人或其...
    倦鸟归矣阅读 364评论 0 0
  • 有人说,想结婚;有人说,你说爱我,却不体谅我。我呢?要结婚也不能是因为一句你爱我。可若是连这句话也不说,平时也懒得...
    空灵人生阅读 280评论 0 0