函数

abs(-20)                                        //结果为:20,绝对值函数

def 函数名称([参数1,参数2,参数3]):

执行语句

return

def test(r):

s=3.14*r*r

return s

s=test(1)

print s                                            //结果为:3.14

cmp(x,y)                                         //比较函数,如果x<y,返回-1,如果x==y,返回0,如果x>y,返回1

float('12.34')                                  //结果为:12.34

str(123)                                          //结果为:'123'

unicode(100)                                //结果为:u'100'

bool(1)                                          //结果为:True

bool('')                                          //结果为:False

空语句 pass ,相当于占位符,让代码先跑起来



据类型检查可以用内置函数isinstance实现:

if not isinstance(x, (int, float)):         raise TypeError('bad operand type')


函数可以同时返回多个值,但其实就是一个tuple。

def test(a):

x=a+1

y=a+2

return x,y

r=test(1)

print r                    //结果为(2,3)

def enroll(name,gender,age=6,city='BeiJing'):    //参数后面加等号即为默认参数,调取函数时选填
    print 'name:',name
    print 'gender:',gender
    print 'age:',age
    print 'city:'city
enroll('Ling','F')
enroll('ling','M',7)
enroll('ling','M',city='ShangHai')
默认参数如用到[],会有个坑,重复调用会记住上次的[],写的时候最好这么写
def add_end(L=None)    //使用None这个不变的对象来实现
    if L is None:
        L=[]
.....
可变参数
def calc(numbers):     sum = 0     for n in numbers:         sum = sum + n * n     return sum
但是调用时需要先组装出一个list或者tuple

calc([1, 2, 3]) 
calc((1, 3, 5, 7)) 
在参数numbers前面加个*,就变为可变参数,调用时就不须组装list和tuple

def calc(*numbers):
calc(1,2,3)
calc()
此时若已有list或者tuple,可以这样调取
nums=[1,2,3]
calc(*nums)
关键字参数
def person(name, age, **kw):     print 'name:', name, 'age:', age, 'other:', kw 
person('Michael', 30)            //结果为:name: Michael age: 30 other: {} 
 kw = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **kw)         //结果为:name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'} 

要注意定义可变参数和关键字参数的语法:

*args是可变参数,args接收的是一个tuple;

**kw是关键字参数,kw接收的是一个dict。

以及调用函数时如何传入可变参数和关键字参数的语法:

可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))

关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

使用*args**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。

递归函数
def fact(n):     if n==1:         return 1     return n * fact(n - 1)

使用递归函数需要注意防止栈溢出。在计算机中,函数调用是通过栈(stack)这种数据结构实现的,每当进入一个函数调用,栈就会加一层栈帧,每当函数返回,栈就会减一层栈帧。由于栈的大小不是无限的,所以,递归调用的次数过多,会导致栈溢出

解决递归调用栈溢出的方法是通过尾递归优化,事实上尾递归和循环的效果是一样的,所以,把循环看成是一种特殊的尾递归函数也是可以的。

尾递归是指,在函数返回的时候,调用自身本身,并且,return语句不能包含表达式。这样,编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

切片
L=[1,2,3,4,5,6,7,8]
L[0:3]                    //结果为:[1,2,3],从索引0开始取,直到取到索引3,但不包括索引3
L[:3]                     //结果为:[1,2,3],索引0可省略
L[-8:]                    //结果为:[1,2,3,4,5,6,7,8],从倒数第八个开始取,冒号后省略就取到最后一个
L[::2]                    //结果为:[1,3,5,7],第三个参数是每隔几个取一个

'ABCDEFG'[1:5:2] //结果为:'BD',可对字符串进行切片

迭代


如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。

默认情况下,dict迭代的是key:
d = {'a': 1, 'b': 2, 'c': 3} for key in d:     print key a c b 
如果要迭代value可以用for value in d.itervalues(),如果要同时迭代key和value,可以用for k, v in d.iteritems()

字符串也可迭代

那么,如何判断一个对象是可迭代对象呢?方法是通过collections模块的Iterable类型判断:

>>> from collections import Iterable >>> isinstance('abc', Iterable) # str是否可迭代 True >>> isinstance([1,2,3], Iterable) # list是否可迭代 True 

最后一个小问题,如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

>>> for i, value in enumerate(['A', 'B', 'C']): ...     print i, value ... 0 A 1 B 2 C 

