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]
这样就仅仅会返回新值。
keyword參数和默认值
眼下我们所使用的參数都叫做位置參数,由于它们的位置非常重要——其实比它们的名字更重要。
>>> 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
这类使用參数名提供的參数叫做keyword參数。
主要作用是明白每一个參数的作用。
keyword參数最厉害的地方在于能够在函数中给參数提供默认值。当參数具有默认值的时候。调用的时候就不用提供參数了。能够不提供、提供一些或者提供全部的參数:
>>> 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!
位置和keyword參数是能够联合使用的。
把位置參数放置在前面就能够了。
注意:除非全然清楚程序的功能和參数的意义。否则应该避免混合使用位置參数和keyword參数。
收集參数
有时候让用户提供随意数量的參数是非常实用的。
试着像以下这样定义函数:
>>> 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'
我们须要另外一个能处理keyword參数的“收集”操作。
>>> 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開始的无穷循环。中间没有break或return语句。
实用的递归函数包含以下几个部分:
当函数直接返回值时有基本实例(最小可能性问题);
递归实例,包含一个或者多个问题最小部分的递归调用;
两个经典:阶乘和幂
>>> 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
S是SPAMFilter类的(直接)成员。但也是Filter类的间接成员,由于SPAMFilter是Filter的子类。
假设想知道一个对象属于哪个类,能够使用__class__特性:
>>> s.__class__
<class '__main__.SPAMFilter'>
多个超类
>>> class Calculator:
def calculate(self,expression):
self.value=eval(expression)
>>> class Talker:
def talk(self):
print 'Hi,my value is',self.value
>>> class TalkingCalculator(Calculator,Talker):
pass
超类能够有多个。
在这里,子类不做不论什么事,从自己的超类继承全部的行为。
这样的行为成为多重继承。是个非常实用的工具。
使用多重继承时,有个须要注意的地方。假设一个方法从多个超类继承,必须要注意一下超类的顺序:
先继承的类中的方法会重写后继承的类中的方法。
posted on 2019-04-20 14:01 xfgnongmin 阅读(150) 评论(0) 编辑 收藏 举报