ch6 抽象

6.1 懒惰即美德

  • 不需要在每次需要代码的时候把代码重写一遍,真正的程序员会让自己的程序更抽象一点

6.2 抽象和结构

  • 抽象可以节省很多工作,计算机乐于处理精确和具体的指令,但人只需要抽象的指导。组织计算机程序也是类似的,程序应该是非常抽象的。

6.3 创建函数

  • 函数是可以调用,它执行某种行为并返回一个值,一般来说,内建的callable函数可以用来判断函数是否可以调用。
>>>import math
>>>x = 1
>>>y = math.sqrt
>>>callable(x)
False
>>>callable(y)
True

可以使用def语句定义函数。

def hello(name):
    return 'Hello, ' + name + '!'

然后可以像使用内建函数一样使用它:

>>>print hello('world')
Hello, world!
>>>print hello('Gumby')
Hello, Gumby!

6.3.1 记录函数

  • 如果想要给函数写文档,让后面使用该函数的人能理解的话,可以加入注释。另外一个方式就是直接写上字符串,例如在def语句后面,在模块或者类的开头。如果在函数的开头写下字符串,它就会作为函数的一部分存储,这称为文档字符串。
def sequare(x):
    'Calculates the sequare of the number x.'
    return x*x
>>>print sequare.__doc__
Calculates the sequare of the number x.
>>>help(sequare)
Help on function sequare in module __main__:

sequare(x)
    Calculates the sequare of x

6.3.2 并非真正函数的函数

  • python的有些函数不返回任何东西,可以没有return语句,或者虽有return语句,但不返回值。
def test():
    print 'This is printed'
    return
    print 'This is not'

这里的return语句只起到结束语句的作用

>>>x = test()
This is printed
>>>x

>>>print x
None

6.4 参数魔法

6.4.1 值从哪里来

  • 编写函数只是给程序需要的部分提供服务,能保证函数在被提供给可接受参数的时候正常工作就行,参数错误的话显然会导致失败。
    写在def语句中函数名后面的变量通常叫做函数的形式参数,而调用函数的时候提供的值是实际参数,或者称为参数。

6.4.2 我能改变参数吗

  • 在函数内为参数赋予新值不会改变外部任何变量的值:
>>>def try_to_change(n):
    n = 'Mr. Gumby'
>>>name = 'Mrs. Entity'
>>>try_to_change(name)
>>>name
'Mrs. Entity'
  • 和下面的例子比较
>>>def change(n):
    n[0] = 'Mr. Gumby'
>>>names = ['Mrs. Entity', 'Mrs. Thing']
>>>change(names)
>>>names
['Mr. Gumby', 'Mrs. Thing']

当两个变量同时引用一个列表的时候,他们的确是同时引用一个列表。为了避免这种情况们可以赋值一个列表的副本,当在序列中做切片的时候,返回的切片总是一个副本。

>>>names = ['Mrs. Entity', 'Mrs. Thing']
>>>n = name[:]
>>> n is names
False
>>>n == names
True

如果现在改变n,则不会影响到names。现在的参数n包含一个副本,而原始的列表是安全的。

1. 为什么我想要修改参数
  • 假设需要编写一个存储名字并且能用名字、中间名、或姓查找联系人的程序,可以使用下面的数据结构
storage = {}
storage['first'] = {}
storage['second'] = {}
storage['last'] = {}

storage这个数据结构的存储方式是带有三个键“first”、“middle”、“last”的字典,每个键下面又存储一个字典。子字典中可以使用名字作为键,插入联系人列表作为值。

>>>me = 'Magnus Lie Hetland'
>>>storage['first']['Magnus'] = [me]
>>>storage['smiddle']['Lie'] = [me]
>>>storage['last']['Hetland'] = [me]

每个键下面存储了以人名组成的列表,本例中,列表中只有我。
如果想要得到所有注册的中间名为Lie的人,可以这样做:

>>>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)

抽象的要点就是隐藏更新时繁琐的细节,这个过程可以用函数实现,下面的例子就是初始化数据结构的函数

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

在编写存储名字的函数前,先写个获得名字的函数