列表生成式

range(1,11)即为list[1,2,3,4,5,6,7,8,9,10]

>>> [x * x for x in range(1, 11)] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] 


for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

>>> [x * x for x in range(1, 11) if x % 2 == 0] [4, 16, 36, 64, 100] 


还可以使用两层循环,可以生成全排列:

>>> [m + n for m in 'ABC' for n in 'XYZ'] ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ'] 
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' } >>> [k + '=' + v for k, v in d.iteritems()] ['y=B', 'x=A', 'z=C'] 

生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器(Generator)。

要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

>>> L = [x * x for x in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>> g = (x * x for x in range(10)) >>> g <generator object <genexpr> at 0x104feab40>

创建Lg的区别仅在于最外层的[]()L是一个list,而g是一个generator。

我们可以直接打印出list的每一个元素,但我们怎么打印出generator的每一个元素呢?

如果要一个一个打印出来,可以通过generator的next()方法:

>>> g.next() 0 >>> g.next() 1 >>> g.next() 4 >>> g.next() 9 >>> g.next() 16 >>> g.next() 25 >>> g.next() 36 >>> g.next() 49 >>> g.next() 64 >>> g.next() 81 

我们讲过,generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。

当然,上面这种不断调用next()方法实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象:

>>> g = (x * x for x in range(10)) >>> for n in g: ...     print n ... 0 1 4 9 16 25 36 49 64 81 

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):     n, a, b = 0, 0, 1     while n < max:         print b         a, b = b, a + b         n = n + 1 
>>> fib(6) 1 1 2 3 5 8 

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print b改为yield b就可以了:

def fib(max):     n, a, b = 0, 0, 1     while n < max:         yield b         a, b = b, a + b         n = n + 1

这就是定义generator的另一种方法。如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator:

>>> fib(6) <generator object fib at 0x104feaaa0>

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

举个简单的例子,定义一个generator,依次返回数字1,3,5:

>>> def odd(): ...     print 'step 1' ...     yield 1 ...     print 'step 2' ...     yield 3 ...     print 'step 3' ...     yield 5 ... >>> o = odd() >>> o.next() step 1 1 >>> o.next() step 2 3 >>> o.next() step 3 5 

变量可指向变量

f=abs
f(-10)结果为10

既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

map/reduce

map()函数接收两个参数,一个是函数,一个是序列,map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。

def f(x):

return x*x

map(f,range(10))

map(str, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 

再看reduce的用法。reduce把一个函数作用在一个序列[x1, x2, x3...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) 

比方说对一个序列求和,就可以用reduce实现:

>>> def add(x, y): ...     return x + y ... >>> reduce(add, [1, 3, 5, 7, 9]) 25 

首字母大写 'LING'.capitalize()类似的还有大小写upper(),lower()



Python内建的filter()函数用于过滤序列。

和map()类似,filter()也接收一个函数和一个序列。和map()不同的时,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

排序算法


通常规定,对于两个元素xy,如果认为x < y,则返回-1,如果认为x == y,则返回0,如果认为x > y,则返回1


>>> sorted([36, 5, 12, 9, 21]) [5, 9, 12, 21, 36] 

此外,sorted()函数也是一个高阶函数,它还可以接收一个比较函数来实现自定义的排序。比如,如果要倒序排序,我们就可以自定义一个reversed_cmp函数:

def reversed_cmp(x, y):     if x > y:         return -1     if x < y:         return 1     return 0 

传入自定义的比较函数reversed_cmp,就可以实现倒序排序:
>>> sorted([36, 5, 12, 9, 21], reversed_cmp) [36, 21, 12, 9, 5] 
返回函数

def lazy_sum(*args):     def sum():         ax = 0         for n in args:             ax = ax + n         return ax     return sum
当我们调用lazy_sum()时,返回的并不是求和结果,而是求和函数:
>>> f = lazy_sum(1, 3, 5, 7, 9) >>> f <function sum at 0x10452f668>
调用函数f时,才真正计算求和的结果:
>>> f() 25
在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数,即使传入相同的参数:

>>> f1 = lazy_sum(1, 3, 5, 7, 9) >>> f2 = lazy_sum(1, 3, 5, 7, 9) >>> f1==f2 False 

匿名函数


map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])

