本文收录在Python从入门到精通系列文章系列

1. 了解面对对象编程

  活在当下的程序员应该都听过"面向对象编程"一词,也经常有人问能不能用一句话解释下什么是"面向对象编程",我们先来看看比较正式的说法。

  "把一组数据结构和处理它们的方法组成对象(object),把相同行为的对象归纳为类(class),通过类的封装(encapsulation)隐藏内部细节,通过继承(inheritance)实现类的特化(specialization)和泛化(generalization),通过多态(polymorphism)实现基于对象类型的动态分派。"

  这样一说是不是更不明白了。所以我们还是看看更通俗易懂的说法,请看下图:

  之前我们说过"程序是指令的集合",我们在程序中书写的语句在执行时会变成一条或多条指令然后由CPU去执行。当然为了简化程序的设计,我们引入了函数的概念,把相对独立且经常重复使用的代码放置到函数中,在需要使用这些功能的时候只要调用函数即可;如果一个函数的功能过于复杂和臃肿,我们又可以进一步将函数继续切分为子函数来降低系统的复杂性。

  但是说了这么多,不知道大家是否发现,所谓编程就是程序员按照计算机的工作方式控制计算机完成各种任务。但是,计算机的工作方式与正常人类的思维模式是不同的,如果编程就必须得抛弃人类正常的思维方式去迎合计算机,编程的乐趣就少了很多,"每个人都应该学习编程"这样的豪言壮语就只能说说而已。当然,这些还不是最重要的,最重要的是当我们需要开发一个复杂的系统时,代码的复杂性会让开发和维护工作都变得举步维艰,所以在上世纪60年代末期,"软件危机"、"软件工程"等一系列的概念开始在行业中出现。

  当然,程序员圈子内的人都知道,现实中并没有解决上面所说的这些问题的"银弹",真正让软件开发者看到希望的是上世纪70年代诞生的Smalltalk编程语言中引入的面向对象的编程思想(面向对象编程的雏形可以追溯到更早期的Simula语言)。按照这种编程理念,程序中的数据和操作数据的函数是一个逻辑上的整体,我们称之为“对象”,而我们解决问题的方式就是创建出需要的对象并向对象发出各种各样的消息,多个对象的协同工作最终可以让我们构造出复杂的系统来解决现实中的问题。

  说明: 当然面向对象也不是解决软件开发中所有问题的最后的“银弹”,所以今天的高级程序设计语言几乎都提供了对多种编程范式的支持,Python也不例外。

2. 类和对象

  简单的说,类是对象的蓝图和模板,而对象是类的实例。这个解释虽然有点像用概念在解释概念,但是从这句话我们至少可以看出,类是抽象的概念,而对象是具体的东西。在面向对象编程的世界中,一切皆为对象,对象都有属性和行为,每个对象都是独一无二的,而且对象一定属于某个类(型)。当我们把一大堆拥有共同特征的对象静态特征(属性)和动态特征(行为)都抽取出来后,就可以定义出一个叫做“类”的东西。

2.1 定义类

  在Python中可以使用class关键字定义类,然后在类中通过之前学习过的函数来定义方法,这样就可以将对象的动态特征描述出来,代码如下所示。

  1. class Student(object):
  2.  
  3. # __init__是一个特殊方法用于在创建对象时进行初始化操作
  4. # 通过这个方法我们可以为学生对象绑定name和age两个属性
  5. def __init__(self, name, age):
  6. self.name = name
  7. self.age = age
  8.  
  9. def study(self, course_name):
  10. print('%s正在学习%s.' % (self.name, course_name))
  11.  
  12. # PEP 8要求标识符的名字用全小写多个单词用下划线连接
  13. # 但是部分程序员和公司更倾向于使用驼峰命名法(驼峰标识)
  14. def watch_movie(self):
  15. if self.age < 18:
  16. print('%s只能观看《熊出没》.' % self.name)
  17. else:
  18. print('%s正在观看岛国爱情大电影.' % self.name)

