(一)面向对象的特性
- 程序包含类和方法的定义;
- 大部分计算以对象上的操作表示;
- 对象通常代表现实世界的物体,方法对应现实世界中物体交互的方法;
**方法:**方法是一个与特定的类相关联的函数;
- 方法和函数语义相同,但是有两处句法的不同:
- 方法在一个类定义内部声明,为的是显示地与类进行关联;
- 调用方法的语句和调用函数的语法不同;
(二)打印对象
class Time:
"""Represents the time of day.
"""
def print_time(time):
print('%.2d:%.2d%.2d' % (time.hour, time.minute, time.second))
- 下面将这个
print_time
函数变为Time
类的一个方法:
class Time:
def print_time(time):
print('%.2d:%.2d:%.2d' % (time.hour, time.minute, time.second))
>>> start = Time()
>>> start.hour = 9
>>> start.minute = 45
>>> start.second = 0
>>> start.print_time()
09:45:00
- 上面的例子中,
start
称之为主语(subject); - 在方法的调用中,主语被赋值为方法的第一个参数;
- 根据方法的约定,方法的第一个参数写作
self
:
class Time:
def print_time(self):
print('%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second))
- 下面增加另外一个方法:
class Time:
def print_time(self):
print('%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second))
def time_to_int(self):
return self.hour * 3600 + self.minute * 60 + self.second
def increment(self, seconds):
seconds += self.time_to_int()
return int_to_time(seconds)
这里的increment
方法是一个纯函数,而不是一个修改器,它会返回一个值,但是并不会修改原来的参数对象;
>>> start.print_time()
09:45:00
>>> end = start.increment(1337)
>>> end.print_time()
10:07:17
-
主语实际上也是当做一个参数进行处理;
-
将
is_after
函数重写为方法:
# inside class Time:
def is_after(self, other):
return self.time_to_int() > other.time_to_int()
>>> end.is_after(start)
True
(三)init
方法
init
方法是一个特殊的方法,当对一个对象初始化的时候调用,全名是__init__
;【相当于 C#中的构造函数】
# inside class Time:
def __init__(self, hour=0, minute=0, second=0):
self.hour = hour
self.minute = minute
self.second = second
-
通常情况下,
__init__
方法的参数和属性的名称一样;- 上面的参数是可选的,当不带参数调用
Time
,会得到默认值:
>>> time = Time() >>> time.print_time() 00:00:00
- 上面的参数是可选的,当不带参数调用
(四)__str__
方法
__str__
方法和__init__
方法类似的特殊方法,返回一个对象的字符串的表现形式:
# inside class Time:
def __str__(self):
return '%.2d:%.2d:%.2d' % (self.hour, self.minute, self.second)
- 当需要打印一个对象的时候,可以使用
str
方法:
>>> time = Time(9, 45)
>>> print(time)
09:45:00
- 通常,写一个新的类,从
__init__
开始,使得更加容易实例化;接着__str__
方法,方便调试;
(五)运算符重载
通过定义一些其他的特殊方法,可以在自定义的类上指定运算符的行为;
- 可以为
Time
类定义一个叫__add__
的方法,这样可以在Time
,对象上使用+
运算符;
# inside class Time:
def __add__(self, other):
seconds = self.time_to_int() + other.time_to_int()
return int_to_time(seconds)
使用方法:
>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print(start + duration)
11:20:00
- 当在
Time
对象上使用+
运算符,python会调用__add__
;
改变一个运算符的行为,是其兼容自定义的类,称之为运算符重载(Operator Overloading)
(六)类型分发(type-based dispatch)
- 上面的是将两个
Time
对象相加,如果想要将一个整数与Time
对象相加;- 下面版本的
__add__
将会检查other
类型,并相应调用add_time
或者increment
# inside class Time
def __add__(self, other):
if isinstance(other, Time):
return self.add_time(other)
else:
return self.increment(other)
def add_time(self, other):
seconds = self.time_to_int() + other.time_to_int()
return int_to_time(seconds)
def increment(self, seconds):
seconds += self.time_to_int()
return int_to_time(seconds)
- 内建函数
isinstance
接受一个值和一个类对象,如果是这个类的实例,则返回True
像上面的,根据参数的类型,将计算任务发送给不同的方法,这个操作称之为类型分发(type-based dispatch);
>>> start = Time(9, 45)
>>> duration = Time(1, 35)
>>> print(start + duration)
11:20:00
>>> print(start + 1337)
10:07:17
- 但是这个加法并没有交换性,可以使用一个
__radd__
这个特殊的方法,这个是一个右手加法,当一个Time
对象出翔在+
运算符的右边时候调用这个方法:
# inside class Time:
def __radd__(self, other):
return self.__add__(other)
使用方法:
>>> print(1337 + start)
10:07:17
(七)多态性
类型的分发在必要的时候是非常有用的,但是并不是绝对必须的,可以编写对不同参数类型都适用的函数来避免这种情况;
- 许多为字符串编写的函数,实际上也是适用于其他序列的;如使用
histogram
计算单词中每个字母出现的次数:
def histogram(s):
d = dict()
for c in s:
if c not in d:
d[c] = 1
else:
d[c] = d[c] + 1
return d
- 其实这个函数同样适用于列表、元组甚至是字典,只要
s
的元素是可以哈希的:
>>> t = ['spam', 'egg', 'spam', 'spam', 'bacon', 'spam']
>>> histogram(t)
{'bacon': 1, 'egg': 1, 'spam': 4}
适用于多种类型的函数,称之为多态函数,多态函数有助于代码的复用;
- 如,内建函数
sum
对一个序列的元素进行求和,只要这个序列中的元素支持加法就可以:
>>> t1 = Time(7, 43)
>>> t2 = Time(7, 41)
>>> t3 = Time(7, 37)
>>> total = sum([t1, t2, t3])
>>> print(total)
23:01:00
(八)接口和实现
- 面向对象编程的目的之一使得软件更加容易进行维护;
- 实现这个目标的一个原则是:接口和实现分离;
- 对于对象,意味着,一个类提供的方法不能依赖于属性的形式;
- 当我们设计完成一个类,可能会发现有更好的实现,如果程序其他部分使用了这个类,再来改变接口需要很多时间,而且容易出错;
- 但是,如果细心设计好接口,可以改变实现,而接口不变,这样程序的其他部分都不用改变;
(九)调试
- 在python中,在程序执行的任何时间,为一个对象添加属性都是合法的;
- 但是,如果相同类型的对象有不同的属性,容易出现错误;
- 通常一个好的办法是在
init
方法中初始化一个对象的所有属性;
- 当不确定一个对象是不是有某个属性,可以使用内建函数
hasattr
,这样可以检查,这个类中是不是包含这个属性; - 可以使用内建函数
vars
访问对象属性;这个函数接受一个对象,并返回一个将属性名称到对应值的字典:
>>> p = Point(3, 4)
>>> vars(p)
{'y':4, 'x':3}
- 可以定义下面的代码。用于调试:
def print_attribute(obj):
for attr in var(obj):
print(attr, getattr(obj, attr))
print_attributes
遍历一个对象的字典,然后打印每个属性的名称和对应的值;- 内建函数
getattr
接受一个对象和一个属性名称(字符串)作为参数,然后返回该属性的值;