Python基础教程--抽象,函数,参数,递归,作用域

1.懒惰即美德

不要在每次需要的时候都把代码重写一遍

2.抽象和结构

3.创建函数

内建函数callable函数可用来判断函数是否可调用

>>>import math

>>> x = 1

>>> y = math.sqrt

>>> callable(x)            False

>>> callable(y)            True

函数callable在python3.0中不再使用,需要使用表达式hasattr(func,__call__)代替

创建函数

def hello(name):

    return 'Hello, ' + name + '!'

返回斐波那契数列列表

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]

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)

  Calculates the square of the number x.

3.2并非真正函数的函数

没有return语句或者return语句后面没有返回值

只起到结束函数的作用

函数无返回值时,就返回None

4.参数魔法

4.1

形参,写在def语句函数名后面的变量

实参,调用函数时提供的值,是实际参数

4.2参数改变

4.2.1字符串

在函数内为参数赋予新值不会改变外部任何变量的值,

>>> def try_to_change(n):

        n = 'Mr. Gumby'

>>> name = 'Mrs. Entity'

>>> try_to_change(name)

>>> name                           'Mrs. Entity'

具体工作方式为

n = name

n = 'Mr. Gumby'

字符串(数字和元组)是不可改变的,即无法被修改(只能通过新值覆盖)

4.2.2将可变的数据结构如列表用在参数

>>> def change(n):

               n[0] = 'Mr. Gumby'

>>> names = ['Mrs. Entity', 'Mrs. Thing' ]

>>> change(names)

>>> names

['Mr. Gumby', 'Mrs. Thing']

参数被改变了,

>>> n = names

>>> n[0] = 'Mr. Gumby'

当两个变量同时引用一个列表时,他们的确同时引用一个列表

避免这种情况,可以复制一个列表的副本,当在序列中做切片的时候,返回的切片总是一个副本。

>>> names = ['Mrs. Entity', 'Mrs. Thing' ]

>>> n = names[:]

现在n和name包含两个独立(不同)的列表,其值相等

>>> n is names               False

>>> n == names             True

现在改变n则不会影响到names

>>> change(names[;])

4.2.3为什么修改参数

使用函数改变数据结构是将程序抽象化的好方法,假设需要编写一个存储名字并能用名字,中间名或姓查找联系人的程序,可以使用如下数据结构:

storage = {}

storage['first'] = {}

storage['middle'] = {}

storage['last'] = {}

storage这个数据结构存储方式是带有3个键,’first','middle'和‘last’的字典,每个键下面又是一个字典

子字典可以使用名字(名字,中间名或姓)作为键,插入联系人列表作为值,比如要把我的名字加入这个数据结构:

>>> me = 'Magnus Lie Hetland'

>>> storage['first']['Magnus'] = [me]

>>> stroage['middle']['Lie'] = [me]

>>> storage['last']['Hetland'] = [me]

每个键下面都存储了一个以人名组成的列表,本例中,列表中只有我。

先如果想得到所有注册的中间名为lie的人,可如下

>>> storage['middle']['Lie']

['Magnus  Lie Hetland']

加入我姐姐的名字,而且假设不知道数据库中已经存储了什么

>>> my_syster = 'Anne Lie Hetlan'

>>> storage['first'].setdefault('Anne'.[]).append(my_syster)

>>> storage['middle'].setdefault('Lie'.[]).append(my_syster)

>>> storage['last'].setdefault('Hetland'.[]).append(my_syster)

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

标签(比如‘middle')以及名字(比如’Lie')可以作为参数提供给lookup函数使用,这样会获得包含全名的列表

换句话说,如果我的名字已经储存了,可以像下面这样做:

>>> lookup(storage, 'middle', 'Lie')

['Magnus Lie Hetland']

返回的列表和存储在数据结构中的列表是相同的,所以如果列表被修改了,那么也会影响数据结构(没有查询到人的时候就问题不大,因为函数返回的是None)

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]

store函数执行以下步骤:

使用参数data和full_name进入函数,这两个参数被设置为函数在外部获得的一些值

通过拆分full_name,得到一个叫做names的列表

如果names的长度为2,(只有首名和末名),那么插入一个空字符串作为中间名

将字符串"first', 'middle'和'last'作为元组存储在labels中(也可以使用列表)

