作者 |  天元浪子

责编 | 刘静

出品 | CSDN 博客

前言
光棍节就要到了,一说介绍对象,我猜你一定想到了派森大叔家的克蕾丝(class)小姐姐和黛夫(def)小哥哥。别想入非非了,严肃点儿!我们今天的的话题,不是介绍男女朋友,而是讲解如何面向对象编程,也就是程序员常说的OOP啦。
不知道前辈们为什么会把 Object Oriented Programming 翻译成面向对象编程,搞得单身程序员经常心猿意马地产生幻觉,以为屏幕上的俊男美女就是自己将来要面对的对象了。说到这里,我觉得还是台湾同行的计算机术语翻译得较为恰当。比如,cache,我们叫“缓存”,人家叫“快取”,音意俱佳。台湾同行把OPP翻译成“物件导向编程”——虽然同样不明觉厉,但至少可以让单身程序员暂时忘记没有“对象”的烦恼。
限盐少许,本博主正式开始为大家介绍对象。
类和对象的概念
学习面向对象编程,首先得搞明白,什么是对象?类是什么?实例化是什么意思?下图表达了我对OOP这几个基本概念的理解(实际上是妥协的结果——我和我的同事们讨论了很久,并翻墙参考了维基百科的说法)。

类是对我们要处理的客观事物的抽象。类用来描述具有相同的属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法。对象是类在内存的实例,一个类可以实例化为多个对象。类是抽象的,不占用内存,而对象是具体的,占用存储空间。
类的成员
作为Python初学者,大可不必把精力花费在令人费解的概念上,只需要掌握使用类的基本要素就可以了。未来的日子里,你有足够多的时间慢慢体会OOP的博大精深。随着经验的积累,OOP会自然而然地成为你的思维工具。
下面的代码,定义了一个名为A的类。所有的类,都有构造函数和析构函数,此外,还可以包含成员函数和成员变量。我喜欢把成员函数叫做类的方法,把成员变量叫做类的属性。
class A:def __init__(self):""" 构造函数"""self.a = 10 # 定义了一个成员变量a     def getA(self):"""成员函数"""print("a=%d" % self.a)def __del__():"""析构函数"""print("delete object")a = A()
a.getA()

当类被实例化为对象时,首先执行构造函数,当对象被销毁时,会自动执行析构函数。一般的,我们会在构造函数中进行初始化工作,在析构函数中进行清理工作。

读到这里,有很多初学者一定会说:我定义类的时候,写过构造函数,但从没有写过析构函数,你为什么说所有的类都有构造函数和析构函数呢?没错,定义类的时候,即便我们不写构造函数和析构函数,这两个方法也照样存在(析构函数稍微有点特殊,我们不能直接看到它——除非是我们自己定义的)。如果我们自己定义了构造函数和析构函数,则将会取代系统自动赋予的这两个函数。下面的例子清晰地说明了其中的奥秘:类A既没有构造函数,也没有析构函数,类B只有析构函数,两个类都可以生成类实例,也都可以销毁,且 del b 时首先调用了自定义的析构函数。
>>> class A:pass>>> a = A()
>>> del a
>>> a
Traceback (most recent call last):File "<pyshell#70>", line 1, in <module>a
NameError: name 'a' is not defined
>>> class B:def __del__(self):print('执行析构函数,清理现场')>>> b = B()
>>> del b
执行析构函数,清理现场
>>> b
Traceback (most recent call last):File "<pyshell#75>", line 1, in <module>b
NameError: name 'b' is not defined
新式类和旧式类

在PY2中,类有新式类和旧式类两种。新式类需要继承自虚类Object,旧式类则不需要。PY2中类的写法有三种:

class A(object):新式类写法
class A():旧式类写法
class A:旧式类写法

在PY3中,只有新式类,不再支持旧式类。你如果习惯继承Object的写法,也完全没有问题。上面三种写法在PY3中都被解释成新式类。新式类和旧式类的主要区别是:

  • 新式类可以继承Object的构造函数和析构函数,如果类的构造和析构函数没有特别的工作,可以省略。而旧式类则不能:

class A():def print(self):print("I am A")class B(A):def __init__(self):A.__init__(self)
b = B()
此时用PY2运行,会出现错误:AttributeError: class A has no attribute '__init__',使用PY3不会出现此错误。若改成新式类写法:
class A(object):def print(self):print("I am A")class B(A):def __init__(self):A.__init__(self)
b = B()
仍然用PY2运行,则都不会出错。
  • 新式类可以使用super:

