首页 > 代码库 > python基础教程_学习笔记9:抽象

python基础教程_学习笔记9:抽象

抽象

懒惰即美德。

抽象和结构

抽象可以节省大量工作,实际上它的作用还要更大,它是使得计算机程序可以让人读懂的关键。

创建函数

函数可以调用(可能包含参数,也就是放在圆括号中的值),它执行某种行为并且返回一个值。一般来说,内建的callable函数可以用来判断函数是否可调用

>>> import math

>>> y=1

>>> x=math.sqrt

>>> callable(x)

True

>>> callable(y)

False

 

创建函数是组织程序的关键。那么怎样定义函数呢?

使用def(或“函数定义”)语句即可:

>>> def hello(name):

return ‘Hello, ‘ + name + ‘!‘

 

传入不同的参数,得到不同的结果:

>>> print hello(‘signjing‘)

Hello, signjing!

>>> print hello(‘jiao‘)

Hello, jiao!

 

斐波那契数列的获取方法(例如,前10项)为:

>>> f=[0,1]

>>> for i in range(8):

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

 

>>> print f

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

 

如果用函数的方法实现,则为:

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

>>> fibs(16)

[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610]

 

return语句是用来从函数中返回值的。

记录函数

如果想给函数写文档,让后面使用该函数的人能理解的话,可以加入注释(以#开头)。

另外一个方式是直接写上字符串。这里字符串在其它地方可能会非常有用,比如def语句后面(以及在模块或类的开头)。如果在函数的开头写下字符串,它就会成为函数的一部分进行存储,称为文档字符串

>>> def fibs(num):

‘fibs is a funtion:*************‘

result=[0,1]

for i in range(num-2):

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

return result

 

>>> fibs.__doc__

‘fibs is a funtion:*************‘

 

注意:__doc__是函数属性。

内建的help函数非常有用。在交互式解释器中使用它,就可以得到关于函数,包括它的文档字符串的信息。

>>> help(fibs)

Help on function fibs in module __main__:

 

fibs(num)

    fibs is a funtion:*************

 

并非真正函数的函数

数学意义上的函数,总在计算其参数后返回点什么。python的有些函数却并不返回任何东西。

没有return语句,或者虽有return语句但return后边没有跟任何值的函数不返回值。

 

>>> def test():

print "This is printed"

return

print ‘this is not‘

 

>>> x=test()

This is printed

上述函数中的return语句只起到结束函数的作用。

>>> x

>>> print x

None

 

所以,所有的函数的确都返回了东西:当不需要它们返回值的时候,它们就返回None

参数魔法

值从哪里来

写在def语句中函数名后面的变量通常叫函数的形式参数,而调用函数时提供的值是实际参数,或者成为参数。

我能改变参数吗?

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

>>> def try_to_change(n):

n="Hello , signjing"

 

>>> say="Hello , jiao"

>>> try_to_change(say)

>>> say

‘Hello , jiao‘

 

字符串(以及数字和元组)是不可变的,即无法被修改。所以它们做参数的时候也就无需多做介绍。但如果将可变的数据结构如列表做参数的时候会发生什么:

 

>>> def change(n):

n[0]=‘signjing‘

 

>>> names=[‘Li lei‘,‘Han meimei‘]

>>> change(names)

>>> names

[‘signjing‘, ‘Han meimei‘]

 

下面不用函数调用再做一次:

>>> names=[‘Li lei‘,‘Han meimei‘]

>>> n=names

>>> n[0]=‘signjing‘

>>> names

[‘signjing‘, ‘Han meimei‘]

 

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

>>> n=names[:]

>>> n

[‘Li lei‘, ‘Han meimei‘]

>>> names

[‘Li lei‘, ‘Han meimei‘]

>>> n is names

False

>>> m=n

>>> m is n

True

 

 

在某些语言(如c++Ada)中,重绑定参数并且使这些改变影响到函数外的变量是很平常的事情。但在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]

这样就只会返回新值。

 

关键字参数和默认值

目前我们所使用的参数都叫做位置参数,因为它们的位置很重要——事实上比它们的名字更重要。

>>> def hello_1(greeting,name):

print ‘%s,%s‘ %(greeting,name)

 

>>> def hello_2(name,greeting):

print ‘%s,%s‘ %(name,greeting)

 

>>> hello_1(‘hello‘,‘boy‘)

hello,boy

>>> hello_2(‘hello‘,‘girl‘)

hello,girl

 

有些时候(尤其是参数很多的时候),参数的顺序是很难记住的。为了让事情简单些,可以提供参数的名字:

>>> hello_1(greeting=‘hello‘,name=‘boy‘)

hello,boy

>>> hello_1(name=‘boy‘,greeting=‘hello‘)

hello,boy

但参数名和值一定要对应:

>>> hello_2(name=‘boy‘,greeting=‘hello‘)

boy,hello

>>> hello_2(greeting=‘hello‘,name=‘boy‘)

boy,hello

 

