面向对象高级编程
使用__slots_
使用@property
多重继承
定制类
_str__
__iter__
__getitem__
__getattr__
__call__
使用元类
type()
metaclass
错误、调试、测试
错误处理
调用堆栈
记录错误
抛出错误
调试
断言:
单元测试
Python 练习实例
面向对象高级编程:
使用__slots__:正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。
from types import MethodType
class Animal(object):
pass
def set_weight(self,weight):
self.weight=weight
Animal.set_weight=MethodType(set_weight,None,Animal)
dog=Animal()
dog.set_weight(100)
print dog.weight
class Student(object):
pass
def set_age(self,age):
self.age=age
s1=Student()
s2=Student()
s1.name='lsw'
print s1.name
s1.set_age=MethodType(set_age,s1,Student)
s1.set_age(25)
print s1.age
s2.set_age(25)
print s2.age
如果我们想要限制class的属性怎么办?比如,只允许对Student实例添加name
和age
属性。
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__
变量,来限制该class能添加的属性
class Student(object): __slots__ = ('name','age') s=Student() s.name='Lsw' s.age=100 s.score=100
使用__slots__
要注意,__slots__
定义的属性仅对当前类起作用,对继承的子类是不起作用的
class Student(object): __slots__ = ('name','age') class GraduateStudent(Student): pass g=GraduateStudent() g.score=100 print g.score s=Student() s.name='Lsw' s.age=100 s.score=100 print s.score
使用@property
在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:
python内置的@property
装饰器就是负责把一个方法变成属性调用的:
class Student(object): @property 变getter方法 def score(self): return self._score @score.setter 变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 s=Student() s.score=100 print s.score class Teacher(object): @property def birth(self): return self._birth @birth.setter def birth(self, value): self._birth = value @property 只定义getter方法,不定义setter方法就是一个只读属性 def age(self): return 2014 - self._birth t=Teacher() t.birth=100 print t.birth print t.age
@property
广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。
多重继承:
通过继承,子类就可以扩展父类的功能,
Mixin:
在设计类的继承关系时,通常,主线都是单一继承下来的,例如,Ostrich
继承自Bird
。但是,如果需要“混入”额外的功能,通过多重继承就可以实现,比如,让Ostrich
除了继承自Bird
外,再同时继承Runnable
。这种设计通常称之为Mixin。
为了更好地看出继承关系,我们把Runnable
和Flyable
改为RunnableMixin
和FlyableMixin
。类似的,你还可以定义出肉食动物CarnivorousMixin
和植食动物HerbivoresMixin
,让某个动物同时拥有好几个Mixin:
class Dog(Mammal, RunnableMixin, CarnivorousMixin):
pass
Python自带的很多库也使用了Mixin。举个例子,Python自带了TCPServer
和UDPServer
这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由ForkingMixin
和ThreadingMixin
提供。通过组合,我们就可以创造出合适的服务来。
多进程模式的TCP服务:
class MyTCPServer(TCPServer, ForkingMixin):
pass
多线程模式的UDP服务: class MyUDPServer(UDPServer, ThreadingMixin):
pass
更先进的协程模型,可以编写一个CoroutineMixin
:
class MyTCPServer(TCPServer, CoroutineMixin):
pass
由于Python允许使用多重继承,因此,Mixin就是一种常见的设计。
只允许单一继承的语言(如Java)不能使用Mixin的设计。
先横向比较,再纵向比较
class Grandfa(object): def hair(self): print 'no hair' class Father(Grandfa): pass class Mother(object): def hair(self): print 'long hair' class Tom(Father,Mother): pass me = Tom() print me.hair()
class Grandfa(object): def hair(self): print 'no hair' class Father(Grandfa): pass class Mother(Grandfa): def hair(self): print 'long hair' class Tom(Father,Mother): pass me = Tom() print me.hair()
定制类:
Python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类:
_str__
class Student(object): def __init__(self,name): self.name=name def __str__(self): return 'Student Object (name=%s)'%self.name __repr__=__str__ s=Student('LSW') print s print Student('Lsw')
__iter__
# -*- coding: utf-8 -*- class Fib(object): def __init__(self): self.a, self.b = 0, 1 # 初始化两个计数器a,b def __iter__(self): return self # 实例本身就是迭代对象,故返回自己 def next(self): self.a, self.b = self.b, self.a + self.b # 计算下一个值 if self.a > 100: # 退出循环的条件 raise StopIteration(); return self.a # 返回下一个值 for n in Fib(): print n,
__getitem__
Fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行
要表现得像list那样按照下标取出元素,需要实现__getitem__()
方法
class Fib(object): def __getitem__(self, n): a, b = 1, 1 for x in range(n): a, b = b, a + b return a f=Fib() print f[10]
__getattr__
class Student(object): def __init__(self): self.name = 'Michael' def __getattr__(self, attr): if attr == 'score': return 99 raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr) s=Student() print s.name print s.score print s.aaa
这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。
这种完全动态调用的特性有什么实际作用呢?作用就是,可以针对完全动态的情况作调用。
举个例子:
现在很多网站都搞REST API,比如新浪微博、豆瓣啥的,调用API的URL类似:
http:
//api.server/user/friends
http:
//api.server/user/timeline/list
如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。
利用完全动态的__getattr__
,我们可以写出一个链式调用:
class Chain(object): def __init__(self, path=''): self._path = path def __getattr__(self, path): return Chain('%s/%s' % (self._path, path)) def __str__(self): return self._path print Chain().status.user.timeline.list1
__call__
一个对象实例可以有自己的属性和方法,任何类,只需要定义一个__call__()
方法,就可以直接对实例进行调用。
__call__()
还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成函数,把函数看成对象,因为这两者之间本来就没啥根本的区别。
如果你把对象看成函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。
那么,怎么判断一个变量是对象还是函数呢?其实,更多的时候,我们需要判断一个对象是否能被调用,能被调用的对象就是一个Callable
对象,比如函数和我们上面定义的带有__call()__
的类实例:
class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name) s=Student('LSW') print s() print callable(s) print callable(max) print callable([1.2]) print callable(None) print callable('String')
使用元类:
type()
动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的
写一个hello.py
class Hello(object): def hello(self, name='world'): print('Hello, %s.' % name)
from hello import Hello h=Hello() print h.hello()
type()
函数可以查看一个类型或变量的类型,Hello
是一个class,它的类型就是type
,而h
是一个实例,它的类型就是class Hello
。
我们说class的定义是运行时动态创建的,而创建class的方法就是使用type()
函数。
type()
函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()
函数创建出Hello
类,而无需通过class Hello(object)...
的定义
def fn(self,name='world'): print ('Hello,%s.'%name) Hello=type('Hello',(object,),dict(hello=fn)) h=Hello() print h.hello()
要创建一个class对象,type()函数依次传入3个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上
metaclass
除了使用type()
动态创建类以外,要控制类的创建行为,还可以使用metaclass。
metaclass,直译为元类,简单的解释就是:
当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。
连接起来就是:先定义metaclass,就可以创建类,最后创建实例。
所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。
我们先看一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add方法:
定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass
__new__()
方法接收到的参数依次是:当前准备创建的类的对象;类的名字;类继承的父类集合;类的方法集合。
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)
class MyList(list):
__metaclass__ = ListMetaclass#指示Python解释器在创建MyList
时,要通过ListMetaclass.__new__()
来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
L=MyList()
L.add(1)
print L
错误、调试、测试:
错误处理:
Python的错误其实也是class,所有的错误类型都继承自BaseException
,所以在使用except
时需要注意的是,它不但捕获该类型的错误,还把其子类也“一网打尽”:
BaseException
+-- SystemExit
+-- KeyboardInterrupt
+-- GeneratorExit
+-- Exception
+-- StopIteration
+-- StandardError
| +-- BufferError
| +-- ArithmeticError
| | +-- FloatingPointError
| | +-- OverflowError
| | +-- ZeroDivisionError
| +-- AssertionError
| +-- AttributeError
| +-- EnvironmentError
| | +-- IOError
| | +-- OSError
| | +-- WindowsError (Windows)
| | +-- VMSError (VMS)
| +-- EOFError
| +-- ImportError
| +-- LookupError
| | +-- IndexError
| | +-- KeyError
| +-- MemoryError
| +-- NameError
| | +-- UnboundLocalError
| +-- ReferenceError
| +-- RuntimeError
| | +-- NotImplementedError
| +-- SyntaxError
| | +-- IndentationError
| | +-- TabError
| +-- SystemError
| +-- TypeError
| +-- ValueError
| +-- UnicodeError
| +-- UnicodeDecodeError
| +-- UnicodeEncodeError
| +-- UnicodeTranslateError
+-- Warning
+-- DeprecationWarning
+-- PendingDeprecationWarning
+-- RuntimeWarning
+-- SyntaxWarning
+-- UserWarning
+-- FutureWarning
+-- ImportWarning
+-- UnicodeWarning
+-- BytesWarning
使用try...except
捕获错误还有一个巨大的好处,就是可以跨越多层调用,比如函数main()
调用bar()
,bar()
调用foo()
,结果foo()
出错了,这时,只要main()
捕获到了,就可以处理:
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except StandardError, e:
print 'Error!'
finally:
print 'finally...'
main()
调用堆栈
错误的最后一行是最根本的原因(调用堆栈的原因)
记录错误
如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。
Python内置的logging
模块可以非常容易地记录错误信息:
# err.py
import logging
def foo(s):
return 10 / int(s)
def bar(s):
return foo(s) * 2
def main():
try:
bar('0')
except StandardError, e:
logging.exception(e)
main()
print 'END'
抛出错误
如果要抛出错误,首先根据需要,可以定义一个错误的class,选择好继承关系,然后,用raise
语句抛出一个错误的实例
只有在必要的时候才定义我们自己的错误类型。如果可以选择Python已有的内置的错误类型(比如ValueError,TypeError),尽量使用Python内置的错误类型
# err.py
def foo(s):
n = int(s)
return 10 / n
def bar(s):
try:
return foo(s) * 2
except StandardError, e:
print 'Error!'
raise
def main():
bar('0')
main()
raise
语句如果不带参数,就会把当前错误原样抛出。此外,在except
中raise
一个Error,还可以把一种类型的错误转化成另一种类型:(只要是合理的转换逻辑就可以,但是,决不应该把一个IOError
转换成毫不相干的ValueError
)
try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')
调试:
断言:
凡是用print
来辅助查看的地方,都可以用断言(assert)来替代
assert的意思是,表达式n != 0应该是True,否则,后面的代码就会出错。
如果断言失败,assert语句本身就会抛出AssertionError
启动Python解释器时用-O
参数可以关闭assert
,
关闭后,你可以把所有的assert
语句当成pass
来看
def foo(s):
n = int(s)
assert n != 0, 'n is zero!'
return 10 / n
def main():
foo('0')
main()
Logging:把print
替换为logging
是第3种方式,和assert
比,logging
不会抛出错误,而且可以输出到文件
import logging
logging.basicConfig(level=logging.INFO)
s = '0'
n = int(s)
logging.info('n = %d' % n)
print 10 / n
logging允许指定记录信息的级别,有debug,info,warning,error等几个级别,当我们指定level=INFO时,logging.debug就不起作用了。同理,指定level=WARNING后,debug和info就不起作用了。这样一来,你可以放心地输出不同级别的信息,也不用删除,最后统一控制输出哪个级别的信息。
logging的另一个好处是通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件
pdb(Python的调试器pdb,让程序以单步方式运行,可以随时查看运行状态)
单元测试:
以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的。
Mydict.py
class Dict(dict):
def __init__(self, **kw):
super(Dict, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
mydict_test.py
import unittest 为了编写单元测试,我们需要引入Python自带的unittest模块
from mydict import Dict
class TestDict(unittest.TestCase): 编写单元测试时,我们需要编写一个测试类,从unittest.TestCase继承
def test_init(self):
d = Dict(a=1, b='test')
self.assertEquals(d.a, 1)
self.assertEquals(d.b, 'test')
self.assertTrue(isinstance(d, dict))
def test_key(self):
d = Dict()
d['key'] = 'value'
self.assertEquals(d.key, 'value')
def test_attr(self):
d = Dict()
d.key = 'value'
self.assertTrue('key' in d)
self.assertEquals(d['key'], 'value')
def test_keyerror(self):
d = Dict()
with self.assertRaises(KeyError):
value = d['empty']
def test_attrerror(self):
d = Dict()
with self.assertRaises(AttributeError):
value = d.empty
if __name__ == '__main__':
unittest.main()
以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。
对每一类测试都需要编写一个test_xxx()方法。由于unittest.TestCase提供了很多内置的条件判断,我们只需要调用这些方法就可以断言输出是否是我们所期望的。最常用的断言就是assertEquals()
另一种重要的断言就是期待抛出指定类型的Error,比如通过d['empty']
访问不存在的key时,断言会抛出KeyError
一旦编写好单元测试,我们就可以运行单元测试。最简单的运行方式是在mydict_test.py
的最后加上两行代码:
if __name__ == '__main__':
unittest.main()
setUp与tearDown
可以在单元测试中编写两个特殊的setUp()和tearDown()方法。这两个方法会分别在每调用一个测试方法的前后分别被执行。
setUp()和tearDown()方法有什么用呢?设想你的测试需要启动一个数据库,这时,就可以在setUp()方法中连接数据库,在tearDown()方法中关闭数据库,这样,不必在每个测试方法中重复相同的代码:
def setUp(self):
print 'setUp...'
def tearDown(self):
print 'tearDown...'
Python 练习实例:
- 将一个正整数分解质因数。例如:输入90,打印出90=2*3*3*5
#!/usr/bin/python
# -*- coding: UTF-8 -*-
def reduceNum(n):
print '{} = '.format(n),
if not isinstance(n, int) or n <= 0:
print '请输入一个正确的数字 !'
exit(0)
elif n in [1]:
print '{}'.format(n)
while n not in [1]: # 循环保证递归
for index in xrange(2, n + 1):
if n % index == 0:
n /= index # n 等于 n/index
if n == 1:
print index
else: # index 一定是素数
print '{} *'.format(index),
break
reduceNum(90)
reduceNum(100)
- 输出指定格式的日期
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import datetime
if __name__ == '__main__':
# 输出今日日期,格式为 dd/mm/yyyy。更多选项可以查看 strftime() 方法
print(datetime.date.today().strftime('%d/%m/%Y'))
# 创建日期对象
miyazakiBirthDate = datetime.date(1941, 1, 5)
print(miyazakiBirthDate.strftime('%d/%m/%Y'))
# 日期算术运算
miyazakiBirthNextDay = miyazakiBirthDate + datetime.timedelta(days=1)
print(miyazakiBirthNextDay.strftime('%d/%m/%Y'))
# 日期替换
miyazakiFirstBirthday = miyazakiBirthDate.replace(year=miyazakiBirthDate.year + 1)
print(miyazakiFirstBirthday.strftime('%d/%m/%Y'))
- 输入一行字符,分别统计出其中英文字母、空格、数字和其它字符的个数。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import string
s = raw_input('input a string:\n')
letters = 0
space = 0
digit = 0
others = 0
for c in s:
if c.isalpha():
letters += 1
elif c.isspace():
space += 1
elif c.isdigit():
digit += 1
else:
others += 1
print 'char = %d,space = %d,digit = %d,others = %d' % (letters, space, digit, others)
- 求s=a+aa+aaa+aaaa+aa...a的值,其中a是一个数字。例如2+22+222+2222+22222(此时共有5个数相加),几个数相加由键盘控制。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
Tn = 0
Sn = []
n = int(raw_input('n = '))
a = int(raw_input('a = '))
for count in range(n):
Tn = Tn + a
a = a * 10
Sn.append(Tn)
print Tn
Sn = reduce(lambda x, y: x + y, Sn)
print "计算和为:", Sn
- 一个数如果恰好等于它的因子之和,这个数就称为"完数"。例如6=1+2+3.编程找出1000以内的所有完数。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
from sys import stdout
for j in range(2, 1001):
k = []
n = -1
s = j
for i in range(1, j):
if j % i == 0:
n += 1
s -= i
k.append(i)
if s == 0:
print j
for i in range(n):
stdout.write(str(k[i]))
stdout.write(' ')
print k[n]