class A(object):def print(self):print("I am A")class B(A):def __init__(self):super(A, self).__init__()
b = B()
  • 多重继承时,各父类的初始化和函数查找顺序不同:旧式类为深度优先继承,新式类为广度优先继承。

静态变量和实例变量
在构造函数中定义的变量,我们称之为实例变量。实例变量只能在实例化后使用<对象名.变量名>的方式访问。静态变量一般定义在类的开始位置,独立于构造函数之外。静态变量既可以<对象名.变量名>的方式访问,也可以<类名.变量名>的方式访问。通常,类的静态变量一般用于保存类的静态属性,该属性可被类的方法使用,但不应该被类的方法修改。
>>> class A:static_x = 10 # 静态变量def __init__(self):self.instance_y = 5 # 实例变量>>> a = A()
>>> a.static_x
10
>>> a.instance_y
5
>>> A.static_x
10
>>> A.instance_y
Traceback (most recent call last):File "<pyshell#89>", line 1, in <module>A.instance_y
AttributeError: type object 'A' has no attribute 'instance_y'
静态函数
与其他语音的静态函数不同,Python的静态函数有两种,都是用装饰器实现的:
class A:static_x= 10def __init__(self):self.y = 10@staticmethoddef staticFuc():print(A.static_x)@classmethoddef classFuc(cls):print(cls.static_x)A.staticFuc()
A.classFuc()

Staticmethod 函数不能使用Self参数,因此不成访问任何成员变量,只能通过类名访问类的静态变量。

Classmethod 函数也不能使用Self参数,因此不成访问任何成员变量,但它有cls参数。cls参数不是对象的引用,而是类的引用,可以通过cls参数访问类的静态变量。
面向对象三要素
面向对象,有三大要素:继承、封装、多态。这里面概念非常多,往往越讲越糊涂。为了不至于误导读者,我尽可能不做解释,只给出例子,请自行揣摩。
(1) 继承
如果派生类只有一个父类,就是单继承。这是最常见的类定义形式。
class Animal:def eat(self):print('我能吃')class Brid(Animal):def __init__(self):Animal.__init__(self)def fly(self):print('我会飞')brid = Brid()
brid.eat()
brid.fly()
如果派生类有多个父类,就是多继承。
class Deer:def showHorns(self):print('我有鹿角')class Horse:def showFace(self):print('我有马脸')class Cow:def showHoof(self):print('我有牛蹄')class Donkey:def showTail(self):print('我有驴尾')class Milu(Deer, Horse, Cow, Donkey): # 多继承派生出一个四不像类def __init__(self):Deer.__init__(self)Horse.__init__(self)Cow.__init__(self)Donkey.__init__(self)milu = Milu()
milu.showHorns()
milu.showFace()
milu.showHoof()
milu.showTail()

不管是单继承还是多继承,都可以在派生类中重写父类的函数——这叫做覆盖。

(2) 封装
所谓封装,就是将类的成员变量、成员函数整合在一起,并对关键的信息进行保护或隐藏。信息保护或隐藏有三个级别:公有、保护、私有。如果你有C++的使用经验,我们先来回顾一下C++的信息隐藏规则:
  1. 公有成员:对类外部的任何代码可见;

  2. 保护成员:对类外部的任何代码都不可见,但对派生类可见;

  3. 私有成员:对类外部及派生类都不可见。

对应这三个级别,Python 是这样定义的:
  1. 以英文字母开头的成员为公有成员
  2. 以一个下划线开头的成员为保护成员
  3. 以两个下划线开关的成员为私有成员
下面我们试试 Python 的信息保护或隐藏规则是否有效。
>>> class A(object):def __init__(self, a, b, c):self.a = 10      # 公有self._b = b      # 保护self.__c = c     # 私有def getA(self):      # 公有return self.adef setA(self, a):   # 公有self.a = adef getB(self):      # 公有return self._bdef _setB(self, b):  # 保护self._b = bdef getC(self):      # 公有return self.__cdef __setC(self, c): # 私有self.__c = c>>> a = A(10, 20, 30)
>>> class B(A):pass
>>> b = B(10, 20, 30)
试试访问公有成员:
>>> a.a
10
>>> a.getA()
10
>>> a.setA(5)
>>> a.a
5
>>> b.a
10
>>> b.getA()
10
>>> b.setA(5)
>>> b.a
5
公有成员访问规则与C++相同。先跳过保护成员,看看私有成员:
>>> a.__c
Traceback (most recent call last):File "<pyshell#85>", line 1, in <module>a.__c
AttributeError: 'A' object has no attribute '__c'
>>> a.getC()
30
>>> a.__setC(5)
Traceback (most recent call last):File "<pyshell#87>", line 1, in <module>a.__setC(5)
AttributeError: 'A' object has no attribute '__setC'
>>> b.__c
Traceback (most recent call last):File "<pyshell#88>", line 1, in <module>b.__c
AttributeError: 'B' object has no attribute '__c'
>>> b.getC()
30
>>> b.__setC()
Traceback (most recent call last):File "<pyshell#90>", line 1, in <module>b.__setC()
AttributeError: 'B' object has no attribute '__setC'