这类使用参数名提供的参数叫做关键字参数。主要作用是明确每个参数的作用。

关键字参数最厉害的地方在于可以在函数中给参数提供默认值。当参数具有默认值的时候,调用的时候就不用提供参数了。可以不提供、提供一些或者提供所有的参数:

>>> def hello_3(greeting=‘hello‘,name=‘world‘):

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

 

>>> hello_3()

hello,world!

>>> hello_3(‘Greeting‘)

Greeting,world!

>>> hello_3(‘Greeting‘,‘universe‘)

Greeting,universe!

>>> hello_3(name=‘boys‘)

hello,boys!

 

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

 

注意:除非完全清楚程序的功能和参数的意义,否则应该避免混合使用位置参数和关键字参数。

 

收集参数

有时候让用户提供任意数量的参数是很有用的。试着像下面这样定义函数:

>>> def print_params(*params):

print params

 

>>> print_params(1,2)

(1, 2)

>>> print_params(1,2,‘ab‘)

(1, 2, ‘ab‘)

 

参数前的星号将所有值放置在同一个元组中。可以说是将这些值收集起来,然后使用。

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

()

 

>>> print_params_2(‘Hmm...‘,something=42)

 

Traceback (most recent call last):

  File "<pyshell#73>", line 1, in <module>

    print_params_2(‘Hmm...‘,something=42)

TypeError: print_params_2() got an unexpected keyword argument ‘something‘

我们需要另外一个能处理关键字参数的“收集”操作。

>>> def print_params_3(**params):

print params

 

>>> print_params_3(x=1,y=2,z=3)

{‘y‘: 2, ‘x‘: 1, ‘z‘: 3}

 

 

反转过程

>>> def add(x,y):

return x+y

 

>>> params=(1,2)

>>> add(*params)

3

在调用中使用而不是在定义中使用。

 

对于参数列表来说工作正常,只要扩展到部分是最新的就可以。可以使用同样的技术来处理字典——使用双星号运算符。

 

>>> def hello_3(greeting=‘hello‘,name=‘world‘):

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

 

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

>>> hello_3(*params)

name, greeting!

>>> hello_3(**params)

Well met, Sir Robin!

 

星号只在定义函数(允许使用不定数目的参数)或者调用(“分割”字典或者序列)时才有用。

 

作用域

变量和所对应的值用的是个“不可见”的字典。实际上这么说已经很接近真实情况了。内建的vars函数可以返回这个字典:

>>> x=1

>>> scope=vars()

>>> scope[‘x‘]

1

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

>>> x

2

 

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

 

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

 

如果需要在函数内部访问全局变量,应该怎么办呢?而且只想读取变量的值(也就是说不想重绑定变量),一般来说是没有问题的:

>>> def combine(parameter):

print parameter+external

 

>>> external=‘berry‘

>>> combine(‘Shrub‘)

Shrubberry

 

读取全局变量一般来说并不是问题,但是还是有个会出问题的事情。如果局部变量或者参数的名字和想要访问的全局变量名相同的话,就不能直接访问了。全局变量会被局部变量屏蔽。

如果的确需要的话,可以使用globals函数获取全局变量值,该函数的近亲是vars,它可以返回全局变量的字典(locals返回局部变量的字典)。

 

重绑定全局变量(使变量引用其他新值):如果在函数内部将值赋予一个变量,它会自动成为局部变量——除非告知python将其声明为全局变量。

>>> x=1

>>> def change_global():

global x

x=x+1

 

>>> change_global()

>>> x

2

递归

想到了一个笑话

你要想理解递归,首先得理解递归。

 

好吧,有点冷,继续热乎的话题....

 

递归的定义(包括递归函数定义)包括它们自身定义内容的引用。

需要查找递归的意思,结果它告诉请参见递归,无穷尽也,一个类似的函数定义如下:

>>> def recursion():

return recursion()

 

显然它什么也做不了,理论上讲,它应该永远运行下去。

 

由于每次调用函数都会用掉一点内存,在足够的函数调用发生后,空间就不够了,程序以一个“超过最大递归深度”的错误信息结束:

Traceback (most recent call last):

  File "<pyshell#37>", line 1, in <module>

    recursion()

  File "<pyshell#36>", line 2, in recursion

return recursion()

......

  File "<pyshell#36>", line 2, in recursion

    return recursion()

RuntimeError: maximum recursion depth exceeded

 

这类递归叫无穷递归,类似于while True开始的无穷循环,中间没有breakreturn语句。

 

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

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

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

 

两个经典:阶乘和幂

>>> def factorial(n):

result=n

for i in range(1,n):

result*=i

return result

 

>>> factorial(5)

120

 

递归的实现方式:

>>> def factorial(n):

if n==1:

return 1

else:

return n*factorial(n-1)

>>> factorial(4)

24

 

>>> def power(x,n):

result=1

for i in range(n):

result*=x

return result

 

>>> power(5,3)

125

递归实现:

>>> def power(x,n):

if n==0:

return 1

else:

return x*power(x,n-1)

 

