面向对象编程基础
把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过集成(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派
通俗理解
程序是指令的集合,我们在程序中书写的语句在执行时会变成一条或多条指令然后由CPU去执行。但是编程的乐趣应该是以人的思维模式去编程,而不是为了迎合计算机,应该是计算机给人提供便捷。为了简化程序的设计,避免代码重复写的冗余,引入函数的概念,如果函数过于复杂,还可以切分为子函数编写,降低系统的复杂性。但是当系统极其复杂的时候呢?我们引入了面向对象编程的概念,把数据和操作数据的函数看做是一个逻辑上的整体,称之为“对象”,解决问题就是创建出需要的对象并向对象发出信息,多个对象协同工作构造出复杂的系统解决实际问题。这就像现实社会
中的公司一样(对象),公司中有很多员工,这些员工分成几个小组(方法),每个小组中每个人为了同一个工程,有各自的分工(多态),共同完成一件事情。
类和对象
类是蓝图和模板,对象是实例。类是抽象的,对象是具体的。面向对象的编程中,一切皆对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类。(花类,郁金香对象)。把一大推拥有共同特征的静态特征(属性)和动态特征(行为)抽取出来,就可以定义一个“类”
定义类
通常卸载类中的函数,我们称之为(对象的)方法,这些方法就是对象可以接收的消息
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):
is self.age < 18:
print("%s 只能观看《熊出没》" % self.name)
else:
print("%s 随意观看,你懂得" % self.name)
创建和使用对象
当定义好一个类时,可以实例出一个对象并给对象发消息
def main():
# 创建学生对象并制定姓名和年龄
stu1 = Student("bayhax", 24)
# 给对象发送study消息
stu1.study("python")
# 给对象发watch_movie消息
stu1.watch_moive()
stu2 = Student("mike", 14)
stu2.study("物理")
stu2.watch_movie()
if __name__ == "__main__":
main()
访问可见性问题
python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,那么在命名时只需以两个下划线开头
class Test:
def __init__(self, foo):
self.__foo = foo
def __bar(self):
print(self.__foo)
print('__bar')
def main():
test = Test("hello")
test.__bar() # 报错,'Test' 没有 '__bar'这个属性
print(test.__foo) # 报错,'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程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻.
面向对象的支柱
封装、继承、多态
封装:隐藏一切可以隐藏的实现细节,只向外界提供简单的编程接口
只需要知道方法的名字和传入的参数,不需要具体的内部细节
练习
定义个类描述数字时钟
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()
定义一个类描述平面上的点并提供移动电和计算到另一个点距离的方法
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 distance_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.distance_to(p2)) if __name__ == "__main__": main()