说明: 写在类中的函数,我们通常称之为(对象的)方法,这些方法就是对象可以接收的消息。

2.2 创建和使用对象

  当我们定义好一个类之后,可以通过下面的方式来创建对象并给对象发消息。

  1. def main():
  2. # 创建学生对象并指定姓名和年龄
  3. stu1 = Student('along', 66)
  4. # 给对象发study消息
  5. stu1.study('Python从入门到精通系列文章总目录')
  6. # 给对象发watch_av消息
  7. stu1.watch_movie()
  8. stu2 = Student('小黑', 15)
  9. stu2.study('数学')
  10. stu2.watch_movie()
  11.  
  12. main()

3. 访问可见性问题

  对于上面的代码,有C++、Java、C#等编程经验的程序员可能会问,我们给Student对象绑定的name和age属性到底具有怎样的访问权限(也称为可见性)。因为在很多面向对象编程语言中,我们通常会将对象的属性设置为私有的(private)或受保护的(protected),简单的说就是不允许外界访问,而对象的方法通常都是公开的(public),因为公开的方法就是对象能够接受的消息。在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头,下面的代码可以验证这一点。

  1. class Test:
  2.  
  3. def __init__(self, foo):
  4. self.__foo = foo
  5.  
  6. def __bar(self):
  7. print(self.__foo)
  8. print('__bar')
  9.  
  10. def main():
  11. test = Test('hello')
  12. # AttributeError: 'Test' object has no attribute '__bar'
  13. # test.__bar()
  14. # AttributeError: 'Test' object has no attribute '__foo'
  15. # print(test.__foo)
  16.  
  17. if __name__ == "__main__":
  18. main()

  但是,Python并没有从语法上严格保证私有属性或方法的私密性,它只是给私有的属性和方法换了一个名字来妨碍对它们的访问,事实上如果你知道更换名字的规则仍然可以访问到它们,下面的代码就可以验证这一点。之所以这样设定,可以用这样一句名言加以解释,就是"We are all consenting adults here"。因为绝大多数程序员都认为开放比封闭要好,而且程序员要自己为自己的行为负责。

  1. class Test:
  2.  
  3. def __init__(self, foo):
  4. self.__foo = foo
  5.  
  6. def __bar(self):
  7. print(self.__foo)
  8. print('__bar')
  9.  
  10. def main():
  11. test = Test('hello')
  12. test._Test__bar()
  13. print(test._Test__foo)
  14.  
  15. if __name__ == "__main__":
  16. main()

  在实际开发中,我们并不建议将属性设置为私有的,因为这会导致子类无法访问(后面会讲到)。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻.

4. 面向对象的支柱

  面向对象有三大支柱:封装、继承和多态。后面两个概念在下一篇中进行详细的说明,这里我们先说一下什么是封装。我自己对封装的理解是"隐藏一切可以隐藏的实现细节,只向外界暴露(提供)简单的编程接口"。我们在类中定义的方法其实就是把数据和对数据的操作封装起来了,在我们创建了对象之后,只需要给对象发送一个消息(调用方法)就可以执行方法中的代码,也就是说我们只需要知道方法的名字和传入的参数(方法的外部视图),而不需要知道方法内部的实现细节(方法的内部视图)。

5. 练习

练习1:定义一个类描述数字时钟

参考答案:

  1. from time import sleep
  2.  
  3. class Clock(object):
  4. """数字时钟"""
  5.  
  6. def __init__(self, hour=0, minute=0, second=0):
  7. """初始化方法
  8.  
  9. :param hour: 时
  10. :param minute: 分
  11. :param second: 秒
  12. """
  13. self._hour = hour
  14. self._minute = minute
  15. self._second = second
  16.  
  17. def run(self):
  18. """走字"""
  19. self._second += 1
  20. if self._second == 60:
  21. self._second = 0
  22. self._minute += 1
  23. if self._minute == 60:
  24. self._minute = 0
  25. self._hour += 1
  26. if self._hour == 24:
  27. self._hour = 0
  28.  
  29. def show(self):
  30. """显示时间"""
  31. return '%02d:%02d:%02d' % \
  32. (self._hour, self._minute, self._second)
  33.  
  34. def main():
  35. clock = Clock(23, 59, 58)
  36. while True:
  37. print(clock.show())
  38. sleep(1)
  39. clock.run()
  40.  
  41. if __name__ == '__main__':
  42. main()