关键字lambda表示匿名函数,冒号前面的x表示函数参数。

匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。

装饰器

def now():

    print '2015-3-28'
f=now
f()    
f.__name__                        //结果为:'now',函数对象有个__name__属性,可以拿到函数的名字


在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。

decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。

现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:
def log(func):     def wrapper(*args, **kw):         print 'call %s():' % func.__name__         return func(*args, **kw)     return wrapper 

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

@log def now():     print '2013-12-25' 

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

>>> now() call now(): 2013-12-25
@log放到now()函数的定义处,相当于执行了语句:
now = log(now) 

由于log()是一个decorator,返回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量指向了新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。

wrapper()函数的参数定义是(*args, **kw),因此,wrapper()函数可以接受任意参数的调用。在wrapper()函数内,首先打印日志,再紧接着调用原始函数。

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来会更复杂。



一个完整的decorator的写法如下:
import functools  def log(func):     @functools.wraps(func)     def wrapper(*args, **kw):         print 'call %s():' % func.__name__         return func(*args, **kw)     return wrapper
import functools  def log(text):     def decorator(func):         @functools.wraps(func)         def wrapper(*args, **kw):             print '%s %s():' % (text, func.__name__)             return func(*args, **kw)         return wrapper     return decorator 

import functools是导入functools模块。模块的概念稍候讲解。现在,只需记住在定义wrapper()的前面加上@functools.wraps(func)即可。

偏函数

functools.partial就是帮助我们创建一个偏函数的,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2

>>> import functools >>> int2 = functools.partial(int, base=2) >>> int2('1000000') 64 >>> int2('1010101') 85

所以,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

模块

#!/usr/bin/env python # -*- coding: utf-8 -*-  ' a test module '  __author__ = 'Michael Liao'  import sys  def test():     args = sys.argv     if len(args)==1:         print 'Hello, world!'     elif len(args)==2:         print 'Hello, %s!' % args[1]     else:         print 'Too many arguments!'  if __name__=='__main__':     test() 

第1行和第2行是标准注释,第1行注释可以让这个hello.py文件直接在Unix/Linux/Mac上运行,第2行注释表示.py文件本身使用标准UTF-8编码;

第4行是一个字符串,表示模块的文档注释,任何模块代码的第一个字符串都被视为模块的文档注释;

第6行使用__author__变量把作者写进去,这样当你公开源代码后别人就可以瞻仰你的大名;

以上就是Python模块的标准文件模板,当然也可以全部删掉不写,但是,按标准办事肯定没错。

后面开始就是真正的代码部分。

你可能注意到了,使用sys模块的第一步,就是导入该模块:

import sys 

导入sys模块后,我们就有了变量sys指向该模块,利用sys这个变量,就可以访问sys模块的所有功能。

sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称,例如:

运行python hello.py获得的sys.argv就是['hello.py']