>>> power(4,4)

256

另一个经典:二元查找

此处略;

对象的魔力

创建自己的对象(尤其是类型或者被称为类的对象)是python的核心概念——非常核心。

 

面向对象程序设计中的术语对象基本上可以看作数据(特性)以及由一系列可以存取、操作这些数据的方法所组成的集合。

对象最重要的优点包括以下几个方面:

多态:可以对不同类的对象使用同样的操作;

封装:对外部世界隐藏对象的工作细节;

继承:以普通的类为基础建立专门的类对象;

类和类型

类到底是什么

类就是一种对象。所有的对象都属于某一个类,成为类的实例。

 

当一个对象所属的类是另外一个对象所属类的子集时,前者就被称为后者的子类,相反,后者就称为前者的超类(基类)

 

在面向对象程序设计中,子类的关系是隐式的,因为一个类的定义取决于它所支持的方法。定义子类只是定义更多(也有可能是重载已经存在的)的方法的过程。

创建自己的类

>>> __metaclass__ = type

>>> class Person:

 

def setName(self,name):

self.name=name

def getName(self):

return self.name

def greet(self):

print "Hello,world!I‘m %s." %self.name

 

>>> foo=Person()

>>> bar=Person()

>>> foo.setName(‘abc‘)

>>> foo.getName()

‘abc‘

>>> foo.greet()

Hello,world!I‘m abc.

 

self是对于对象自身的引用。没有它,成员方法就没法访问他们要对其特性进行操作的对象本身了。

 

特效、函数和方法

默认情况下,程序可以从外部访问一个对象的特性。

为了让方法或特性变为私有(从外部无法访问),只要在它的名字前面加上双下划线即可:

>>> class Secretive:

def __inaccessible(self):

print "Bet you can‘t see me..."

def accessible(self):

print "The secret message is:"

self.__inaccessible()

 

>>> s.__inaccessible()

 

Traceback (most recent call last):

  File "<pyshell#52>", line 1, in <module>

    s.__inaccessible()

AttributeError: ‘Secretive‘ object has no attribute ‘__inaccessible‘

>>> s.accessible()

The secret message is:

Bet you can‘t see me...

 

类的内部定义中,所有以双下划线开始的名字都被“翻译”成前面加上单下划线和类名的形式:

>>> Secretive._Secretive__inaccessible

<unbound method Secretive.__inaccessible>

 

简而言之,确保其他人不会访问对象的方法和特性是不可能的,但是这类“名称变化术”就是他们不应该访问这些函数或者特性的强有力信号。

类的命名空间

类的定义其实就是执行代码块,这一点非常有用。

指定超类

将其他类名写在class语句后的圆括号内可以指定超类:

>>> class Filter:

def init(self):

self.blocked=[]

def filter(self,sequence):

return [x for x in sequence if x not in self.blocked]

 

>>> class SPAMFilter(Filter):

def init(self):

self.blocked=[‘SPAM‘]

 

 

Filter是个用于过滤序列的通用类,事实上它不能过滤任何东西:

>>> f=Filter()

>>> f.init()

>>> f.filter([1,3,4])

[1, 3, 4]

 

Filter类的用处在于它可以用作其他类的基类(超类),可以将序列中的“SPAM”过滤出去。

>>> s=SPAMFilter()

>>> s.init()

>>> s.filter([‘abc‘,‘SPAM‘,"SPAM",‘SPAM‘,‘signjing‘])

[‘abc‘, ‘signjing‘]

调查继承

想要查看一个类是否是另一个的子类,可以使用内建的issubclass函数:

>>> issubclass(SPAMFilter,Filter)

True

>>> issubclass(Filter,SPAMFilter)

False

 

如果想要知道已知类的基类(们),可以直接使用它的特殊特性__bases__

>>> SPAMFilter.__bases__

(<class ‘__main__.Filter‘>,)

>>> Filter.__bases__

(<type ‘object‘>,)

 

同样,还能使用isinstance方法检查一个对象是否是一个类的实例:

>>> isinstance(s,SPAMFilter)

True

>>> isinstance(s,Filter)

True

>>> isinstance(s,str)

False

 

SSPAMFilter类的(直接)成员,但也是Filter类的间接成员,因为SPAMFilterFilter的子类。

 

如果想知道一个对象属于哪个类,可以使用__class__特性:

>>> s.__class__

<class ‘__main__.SPAMFilter‘>

多个超类

>>> class Calculator:

def calculate(self,expression):

self.value=http://www.mamicode.com/eval(expression)

 

>>> class Talker:

def talk(self):

print ‘Hi,my value is‘,self.value

 

>>> class TalkingCalculator(Calculator,Talker):

pass

 

超类可以有多个。

在这里,子类不做任何事,从自己的超类继承所有的行为。

这种行为成为多重继承,是个非常有用的工具。

使用多重继承时,有个需要注意的地方。如果一个方法从多个超类继承,必须要注意一下超类的顺序:

先继承的类中的方法会重写后继承的类中的方法。