输出结果:

  1. 23:59:58
  2. 23:59:59
  3. 00:00:00
  4. ... ...

练习2:定义一个类描述平面上的点并提供移动点和计算到另一个点距离的方法

参考答案:

  1. from math import sqrt
  2.  
  3. class Point(object):
  4.  
  5. def __init__(self, x=0, y=0):
  6. """初始化方法
  7.  
  8. :param x: 横坐标
  9. :param y: 纵坐标
  10. """
  11. self.x = x
  12. self.y = y
  13.  
  14. def move_to(self, x, y):
  15. """移动到指定位置
  16.  
  17. :param x: 新的横坐标
  18. "param y: 新的纵坐标
  19. """
  20. self.x = x
  21. self.y = y
  22.  
  23. def move_by(self, dx, dy):
  24. """移动指定的增量
  25.  
  26. :param dx: 横坐标的增量
  27. "param dy: 纵坐标的增量
  28. """
  29. self.x += dx
  30. self.y += dy
  31.  
  32. def distance_to(self, other):
  33. """计算与另一个点的距离
  34.  
  35. :param other: 另一个点
  36. """
  37. dx = self.x - other.x
  38. dy = self.y - other.y
  39. return sqrt(dx ** 2 + dy ** 2)
  40.  
  41. def __str__(self):
  42. return '(%s, %s)' % (str(self.x), str(self.y))
  43.  
  44. def main():
  45. p1 = Point(1, 2)
  46. p2 = Point(2, 1)
  47. print(p1)
  48. print(p2)
  49. p2.move_by(-1, 2)
  50. print(p2)
  51. print(p1.distance_to(p2))

输出结果:

  1. (1, 2)
  2. (2, 1)
  3. (1, 3)
  4. 1.0

  说明: 本章中的插图来自于Grady Booch等著作的《面向对象分析与设计》一书,该书是讲解面向对象编程的经典著作,有兴趣的读者可以购买和阅读这本书来了解更多的面向对象的相关知识。