私有成员的访问规则也与C++相同。那我为什么跳过保护成员了?来试试吧:

>>> a._b
20
>>> a._setB(5)
>>> a._b
5

看到这里就已经不对了,应该只有类内部的代码和派生类能使用啊,怎么可以直接用了呢?是的,Python的保护成员访问规则与C++的确实不一样。那 Python 的保护成员是什么样的机制呢?原来,在 Python 的OOP中,保护成员公有成员没有任何区别。保护规则仅适用于 from xxx import * 这一种情况。

testA.py

class A(object):passclass _B(object):pass

testB.py

from testA import *a = A()
b = _B()
执行testB.py时:
Traceback (most recent call last):File "testB.py", line 4, in <module>b = _B()
NameError: name '_B' is not defined
此时,保护成员_B被保护了。但这种情况仅适用于from xxx import *这一种情况。如果testB.py这样写:
testB.py
from testA import A, _Ba = A()
b = _B()
或者:
import testAa = testA.A()
b = testA._B()
则是没有任何问题的。
(3) 多态
当父类有多个派生类,且派生类都实现了同一个成员函数,则可以实现多态:
class H2O(object):def what(self):print("I am H2O")class Water(H2O):def what(self):print("I am water")class Ice(H2O):def what(self):print("I am ice")class WaterVapor(H2O):def what(self):print("I am water vapor");def what(obj):obj.what()objs = [H2O(), Water(), Ice(), WaterVapor()]for obj in objs:what(obj)
抽象类
抽象类不能被实例化,只能作为父类被其它类继承,且派生类必须实现抽象类中所有的成员函数。抽象类应用场景是什么呢?我曾经做过很多下载数据的脚本插件,不同的数据源使用不同的脚本,所有这些脚本要求必须有名字相同的方法,此时,抽象类就派上用场了。
>>> import abc
>>> class A(object, metaclass=abc.ABCMeta):@abc.abstractmethoddef a(self):pass@abc.abstractmethoddef b(self):pass>>> class C(A):def a(self):print("a")>>> c = C()
Traceback (most recent call last):File "<pyshell#127>", line 1, in <module>c = C()
TypeError: Can't instantiate abstract class C with abstract methods b

单例模式

单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中,某个类只能出现一个实例时(如软件配置类,无论在软件的什么地方实例化,永远都是那一个对象),单例模式就能派上用场。比如,Python 日志模块中的日志对象,或者异步通讯框架Twisted 里面的反应堆(reactor),都是典型的单例模式——尽管它们不一定是下面这种方法实现的。
Python可以使用装饰器的方法使用单例模式:
def Singleton(cls):_instance = {}def _singleton(*args, **kargs):if cls not in _instance:_instance[cls] = cls(*args, **kargs)return _instance[cls]return _singleton@Singleton
class Config(object):passcfg1 = Config()
cfg2 = Config()print(cfg1 is cfg2)

后记

行文至此,说几句题外话。CSDN 不止为我们提供了这样一个交流平台,还经常推出各类技术交流活动。近期我将在 GeekTalk 栏目,和 Python 新手共同探讨如何快速成长为基础扎实、功力强大的程序员。CSDN 还为这个活动提供了一些纪念品。如果有兴趣,请扫码加入:

版权声明:本文为CSDN博主「天元浪子」的原创文章。
想为博主点赞?
想要请教博主?
扫描下方二维码,快速获取与博主直面沟通的方式吧!

【END】

Python学习方法Python入门须知!(内附python教程分享)

https://edu.csdn.net/topic/python115?utm_source=csdn_bw

 热 文 推 荐 