运行python hello.py Michael获得的sys.argv就是['hello.py', 'Michael]

最后,注意到这两行代码:

if __name__=='__main__':     test() 

当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

我们可以用命令行运行hello.py看看效果:

$ python hello.py Hello, world! $ python hello.py Michael Hello, Michael! 

如果启动Python交互环境,再导入hello模块:

$ python Python 2.7.5 (default, Aug 25 2013, 00:04:04)  [GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> import hello >>> 

导入时,没有打印Hello, word!,因为没有执行test()函数。

调用hello.test()时,才能打印出Hello, word!

>>> hello.test() Hello, world!

别名

导入模块时,还可以使用别名,这样,可以在运行时根据当前环境选择最合适的模块。比如Python标准库一般会提供StringIOcStringIO两个库,这两个库的接口和功能是一样的,但是cStringIO是C写的,速度更快,所以,你会经常看到这样的写法:

try:     import cStringIO as StringIO except ImportError: # 导入失败会捕获到ImportError     import StringIO 

这样就可以优先导入cStringIO。如果有些平台不提供cStringIO,还可以降级使用StringIO。导入cStringIO时,用import ... as ...指定了别名StringIO,因此,后续代码引用StringIO即可正常工作。

还有类似simplejson这样的库,在Python 2.6之前是独立的第三方库,从2.6开始内置,所以,会有这样的写法:

try:     import json # python >= 2.6 except ImportError:     import simplejson as json # python <= 2.5 

由于Python是动态语言,函数签名一致接口就一样,因此,无论导入哪个模块后续代码都能正常工作。

如果采用面向对象的程序设计思想,我们首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为一个对象,这个对象拥有namescore这两个属性(Property)。如果要打印一个学生的成绩,首先必须创建出这个学生对应的对象,然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。

class Student(object):      def __init__(self, name, score):         self.name = name         self.score = score      def print_score(self):         print '%s: %s' % (self.name, self.score) 

给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法(Method)。面向对象的程序写出来就像这样:

bart = Student('Bart Simpson', 59) lisa = Student('Lisa Simpson', 87) bart.print_score() lisa.print_score() 

面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student:

所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。

面向对象的抽象程度又比函数要高,因为一个Class既包含数据,又包含操作数据的方法。

数据封装、继承和多态是面向对象的三大特点。

类和实例

面向对象最重要的概念就是类(class)和实例(instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的对象,每个对象都有相同的方法,但是各自的数据可能不同。
仍以Student类为例,在Python中,定义类是通过class关键字:
class Student(object):     pass

class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的:

>>> bart = Student() >>> bart <__main__.Student object at 0x10a67a590> >>> Student <class '__main__.Student'>

可以看到,变量bart指向的就是一个Student的object,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Student本身则是一个类。

可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性:
>>> bart.name = 'Bart Simpson' >>> bart.name 'Bart Simpson'

由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把namescore等属性绑上去:

class Student(object):      def __init__(self, name, score):         self.name = name         self.score = score

注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去:

>>> bart = Student('Bart Simpson', 59) >>> bart.name 'Bart Simpson' >>> bart.score 59

普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,仍然可以用默认参数、可变参数和关键字参数。

数据封装

面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的namescore这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:
>>> def print_score(std): ...     print '%s: %s' % (std.name, std.score) ... >>> print_score(bart) Bart Simpson: 59 

但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法:

class Student(object):      def __init__(self, name, score):         self.name = name         self.score = score      def print_score(self):         print '%s: %s' % (self.name, self.score) 

要定义一个方法,除了第一个参数是self外,其他和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self不用传递,其他参数正常传入:

>>> bart.print_score() Bart Simpson: 59 

这样一来,我们从外部看Student类,就只需要知道,创建实例需要给出namescore,而如何打印,都是在Student类的内部定义的,这些数据和逻辑被“封装”起来了,调用很容易,但却不用知道内部实现的细节。

封装的另一个好处是可以给Student类增加新的方法,比如get_grade

class Student(object):     ...      def get_grade(self):         if self.score >= 90:             return 'A'         elif self.score >= 60:             return 'B'         else:             return 'C' 

同样的,get_grade方法可以直接在实例变量上调用,不需要知道内部实现细节:

>>> bart.get_grade() 'C' 

小结

类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;

方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;

通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。

和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同:

>>> bart = Student('Bart Simpson', 59) >>> lisa = Student('Lisa Simpson', 87) >>> bart.age = 8 >>> bart.age 8 >>> lisa.age Traceback (most recent call last):   File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'age'

访问限制

在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。

但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的namescore属性:

>>> bart = Student('Bart Simpson', 98) >>> bart.score 98 >>> bart.score = 59 >>> bart.score 59 

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

class Student(object):      def __init__(self, name, score):         self.__name = name         self.__score = score      def print_score(self):         print '%s: %s' % (self.__name, self.__score) 

改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name实例变量.__score了:

>>> bart = Student('Bart Simpson', 98) >>> bart.__name Traceback (most recent call last):   File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__name' 

这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。

但是如果外部代码要获取name和score怎么办?可以给Student类增加get_nameget_score这样的方法:

class Student(object):     ...      def get_name(self):         return self.__name      def get_score(self):         return self.__score 

如果又要允许外部代码修改score怎么办?可以给Student类增加set_score方法:

class Student(object):     ...      def set_score(self, score):         self.__score = score 

你也许会问,原先那种直接通过bart.score = 59也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

class Student(object):     ...      def set_score(self, score):         if 0 <= score <= 100:             self.__score = score         else:             raise ValueError('bad score') 

需要注意的是,在Python中,变量名类似__xxx__的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用__name____score__这样的变量名。

有些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。

双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:

>>> bart._Student__name 'Bart Simpson' 

但是强烈建议你不要这么干,因为不同版本的Python解释器可能会把__name改成不同的变量名。

总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。

继承和多态

在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。

比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印:

class Animal(object):     def run(self):         print 'Animal is running...' 

当我们需要编写Dog和Cat类时,就可以直接从Animal类继承:

class Dog(Animal):     pass  class Cat(Animal):     pass 

对于Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类。Cat和Dog类似。

继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法:

dog = Dog() dog.run()  cat = Cat() cat.run() 

运行结果如下:

Animal is running... Animal is running... 

当然,也可以对子类增加一些方法,比如Dog类:

class Dog(Animal):     def run(self):         print 'Dog is running...'     def eat(self):         print 'Eating meat...' 

继承的第二个好处需要我们对代码做一点改进。你看到了,无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running...,符合逻辑的做法是分别显示Dog is running...Cat is running...,因此,对Dog和Cat类改进如下:

class Dog(Animal):     def run(self):         print 'Dog is running...'  class Cat(Animal):     def run(self):         print 'Cat is running...' 

再次运行,结果如下:

Dog is running... Cat is running... 

当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。

要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样:

a = list() # a是list类型 b = Animal() # b是Animal类型 c = Dog() # c是Dog类型 

判断一个变量是否是某个类型可以用isinstance()判断:

>>> isinstance(a, list) True >>> isinstance(b, Animal) True >>> isinstance(c, Dog) True 

看来a、b、c确实对应着list、Animal、Dog这3种类型。

但是等等,试试:

>>> isinstance(c, Animal) True 

看来c不仅仅是Dog,c还是Animal!

不过仔细想想,这是有道理的,因为Dog是从Animal继承下来的,当我们创建了一个Dog的实例c时,我们认为c的数据类型是Dog没错,但c同时也是Animal也没错,Dog本来就是Animal的一种!

所以,在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类。但是,反过来就不行:

>>> b = Animal() >>> isinstance(b, Dog) False 

Dog可以看成Animal,但Animal不可以看成Dog。

要理解多态的好处,我们还需要再编写一个函数,这个函数接受一个Animal类型的变量:

def run_twice(animal):     animal.run()     animal.run() 

当我们传入Animal的实例时,run_twice()就打印出:

>>> run_twice(Animal()) Animal is running... Animal is running... 

当我们传入Dog的实例时,run_twice()就打印出:

>>> run_twice(Dog()) Dog is running... Dog is running... 

当我们传入Cat的实例时,run_twice()就打印出:

>>> run_twice(Cat()) Cat is running... Cat is running... 

看上去没啥意思,但是仔细想想,现在,如果我们再定义一个Tortoise类型,也从Animal派生:

class Tortoise(Animal):     def run(self):         print 'Tortoise is running slowly...' 

当我们调用run_twice()时,传入Tortoise的实例:

>>> run_twice(Tortoise()) Tortoise is running slowly... Tortoise is running slowly... 

你会发现,新增一个Animal的子类,不必对run_twice()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

多态的好处就是,当我们需要传入Dog、Cat、Tortoise……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Tortoise……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法,这就是多态的意思:

对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用run()方法,而具体调用的run()方法是作用在Animal、Dog、Cat还是Tortoise对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:

对扩展开放:允许新增Animal子类;

对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

获取对象信息

当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?

使用type()

首先,我们来判断对象类型,使用type()函数:

基本类型都可以用type()判断:

>>> type(123) <type 'int'> >>> type('str') <type 'str'> >>> type(None) <type 'NoneType'> 

如果一个变量指向函数或者类,也可以用type()判断:

>>> type(abs) <type 'builtin_function_or_method'> >>> type(a) <class '__main__.Animal'> 

但是type()函数返回的是什么类型呢?它返回type类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同:

>>> type(123)==type(456) True >>> type('abc')==type('123') True >>> type('abc')==type(123) False 

但是这种写法太麻烦,Python把每种type类型都定义好了常量,放在types模块里,使用之前,需要先导入:

>>> import types >>> type('abc')==types.StringType True >>> type(u'abc')==types.UnicodeType True >>> type([])==types.ListType True >>> type(str)==types.TypeType True 

最后注意到有一种类型就叫TypeType,所有类型本身的类型就是TypeType,比如:

>>> type(int)==type(str)==types.TypeType True 

使用isinstance()

对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。

我们回顾上次的例子,如果继承关系是:

object -> Animal -> Dog -> Husky 

那么,isinstance()就可以告诉我们,一个对象是否是某种类型。先创建3种类型的对象:

>>> a = Animal() >>> d = Dog() >>> h = Husky() 

然后,判断:

>>> isinstance(h, Husky) True 

没有问题,因为h变量指向的就是Husky对象。

再判断:

>>> isinstance(h, Dog) True 

h虽然自身是Husky类型,但由于Husky是从Dog继承下来的,所以,h也还是Dog类型。换句话说,isinstance()判断的是一个对象是否是该类型本身,或者位于该类型的父继承链上。

因此,我们可以确信,h还是Animal类型:

>>> isinstance(h, Animal) True 

同理,实际类型是Dog的d也是Animal类型:

>>> isinstance(d, Dog) and isinstance(d, Animal) True 

但是,d不是Husky类型:

>>> isinstance(d, Husky) False 

能用type()判断的基本类型也可以用isinstance()判断:

>>> isinstance('a', str) True >>> isinstance(u'a', unicode) True >>> isinstance('a', unicode) False 

并且还可以判断一个变量是否是某些类型中的一种,比如下面的代码就可以判断是否是str或者unicode:

>>> isinstance('a', (str, unicode)) True >>> isinstance(u'a', (str, unicode)) True 

由于strunicode都是从basestring继承下来的,所以,还可以把上面的代码简化为:

>>> isinstance(u'a', basestring) True 

使用dir()

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法:

>>> dir('ABC') ['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__getslice__', '__gt__', '__hash__', '__init__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '_formatter_field_name_split', '_formatter_parser', 'capitalize', 'center', 'count', 'decode', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'index', 'isalnum', 'isalpha', 'isdigit', 'islower', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill'] 

类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

>>> len('ABC') 3 >>> 'ABC'.__len__() 3 

我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法:

>>> class MyObject(object): ...     def __len__(self): ...         return 100 ... >>> obj = MyObject() >>> len(obj) 100 

剩下的都是普通属性或方法,比如lower()返回小写的字符串:

>>> 'ABC'.lower() 'abc' 

仅仅把属性和方法列出来是不够的,配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态:

>>> class MyObject(object): ...     def __init__(self): ...         self.x = 9 ...     def power(self): ...         return self.x * self.x ... >>> obj = MyObject() 

紧接着,可以测试该对象的属性:

>>> hasattr(obj, 'x') # 有属性'x'吗? True >>> obj.x 9 >>> hasattr(obj, 'y') # 有属性'y'吗? False >>> setattr(obj, 'y', 19) # 设置一个属性'y' >>> hasattr(obj, 'y') # 有属性'y'吗? True >>> getattr(obj, 'y') # 获取属性'y' 19 >>> obj.y # 获取属性'y' 19 

如果试图获取不存在的属性,会抛出AttributeError的错误:

>>> getattr(obj, 'z') # 获取属性'z' Traceback (most recent call last):   File "<stdin>", line 1, in <module> AttributeError: 'MyObject' object has no attribute 'z' 

可以传入一个default参数,如果属性不存在,就返回默认值:

>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404 404 

也可以获得对象的方法:

>>> hasattr(obj, 'power') # 有属性'power'吗? True >>> getattr(obj, 'power') # 获取属性'power' <bound method MyObject.power of <__main__.MyObject object at 0x108ca35d0>> >>> fn = getattr(obj, 'power') # 获取属性'power'并赋值到变量fn >>> fn # fn指向obj.power <bound method MyObject.power of <__main__.MyObject object at 0x108ca35d0>> >>> fn() # 调用fn()与调用obj.power()是一样的 81 

小结

通过内置的一系列函数,我们可以对任意一个Python对象进行剖析,拿到其内部的数据。要注意的是,只有在不知道对象信息的时候,我们才会去获取对象信息。如果可以直接写:

sum = obj.x + obj.y 

就不要写:

sum = getattr(obj, 'x') + getattr(obj, 'y') 

一个正确的用法的例子如下:

def readImage(fp):     if hasattr(fp, 'read'):         return readData(fp)     return None 

假设我们希望从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()就派上了用场。

请注意,在Python这类动态语言中,有read()方法,不代表该fp对象就是一个文件流,它也可能是网络流,也可能是内存中的一个字节流,但只要read()方法返回的是有效的图像数据,就不影响读取图像的功能。

使用__slots__


正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class:

>>> class Student(object): ...     pass ... 

然后,尝试给实例绑定一个属性:

>>> s = Student() >>> s.name = 'Michael' # 动态给实例绑定一个属性 >>> print s.name Michael 

还可以尝试给实例绑定一个方法:

>>> def set_age(self, age): # 定义一个函数作为实例方法 ...     self.age = age ... >>> from types import MethodType >>> s.set_age = MethodType(set_age, s, Student) # 给实例绑定一个方法 >>> s.set_age(25) # 调用实例方法 >>> s.age # 测试结果 25 

但是,给一个实例绑定的方法,对另一个实例是不起作用的:

>>> s2 = Student() # 创建新的实例 >>> s2.set_age(25) # 尝试调用方法 Traceback (most recent call last):   File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'set_age' 

为了给所有实例都绑定方法,可以给class绑定方法:

>>> def set_score(self, score): ...     self.score = score ... >>> Student.set_score = MethodType(set_score, None, Student) 

给class绑定方法后,所有实例均可调用:

>>> s.set_score(100) >>> s.score 100 >>> s2.set_score(99) >>> s2.score 99 

通常情况下,上面的set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。

使用__slots__

但是,如果我们想要限制class的属性怎么办?比如,只允许对Student实例添加nameage属性。

为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class能添加的属性:

>>> class Student(object): ...     __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称 ... 

然后,我们试试:

>>> s = Student() # 创建新的实例 >>> s.name = 'Michael' # 绑定属性'name' >>> s.age = 25 # 绑定属性'age' >>> s.score = 99 # 绑定属性'score' Traceback (most recent call last):   File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score' 

由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。

使用__slots__要注意,__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的:

>>> class GraduateStudent(Student): ...     pass ... >>> g = GraduateStudent() >>> g.score = 9999 

除非在子类中也定义__slots__,这样,子类允许定义的属性就是自身的__slots__加上父类的__slots__

使用@property

在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:

s = Student() s.score = 9999 

这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:

class Student(object):      def get_score(self):         return self._score      def set_score(self, value):         if not isinstance(value, int):             raise ValueError('score must be an integer!')         if value < 0 or value > 100:             raise ValueError('score must between 0 ~ 100!')         self._score = value 

现在,对任意的Student实例进行操作,就不能随心所欲地设置score了:

>>> s = Student() >>> s.set_score(60) # ok! >>> s.get_score() 60 >>> s.set_score(9999) Traceback (most recent call last):   ... ValueError: score must between 0 ~ 100! 

但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。

有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的Python程序员来说,这是必须要做到的!

还记得装饰器(decorator)可以给函数动态加上功能吗?对于类的方法,装饰器一样起作用。Python内置的@property装饰器就是负责把一个方法变成属性调用的:

class Student(object):      @property     def score(self):         return self._score      @score.setter     def score(self, value):         if not isinstance(value, int):             raise ValueError('score must be an integer!')         if value < 0 or value > 100:             raise ValueError('score must between 0 ~ 100!')         self._score = value 

@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

>>> s = Student() >>> s.score = 60 # OK,实际转化为s.set_score(60) >>> s.score # OK,实际转化为s.get_score() 60 >>> s.score = 9999 Traceback (most recent call last):   ... ValueError: score must between 0 ~ 100! 

注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。

还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:

class Student(object):      @property     def birth(self):         return self._birth      @birth.setter     def birth(self, value):         self._birth = value      @property     def age(self):         return 2014 - self._birth 

上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。

小结

@property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。

Python学习笔记 for windows 二的更多相关文章

  1. Python学习笔记 for windows

    学习来源 http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001374738136 ...

  2. python学习笔记之基础二(第二天)

    1.编码转换介绍        unicode是最底层.最纯的,会根据终端的编码进行转化展示 一般硬盘存储或传输为utf-8(因为省空间.省带宽),读入内存中为unicode,二者如何转换 a = ' ...

  3. python学习笔记系列----(二)控制流

    实际开始看这一章节的时候,觉得都不想看了,因为每种语言都会有控制流,感觉好像我不看就会了似的.快速预览的时候,发现了原来还包含了对函数定义的一些描述,重点讲了3种函数形参的定义方法,章节的最后讲述了P ...

  4. Python学习笔记第十二周

    目录: 数据库介绍 mysql 数据库安装使用 mysql管理 mysql 数据类型 常用mysql命令事务 索引 创建数据库 外键 增删改查表 权限 python 操作mysql  ORM sqla ...

  5. python学习笔记(十二)之函数

    牛刀小试: 定义一个无参函数 >>> def myFirstFunc(): ... print("Hello python") ... print("h ...

  6. Python 学习笔记(十二)Python文件和迭代(一)

    文件 文件和文件夹 文件:文本文件.二进制文件 文件夹:(windows) G:\pythonWorkspace\python\study (linux/mac) /home/workspace/py ...

  7. 02 Python学习笔记-基本数据类型(二)

    一.基本知识 1.缩进: 2.一行多条语句: 3.断行: 4.注释 # 单行注释 '''这是一段 多行注释''' 5. 变量 1. 变量类型(局部变量.全局变量.系统变量) 2. 变量赋值 多重赋值x ...

  8. python学习笔记:(二)基本数据类型

    python中的变量不需要声明,每个变量中使用前都必须赋值,变量赋值以后该变量才会被创建. 在python中变量就是变量,他没有类型,我们所说的类型是变量所指的内存中对象的类型. python3中有六 ...

  9. python学习笔记(基础二:注释、用户输入、格式化输出)

    注释 单行:# 多行:上下各用3个连续单引号或双引号 3个引号除了多行注释,还可以打印多行 举例: msg = ''' name = "Alex Li" name2 = name ...

随机推荐

  1. CoreAnimation 之CAReplicatorLayer

    CAReplicatorLayer: 主要作用有以下两个: CAReplicatorLayer的目的是为了高效生成许多相似的图层,它会绘制一个或多个图层的子图层 并在每个复制体上应用不同的变换 使用C ...

  2. 关于Ajax工作原理

    1.ajax技术的背景 不可否认,ajax技术的流行得益于google的大力推广,正是由于google earth.google suggest以及gmail等对ajax技术的广泛应用,催生了ajax ...

  3. 【Java EE 学习 47】【Hibernate学习第四天】【sesion】【一级缓存】【懒加载】

    一.Session概述 1.Session 接口是 Hibernate 向应用程序提供的操纵对数据库的最主要的接口, 它提供了基本的保存, 更新, 删除和加载Java 对象的方法. 2.理解Sessi ...

  4. UVA766 Sum of powers(1到n的自然数幂和 伯努利数)

    自然数幂和: (1) 伯努利数的递推式: B0 = 1 (要满足(1)式,求出Bn后将B1改为1 /2) 参考:https://en.wikipedia.org/wiki/Bernoulli_numb ...

  5. LockSupport

    LockSupport是高级线程同步类的基础,用来block和释放线程.这里要区别notify和wait的点在于这里可以先unpark,再park.(有点类似于unpark等于-1,park等于+1. ...

  6. iis 7.0 asp.net发布问题

    问题1: 配置错误:不能在此路径中使用此配置节.如果在父级别上锁定了该节,便会出现这种情况.锁定是默认设置的………… 解决方案: 因为 IIS 7 采用了更安全的 web.config 管理机制,默认 ...

  7. 【CentOS】ifconfig命令 :command not found & yum命令 :cannot find a valid baserl for repo: base/7/x86_64

    第一课,学习的是安装linux的远程连接.相信看阿铭视频的朋友们都会知道,第一个开机输入完root后要敲的命令就是--ifconfig 非常幸运,这迎来了我的第一个问题 这时候你会选择百度,会搜索到这 ...

  8. 如何预览将要上传的图片-使用H5的FileAPI

    这篇将要说的东西已经不新鲜了. 参考资料: Reading files in JavaScript using the File APIs (鉴于作者在美国, 我姑且认为作者母语是英语, 当然链接中有 ...

  9. Python for Infomatics 第12章 网络编程二(译)

    注:文章原文为Dr. Charles Severance 的 <Python for Informatics>.文中代码用3.4版改写,并在本机测试通过. 12.3 用HTTP协议获取一张 ...

  10. erlang mac os 10.9 卸载脚本

    #!/bin/bash if [ "$(id -u)" != "0" ]; then echo "Insufficient permissions. ...