Python语言基础07-面向对象编程基础的更多相关文章

  1. Python基础-week06 面向对象编程基础

    一.面向对象编程 1.面向过程 与 面向对象编程 面向过程的程序设计: 核心是 过程二字,过程指的是解决问题的步骤,即先干什么再干什么......面向过程的设计就好比精心设计好一条流水线,是一种机械式 ...

  2. [.net 面向对象编程基础] (1) 开篇

    [.net 面向对象编程基础] (1)开篇 使用.net进行面向对象编程也有好长一段时间了,整天都忙于赶项目,完成项目任务之中.最近偶有闲暇,看了项目组中的同学写的代码,感慨颇深.感觉除了定义个类,就 ...

  3. python学习第十四天 -面向对象编程基础

    python也是支持面向对象编程的.这一章节主要讲一些python面向对象编程的一些基础. 什么是面向对象的编程? 1.面向对象编程是一种程序设计范式 2.把程序看做不同对象的相互调用 3.对现实世界 ...

  4. Python 面向对象编程基础

    Python 面向对象编程基础 虽然Pthon是解释性语言,但是Pthon可以进行面向对象开发,小到 脚本程序,大到3D游戏,Python都可以做到. 一类: 语法: class 类名: 类属性,方法 ...

  5. 2018.3.5 Java语言基础与面向对象编程实践

    Java语言基础与面向对象编程实践 第一章 初识Java 1.Java特点 http://www.manew.com/blog-166576-20164.html Java语言面向对象的 Java语言 ...

  6. Python基础 — 面向对象编程基础

    目录 1. 面向对象编程基础 2. 定义类和创建对象 3. init() 方法 4. 魔法方法 5. 访问可见性问题 5. 练习 1. 面向对象编程基础 把一组数据结构和处理它们的方法组成对象(obj ...

  7. Python学习-第三天-面向对象编程基础

    Python学习-第三天-面向对象编程基础 类和对象 简单的说,类是对象的蓝图和模板,而对象是类的实例.这个解释虽然有点像用概念在解释概念,但是从这句话我们至少可以看出,类是抽象的概念,而对象是具体的 ...

  8. python面向对象编程基础

    演示了 Python 类与对象的编程基础, 包括属性.方法.继承.组合.动态创建类. python 版本: 2.7.5 class SimpleClass(object): ''' a simple ...

  9. [Java入门笔记] 面向对象编程基础(二):方法详解

    什么是方法? 简介 在上一篇的blog中,我们知道了方法是类中的一个组成部分,是类或对象的行为特征的抽象. 无论是从语法和功能上来看,方法都有点类似与函数.但是,方法与传统的函数还是有着不同之处: 在 ...

  10. [.net 面向对象编程基础] (7) 基础中的基础——流程控制语句

    [.net 面向对象编程基础] (7) 基础中的基础——流程控制语句 本来没有这一节的内容,后来考虑到既然是一个系列文章,那么就尽可能写的详细一些,本节参考了网上朋友所写的例子,为的是让更多小伙伴学习 ...

随机推荐

  1. itest(爱测试) 3.5.0 发布,开源BUG 跟踪管理& 敏捷测试管理软件

    v3.5.0 下载地址 :itest下载 itest 简介:查看简介 V3.5.0 增加了 9个功能增强,和17个BUG修复 ,详情如下所述. 9个功能增强 : (1)增加xmind(思维导图) 转E ...

  2. JSP知识总结

    day11 JSP入门 1 JSP概述 1.1 什么是JSP JSP(Java Server Pages)是JavaWeb服务器端的动态资源.它与html页面的作用是相同的,显示数据和获取数据. 1. ...

  3. web-神盾局的秘密

    题目地址 http://web.jarvisoj.com:32768/ 首页是一张图片 查看源码,看到了一些猫腻,showing.php     c2hpZWxkLmpwZw==是base64编码   ...

  4. 很多人都会做错的一道JVM题?【分享】

    有关Java虚拟机类加载机制相关的文章一搜一大把,笔者这儿也不必再赘述一遍了.笔者这儿捞出一道code题要各位大佬来把玩把玩,假定你一眼就看出了端倪,那么祝贺你,你可以下山了:​ public cla ...

  5. C#获取CPU和内存使用率

    获取内存使用率 方式1: using System; using System.Runtime.InteropServices; namespace ConsoleApp1 { public clas ...

  6. 【day09】PHP

    一.函数 1. 作用域(Scope) (1)局部变量:变量在声明的代码段中有效 a.动态变量 b.静态变量:static ,用在函数中,当调用函数后内存不释放,能存储变量的最后的值. (2)全局变量: ...

  7. 用scanf清空缓冲区 对比fflush

    fflush会将缓冲数据打印到屏幕或者输出磁盘,scanf将丢弃. 如上图.

  8. Paper | Blind Quality Assessment Based on Pseudo-Reference Image

    目录 1. 技术细节 1.1 失真识别 1.2 得到对应的PRI并评估质量 块效应 模糊和噪声 1.3 扩展为通用的质量评价指标--BPRI 归一化3种质量评分 判断失真类型 加权求和 2. 总结 这 ...

  9. vue怎么给自定义组件绑定原生事件

     下面主要以4个示例Demo演示(示例代码JS引用的Vue CDN),建议小伙伴直接复制示例代码运行查看, 赶时间的小伙伴可直接往下拉,看示例demo4 注:全局或局部注册的组件称为子组件,其中声明的 ...

  10. learning rate warmup实现

    def noam_scheme(global_step, num_warmup_steps, num_train_steps, init_lr, warmup=True): ""& ...