def lookup(data, label, name):
    return data[label].get(name)
def store(data, full_name):
    names = full_name.split()
    if len(names) == 2: names.insert(1,'')
    labels = 'first', 'middle', 'last'
for labels, name in zip(labels, names):
    people = lookup(data, label, name):
    if people:
        people.append(full_name)
    else:
        data[label][name] = [full_name]
2. 如果我的参数不可变呢

在某些语言中,重绑定参数并且使这些改变影响到函数外的变量是很平常的事情,但在python中函数只能修改参数对象本身。如果想要修改不可变的参数,只能从函数中返回所有你需要的值。

>>>def inc(x): return x+1
>>>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)

def hello_2(name, greeting):
    print '%s, %s!' % (greeting, name)

可以提供参数的名字

>>>hello_1(greeting = 'Hello', name = 'world')
Hello, world!
>>>hello_1(name = 'world', greeting = 'Hello')
Hello, world!

这种使用参数名提供的参数叫做关键字参数,它的主要作用在于可以明确每个参数的作用。关键字参数最厉害的地方在于可以在函数中给参数提供默认值。

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!

6.4.4 收集参数

有些时候让用户提供任意数量的参数是很有用的,比如在名字存储程序中,每次只能存一个名字。用户可以给函数提供任意多的参数,实现起来也不难。

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)
>>>print_params_3('Nothing:')
Nothing:
()

处理关键字参数的“收集”操作为“*

def print_params_3(**params):
    print params
>>>print_params_3(x=1, y=2, z=3)
{'z': 3, 'x': 1, 'y':2}
def_params_4(x, y, z=3, *pospar, **keypar):
    print x, y, z
    print pospar
    print keypar
>>>print_params_4(1, 2, 3, 4, 5, 6, 7, foo=1, bar=2)
    1, 2, 3
    (4, 5, 6, 7)
    {'foo': 1, 'bar': 2}
>>>print_params_4(1,2)
    1, 2, 3
    ()
    {}

6.4.5 反转过程

  • 在调用过程中使用和*可以实现和上述相反的操作
def add(x, y): return x + y
>>>params = (1, 2)
>>>add(*params)
3

6.3 作用域

在执行x=1赋值语句后,名称x引用到值1,这就像用字典一样,当然变量和所对应的值用的是个不可见的字典。内建的vars()函数可以返回这个字典。

>>>x = 1
>>>scope = vars()
>>>scope['x']
1
>>>scope['x'] += 1
>>>x
2

这类不可见字典叫做命名空间或者命名域,除了全局作用域外,每个函数调用都会创建一个新的作用域:

>>>def foo(): x =42
...
>>>x = 1
>>>foo()
>>>x
1

函数内的变量称为局部变量,参数的工作原理类似于局部变量,所以用全局变量的名字作为参数名并没有问题。

>>>def output(x): print x
...
>>>x = 1
>>>y = 2
>>>output(y)
2

也可以在函数内读取全局变量,但是当局部变量或者参数的名字和想要访问的全局变量名相同的话,就不能直接访问了,全局变量会被局部变量屏蔽。可以使用globals函数获取全局变量值,该函数的近亲是vars,它可以返回全局变量的字典。

>>>def combine(parameter):
    print parameter + globals()['parameter']
...
>>>parameter = 'berry'
>>>combine('Shrub')
Shrubberry

重绑定全局变量

>>>x = 1
>>>def change_global():
    global x
    x = x + 1
>>>change_global()
>>>x
2

6.6 递归

6.6.1 两个经典:阶乘和幂

def factorial(n):
    result = n
    for i in range(1,n):
        result *= i
    return result
def factorial(n):
    if n == 1:
        reutrn 1
    else:
        return n * factorial(n-1)

6.6.2 二元查找

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)

6.7 小结

  • 抽象:隐藏多余细节
  • 函数定义:使用def语句定义,它们是由语句组成的块,可以从外部世界获取值
  • 参数:函数从参数中得到需要的信息,也就是函数调用时设定的变量。python有两类参数,位置参数和关键字参数,参数在给定默认值时是可选的。
  • 作用域
  • 递归
  • 函数型编程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值