光棍节就要到了,要不要给你介绍个 Python 对象?| CSDN 博文精选相关推荐

  1. 光棍节就要到了,要不要给你介绍个 python 对象?

    文章目录 1. 前言 2. 类和对象的概念 3. 类的成员 4. 新式类和旧式类 5. 静态变量和实例变量 6. 静态函数 7. 面向对象三要素 (1) 继承 (2) 封装 (3) 多态 8. 抽象类 ...

  2. 光棍节,我与LoveGate相伴

    老朋友,你好吗?好久不见了,你又长胖了不少-! 昨天(12日)才知道,11月11日是"光棍节",也是在那一天,潜藏在实验室的"爱情"开始了疯狂的爆发.所有开着的 ...

  3. 光棍节程序员闯关秀(总共10关)

    程序员闯关大挑战: https://1111.segmentfault.com/ 仓鼠演示7k7k.4399小游戏: http://cdn.abowman.com/widgets/hamster/ha ...

  4. 光棍节,猪我生日快乐!

    不是光棍,却比光棍更爱过光棍节,无它,因为这天是俺生日!

  5. 祝所有51cto的朋友光棍节快乐

    又是一年一度的光棍节,也不知道是谁发明的这个破节日,想想以前在学校里面没到今天的晚上就是一群哥们出去喝酒,其实大部分都不是光棍也来凑热闹.现在已经没有那样的机会了,祝各位51cto的朋友节日快乐,光棍 ...

  6. FW : 关于光棍节

    中央关于庆祝光棍节的文件 致所有未婚的同学:     群中央,群务院:     我们正处于结婚时代的初级阶段,经过二十几年的努力,虽然取得了结识众多异性的巨大成就,但是人口众多,人均资源相对短缺,局部 ...

  7. 《光棍节程序员闯关秀》闯关攻略

              https://1111.segmentfault.com<光棍节程序员闯关秀>        程序员们,欢迎来攻!!! 在之前的基础上,重新整理了一遍,之前才闯到第8 ...

  8. [2017.11.11特辑]以一个光棍节表白案例浅谈ECMAScript6模块化的使用方法

    双十一,购物节与光棍节,在这个特殊的日子里研究了一下模块化开发的我,突然想结合这个日子,以一个表白的例子浅谈es6模块化的用法. 在之前的 javascript 中一直是没有模块系统的,随着JavaS ...

  9. 11-11 又是一年光棍节!

    11月11日,光棍节,没有哪一天比这一天更形象.更贴切了.当光棍成了"光贵",王老五也有了黄金级和钻石级之分的今天,"光棍节"自然而然地流行开来."光 ...

最新文章

  1. Python培训教程分享:有哪些值得使用的爬虫开源项目?
  2. 深入解析String#intern
  3. Centos下本地连接postgresql时出现认证错误的问题
  4. vue指令写在html中的原理,详解Vue中的MVVM原理和实现方法
  5. datasnap的线程池
  6. Codeforces Round #730 (Div. 2) D2. RPD and Rap Sheet (Hard Version) 交互 + k进制的转换
  7. deno mysql_从 Node 到 Deno
  8. 大三,一点回忆,一点难忘
  9. 怎么样配置java的jdk_如何安装java中的JDK以及配置
  10. Python 基础起步(一)写在开篇的话,写给同为小白的你
  11. CAD工具——批量打印
  12. 【Ubuntu】QT程序 could not find or load the Qt platform plugin “xcb“ in “报错解决
  13. 页面最上方的搜索和logo叫什么_网页顶部导航栏设计总结
  14. 源译识 | 征集开源许可证中文译文,欢迎大家译起来!
  15. Android上传图片到七牛云
  16. (PG\SE\SSE\PL\PM\PD\UI\QA等)软件行业人员职位缩写或简称以及职责划分
  17. mysql修复损坏表_在MySQL中,如何修复损坏的表
  18. HashSet里的元素是不能重复的,那用什么方法来区分重复与否呢?
  19. 【ACL管理与配置(ACL规则编号、通配符、ACL的分类)】(上)-20211214
  20. java用监听捕捉点_使用Robot类创建自己的Java版屏幕捕捉程序

热门文章

  1. 22. 二叉树的层次遍历
  2. [论文翻译] Estimation of Image Rotation Angle Using Interpolation-Related Spectral Signatures
  3. 矩池云Jupyterlab支持download as pdf
  4. opencv一些常用的操作
  5. Docker笔记3 docker的数据管理
  6. Lua笔记3 函数和数组
  7. 剑指Offer之逆序对问题
  8. 第一章 密码学和加密交易的介绍
  9. 中国数字绘图板行业市场供需与战略研究报告
  10. 中国抗生素骨水泥行业市场供需与战略研究报告