面向对象编程基础
”把一组数据结构和处理他们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalition),通过多态(polymorphism)实现基于对象类型的动态分派。”巴拉巴拉,骆昊大大选用了知乎上一个说法:
之前我们说过“程序是指令的集合”,我们在程序中书写的语句在执行时会变成一条或多条指令然后由CPU去执行。当然为了简化程序的设计,我们引入了函数的概念,把相对独立且经常重复使用的代码放置到函数中,在需要使用这些功能的时候只要调用函数即可;如果一个函数的功能过于复杂和臃肿,我们又可以进一步将函数继续切分为子函数来降低系统的复杂性。
程序中的数据和操作数据的函数是一个逻辑上的整体,我们称之为”对象“,而我们解决问题的方式就是创建出需要的对象并向对象 发出各种各样的消息,多个对象的协同工作最终可以让我们构造出复杂的系统来解决现实中的问题。
类和对象
类是对象的蓝图和模板,而对象是类的实例。类是抽象的概念,而对象是具体的东西,在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类(型)。当我们把一大堆拥有共同特征的对象的静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义成一个叫“类”的东西。
定义类、创建和使用对象
在python中可以使用class关键字定义类,然后在类中通过之前学习过的函数来定义方法,这样就可以将对象的动态特征描述出来,代码如下:
class Student(object):
# __init__是一个特殊方法用于在创建对象时进行初始化操作
# 通过这个方法我们可以为学生对象绑定name和age两个属性
def __init__(self, name, age):
self.name = name
self.age = age
def study(self, course_name):
print('%s正在学习%s.' % (self.name, course_name))
# PEP 8 要求标识符的名字用全小写多个单词用下划线连接
# 但是部分程序员公司更倾向于驼峰命名法
def watch_movie(self):
if self.age < 18:
print('%s只能观看《熊出没》.' % self.name)
else:
print('%s正在观看《哈利波特》.' % self.name)
"""
创建和使用对象
当我们定义好一个类之后,可以通过下面的方式来创建对象并给对象发消息
"""
def main():
# 创建学生对象并指定姓名和年龄
stu1 = Student('陈达杰', 25)
# 给对象发study消息
stu1.study('骆昊大大的100天Python项目')
# 给XX对象发watch_movie消息
stu1.watch_movie()
stu2 = Student('王大锤', 15)
stu2.study('思想品德')
stu2.watch_movie()
if __name__ == '__main__':
main()
这个定义和C语言结构体类似。、
访问可见性问题
在很多面向对象编程语言中,我们通常会将对象的属性设置为私有的(private)或受保护的(protected),简单的说就是不允许外界访问,而对象的方法通常都是公开的(public),因为公开的方法就是对象能够接受的消息。在python中,属性和方法的权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头:
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test('hello')
# AttributeError: 'Test' object has no attribute '__bar'
test.__bar()
# AttributeError: type object 'Test' has no attribute '__bar'
print(test.__foo)
if __name__ == '__main__':
main()
但是,python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来妨碍对它们的访问,事实上如果你知道更换名字的规则依旧可以访问到它们,之所以这样设定,可以用这样一句名言加以解释,就是"We are all consenting adults here"。因为绝大多数程序员都认为开放比封闭好,而且程序员要为自己的行为负责。
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
"""
def main():
test = Test('hello')
test._Test__bar()
print(test._Test__foo)
if __name__ == '__main__':
main()
像结构体,但是好像比结构体复杂一点。
在实际开发过程中,并不建议将属性设置为私有的,因为这会导致子类无法访问,所以大多数python程序员会遵循一种命名惯例就是让属性名以单下划线开头表示属性是受保护的,本类之外的代码在访问这样的属性时应该保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和外界仍然是可以访问的,所以更多时候它是一种暗示或隐喻。这里骆昊大大推荐了他的《Python-那些年我们踩过的那些坑》。
面向对象的支柱
面向对象有三大支柱:封装、继承和多态。
封装:隐藏一切可以隐藏的实现细节,只向外界暴露(提供)简单的编程接口
我们在类定义的方法其实就是把数据和对数据的操作封装起来了,在我们创建了对象之后,只需要给对象发送一个消息(调试方法)就可以执行方法中的代码,也就是说我们只需要知道方法的名字和传入的参数(方法的外部视图),而不需要知道方法内部的实现细节(方法的内部视图)。
练习
1、定义一个类描述数字时钟
from time import sleep
class Clock(object):
"""数字时钟"""
def __init__(self, hour=0, minute=0, second=0):
"""
初始化方法
:param hour: 时
:param minute: 分
:param second: 秒
"""
self._hour = hour
self._minute = minute
self._second = second
def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0
def show(self):
"""显示时间"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)
def main():
clock = Clock(23, 59, 58)
while True:
print(clock.show())
sleep(1)
clock.run()
if __name__ == '__main__':
main()
和结构体是一样的,引用也是可以用结构体引用方法
2、定义一个类描述平面上的点并提供移动点到另一个点距离的方法
from math import sqrt
class Point(object):
def __init__(self, x=0, y=0):
"""初始化方法
:param x: 横坐标
:param y: 纵坐标
"""
self.x = x
self.y = y
def move_to(self, x, y):
"""移动到指定位置
:param x:新的横坐标
:param y: 新的纵坐标
"""
self.x = x
self.y = y
def move_by(self, dx, dy):
"""移动指定的增量
:param dx:横坐标的增量
:param dy:纵坐标的增量
"""
self.x += dx
self.y += dy
def distantce_to(self, other):
"""计算与另一个点的距离
:param other: 另一个点
"""
dx = self.x - other.x
dy = self.y - other.y
return sqrt(dx ** 2 + dy ** 2)
def __str__(self):
return '(%s, %s)' % (str(self.x), str(self.y))
def main():
p1 = Point(3, 5)
p2 = Point()
print(p1)
print(p2)
p2.move_by(-1, 2)
print(p2)
print(p1.distantce_to(p2))
if __name__ == '__main__':
main()
将你所需要实现的功能通过函数封装起来,需要使用时直接调用函数即可。