Python基础教程【读书笔记】 - 2016/6/26

时间:2023-03-09 14:36:47
Python基础教程【读书笔记】 - 2016/6/26

  希望通过博客园持续的更新,分享和记录Python基础知识到高级应用的点点滴滴!

第一波:第6章  抽象

[总览] 介绍函数、参数parameter、作用于scope概念,以及递归概念。

[6.1] 函数 - 懒惰即美德!

  将程序的具体细节抽象为函数。

[6.2] 抽象和结构

  抽象可以节省代码工作量,关键易使程序让人读懂。程序应该是非常抽象额,就像“下载网页、计算词频、打印单词频率”描述一样易懂。

page = download_page()

freqs = compute_frequencies(page)

for word,freq in freqs:

  print word,freq

[6.3] 创建函数

  函数可调用,并返回一个值。内建callable函数可判断函数是否可调用。

  函数callable在Python3.0中需要使用表达式hasattr(func.__call__)代替。

  使用def定义函数,return用来从函数中返回值。

def fibs(num):

  result = [0,1]

  for i in range(num-2):

    result.append(result[-2] + result[-1])

  return result

[6.3.1] 记录函数

  给函数写文档,除加入注释(以#开头),还可直接在函数def语句内写上字符串。在函数的开头写下的字符串,它就会作为函数的一部分进行存储,称为文档字符串。

  文档字符串可以function.__doc__方式访问。__doc__使函数属性,双下划线表示它是个特殊属性。

  或者使用内建help函数,如help(function_name),可以得到关于函数,包括文档字符串的信息。

[6.3.2] 并非真正函数的函数

  Python的有些函数并不返回任何东西,没有return语句或有return但后面没有跟任何值得函数,将不返回值。此时return语句只起到结束函数作用。

  更准确的将,所有的函数的确都返回了东西:当不需要函数返回值的时候,函数就返回None。

[6.4] 参数魔法

[6.4.1] 值从哪里来

  卸载def语句中函数名后面的变量通常叫形式参数,而调用函数的时候提供的值使实际参数,或者称为参数。

[6.4.2] 我能改变参数吗

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

def try_to_change(n):

  n = 'Mr. New'

name = 'Mrs. Old'

try_to_change(name)

name

'Mrs. Old'

  参数n获得了新值,但是没有影响到name变量。n实际上是个完全不同的变量。当变量n改变的时候,变量name不变。即当在函数内部把参数重绑定(赋值)的时候,函数外部的变量不会受到影响。

  注意:参数存储在局部作用于local scope内。

  字符串(以及数字和元组)是不可变的,考虑一下如果将可变的数据结构如列表用作参数的情况:

def change(n):

  n[0] = 'Mr. Change_New'

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

change(names)

names

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

  当两个变量同时引用一个列表的时候,它们的确是同时引用一个列表。如果避免出现这种情况,可以复制一个列表的副本,在序列中做切片,返回的切片总是一个副本。如果复制了整个列表的切片,即得到列表的一个副本。

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

n = names[:] #现在n和name是两个不同的列表。可通过is或==理解。原列表是安全的。

  1.为什么我想要修改参数

  使用函数改变数据结构是将程序抽象画的好方法。抽象的要点就是通过函数实现隐藏繁琐的细节。

def init(data):

  data['first'] = {}

  data['middle'] = {}

storage

{'first':{},'middle':{},'last':{}}

[6.4.3] 关键字参数和默认值

  位置参数和关键字参数。慢慢习惯使用这个功能以后,就会发现程序规模越大,关键字参数的作用也越大。关键字参数主要作用在于可以明确每个参数的作用。关键词参数最厉害的地方在于可以在函数中给参数提供默认值。

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

[6.4.4] 收集参数

  提供任意数量的参数是很有必要且重要的。

def print_params(*params):print params

('Testing',)

  结果作为元祖打印出来。参数前的星号*将所有值放置在同一个元祖中。可以说是将这些值收集起来没然后使用。还可以联合普通参数使用。*星号的意思就是“收集其余的位置参数”。注意,此处强调位置参数

def print_params(**params):print params

print_params(x=1,y=2,z=3)

{'x':1,'y':2,'z':3}  # f返回的是字典而不是元组

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

  print x,y,z

  print pospar

  print keypar

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

1 2 3

(5,6,7)

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

[6.4.5] 反转过程

[6.4.6] 练习使用参数

  位置参数,关键字参数,*,**

[6.5] 作用域

  到底什么是变量?你可以把他们看作是值的名字。在执行x=1赋值语句时,名称x引用到值1。这就像用字典一样,键引用值,当然变量和所对应的值用的是个“不可见”的字典。实际上这个么说已经很接近真实情况啦。内建的vars函数可以返回这个字典:

x=1

scope = vars()

scope['x]

  这类“不可见字典”叫做命名空间或者作用域。到底有多少个命名空间?除了全局作用域外,每个函数调用都会创建一个新的作用域。

def foo():x=42

x = 1

foo()

x

1

  这里的foo函数改变了变量x,但是在最后的时候,x并没有变。这是因为当调用foo的时候,新的命名空间就被创建了,它作用于foo内的代码块。赋值语句x=42只在内部作用域(局部命名空间)起作用,所以它并不影响外部(全局)作用域中的x。

  函数内的变量被称为局部变量local variable,这是与全局变量相反的概念。参数的工作原理类似于局部变量,所以用全局变量的名字作为参数名并没有问题。

  【待深入问题:局部变量与全局变量屏蔽问题,重绑定全局变量问题,嵌套作用域问题】

[6.6] 递归

  函数可以调用自身。递归简单来说就是引用自身。

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

    当函数直接返回值时有基本实例(最小可能性问题);

    递归实例,包括一个或者多个问题最小部分的递归调用。

  这里的关键就是讲问题分解为小部分,递归不能永远继续下去,因为它总是以最小可能性问题结束,而这些问题又存在基本实例中的。每次函数被调用时没针对这个调用的新命名空间会被创建,意味着当函数调用“自身”时,实际上运行的是两个不同的函数(或者说是同一个函数具有两个不同的命名空间)。

[6.6.1] 两个经典:阶乘和幂

  n的阶乘定义为nx(n-1)x(n-2)x...x1

    1的阶乘是1;

    大于1的数n的阶乘是n乘n-1的阶乘

def factorial(n):

  if n == 1:

    return 1

  else:

    return n * factorial(n-1)

  计算幂,就像内建的pow函数或者**运算符一样。

    对于任意数字x来说,power(x,0)是1;

    对于任意大于0的数来说,power(x,n)是x乘以(x,n-1)的结果。

def power(x,n):

  if n == 0:

    return 1

  else:

    return x * power(x,n-1)

[6.6.2] 另一个经典:二元查找

  二元查找binary search。

  首先定义二元查找算法的递归:

    如果上下限相同,那么就是数字所在位置,返回;

    否则找到两者的重点(上下限的平均值),查找数字是在左侧还是在右侧,继续查找数字所在的那半部分。

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)

  提示:标准库中的bisect可以非常有效地实现二元查找。

[6.6.3] 有用的函数

  map、filter、reduce函数

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

map(str,range(10))  # equivalent to [str(i) for i in range(10)]

  filter函数可以基于一个返回布尔值的函数对元素进行过滤:

filter(func,seq)

  lambda表达式,可以创建短小的函数。

filter(lambda x: x.isalnum(),seq)

  reduce函数会将序列的前两个元素与给定的函数联合使用,并且将他们的返回值和第三个元素继续联合使用,直到整个序列都处理完毕,并且得到一个最终结果。

numbers = [77,102,33,45,23,62,89,201]

reduce(lambda x,y:x+y,numbers)

[6.7] 小结

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

  函数定义:函数使用def语句定义。由语句组成的块,可以从”外部“获取参数,可以返回一个或者多个结果。

  参数:即函数调用时设定的变量。python中有两类参数:位置参数和关键字参数。

  作用域:变量存储在作用(也叫命名空间)中。两类作用域:全局作用域和局部作用域,作用域可嵌套。

  递归:调用函数自身。

  函数式编程:Python有一些函数型编程的机制。包括lambda表达式以及map、filter、reduce函数。