使用zip函数联合标签和名字,对于每一个(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')

>>> store(MyNames, 'first', 'Robin')

['Robin Hood', 'Robin Licksley']

>>> store(MyNames, 'Mr. Gumby')

>>> store(MyNames, 'middle', ' ' )

['Robin Hood', 'Robin Locksley', 'Mr. Gumby']

4.2.4参数不改变

在某些语言中(如C++),重绑定参数并使这些改变影响到函数外的变量是很平常的事,但在python中是不可能的,

函数只能修改参数对象本身,但是如果参数不可变---如数字,怎么办?

应该从函数中返回所有需要的值(如果值多于一个的话就以元组形式返回),

例如将变量的数值增1的函数:

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

4.3关键字参数和默认值

目前位置所用的参数都是位置参数,因为它们的位置很重要,事实上比名字更重要,但是很难记住

使用参数名提供的参数叫做关键字参数,主要作用在于可以明确每个参数的作用,

def hello_1(greeting, name):

       print '%s, %s!', %(greeting, name)

def hello_2(name, greeting):

       print '%s, %s!', %(name, greeting)

>>> hello_1('Hello', 'world')                  Hello, world!

>>> hello_2('Hello', 'world')                  Hello. world!

提供参数的名字

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

如果只提供name参数,

>>> hello_3(name='Gumby')                   Hello, Gumby!

位置和关键字参数可以联合使用,把位置参数放置在前面即可。

但是除非完全清楚程序的功能和参数意义,否则应避免混合使用。

4.4收集参数

存储多个名字,位置参数,返回元组*

>>> storage(data, name1, name2, name3)

def print_params(*params):

       print params

>>> 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('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 print_params_4(x, y, z=3, *pospar, **keypar):

    print x,y, z

    print pospar

    print keypar

结果

>>> print_params_4(1,2,3,5,6,7,foo=1, bar=2)

1 2 3

(5,6,7)

{'foo':1, 'bar': 2}

>>> print_params_4(1,2)

123

()

{}

怎样实现多个名字同时存储:

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]

使用

>>> d = {}

>>> init(d)

>>> store(d, 'Han Solo')

多个

>>> store(d, 'Luke Skywalker'. 'Anakin Skywalker')

>>> lookup(d, 'last', 'Skywalker')

['Luke Skywalker', 'Anakin Skywalker']

4.5反转过程

在调用而不是定义时使用,

def  add(x, y) : return x + y

params = (1, 2)

>>> add(*params)                 3

对于参数列表来说工作正常,只要扩展部分是最新的就可以

使用双星号运算符来处理字典

>>> params = {'name' : 'Sir Robin', 'greeting' : 'Well met' }

>>> hello_3(**params)                  Well met, Sir Robin!

在定义或者调用函数时使用星号(或双星号)仅传递元组或字典,

>>> def with_stars(**kwds):

             print kwds['name'], 'is', kwds['age'], 'years old'

>>> def withour_stars(kwds):

             print kwds['name'], 'is', kwds['age'], 'years old'

>>> args = {'name': 'Mr.Gumby', 'age': 42}

>>> with_stars(**args)                      Mr.Gumby is 42 years old

>>> without_stars(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(*args, **kwds):

      print "Calling foo!"

      foo(*args, **kwds)

4.6练习使用参数

5.作用域

内建的vars函数将变量和值映射为不可见的字典,变量是值的名字,

>>> x = 1

>>> scope = vars()

>>> scope['x']                  1

>>> scope['x'] += 1

>>> x                       2

一般来说vars返回的字典是不能修改的,因为官方文档说法,结果是未定义的,可能得不到想要的结果

这类不可见的字典叫做命名空间或者作用域,

除了全局作用域外,每个函数调用都会创建一个新的作用域

函数内的变量为局部变量

慎用全局变量,当参数名相同时,全局变量会被局部变量屏蔽

重新绑定全局变量(使变量引用其他新值)

如果在函数内部将值赋予一个变量,它会自动成为局部变量,除非告知python将其声明为全局变量,

>>> x = 1

>>> def change_global():

                global  x

                x = x + 1

>>> change_global()

>>> x                                    2

嵌套作用域,python的函数是可以嵌套的,将一个函数放在另一个里面

def  multiplier(factor):

       def multiplyByFactor(number):

            return number* factor

       return multiplyByFactor

外层函数返回里层函数,也就是函数本身被返回了,但并没有被调用(返回的函数还可以访问它的定义所在的作用域),

即它“带着”它的环境和相关的局部变量

每次调用外层函数,内部的函数都被重新绑定,factor变量每次都有一个新值,

由于python的嵌套作用域,来自外部作用域的这个变量稍后会被内层函数访问。

>>> double = multiplier(2)

>>> double(5)            10

>>> multiplier(5)(4)       20

类似multiplyByFactor函数存储子封闭作用域的行为叫做闭包

外部作用域的变量一般来说都是不能进行重新绑定的,但在python3.0中,nonlocal关键字被引入。

它和global关键字使用方式类似,可以让用户对外部作用域(但非全局作用域)的变量进行赋值

6.递归

有用的递归函数包含以下几部分:

当函数直接返回值时有基本实例(最小可能性问题);递归实例,包括一个或多个问题最小部分的递归调用

6.1经典:阶乘和幂

计算数n的阶乘

def factorial(n):

     if n == 1:

         return 1

     else :

         return n * factorial(n-1)

计算幂,pow(2,3)  2*2*2

def power(x, n):

      result = 1

      for i in range(n):

           result *= x

      return result

递归版本

def power(x, n):

      if n == 0:

          return 1

      else:

          return x*power(x, n-1)

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)

>>> seq = [34, 67, 8, 123, 4, 100, 95]

>>> seq.sort()

>>> seq              [4, 8,34, 57. 95, 100, 123]

>>> search(seq, 34)           2

>>> search(seq, 100)           5

函数式编程(map, filter, reduce函数)

map函数将序列中的元素全部传递给一个函数

>>> map(str, 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函数一般不能轻松被列表推导式代替,但通常用不到这个功能,它会将序列的前两个元素与给定的函数联合使用,

并且将他们的返回值和第3个元素继续联合使用,直到整个序列处理完毕,并得到一个最终结果。

例如,需计算一个序列的数字的和,可以使用reduce函数加上lambda x. y: x+y

>>> numbers = [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 33]

>>> reduce(lamda x, y : x + y, numbers)                1161

也可使用内建函数sum

7.小结

抽象,抽象是隐藏多余细节的艺术,定义处理细节的函数可以让程序更抽象

函数定义,函数使用def语句定义,他们是由语句组成的块,可以从外部世界获取值(参数),也可以返回一个或多个值作为运算的结果;

参数,函数从参数中得到需要的信息,也就是函数调用时设定的变量,python中有两类参数:位置参数和关键字参数,参数在给定默认值时是可选的

作用域,变量存在作用域中(命名空间),全局好局部作用域,作用域可以嵌套

递归,函数可以调用自身,如果他这么做了就是递归,一切用递归实现的功能都可以用循环实现,但是有时候递归函数更易读

函数型编程,包括lambda表达式以及map, filter, reduce函数







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值