在前面,我用了3篇文章解释python的面向对象:

本篇是第4篇,用一个完整的示例来解释面向对象的一些细节。

例子的模型是父类Employe和子类Manager,从类的定义开始,一步步完善直到类变得完整。

定义Employe类

现在,假设Employe类有3个属性:名字name、职称job和月薪水pay。

定义这个类:

class Employe():

def __init__(self, name, job=None, pay=0):

self.name = name

self.job = job

self.pay = pay

这里为__init__()的job参数提供了默认值:None,表示这个员工目前没有职称。对于没有职称的人,pay当然也应该是0。这样创建Employe对象的时候,可以只给参数name。

例如:

if __name__ == "__main__":

longshuai = Employe("Ma Longshuai")

xiaofang = Employe("Gao Xiaofang", job="accountant", pay=15000)

上面的if判断表示这个py文件如果当作可执行程序而不是模块,则执行if内的语句,如果是以模块的方式导入这个文件,则if内的语句不执行。这种用法在测试模块代码的时候非常方便。

运行该py文件,得到结果:

添加方法

每个Employe对象的name属性由姓、名组成,中间空格分隔,现在想取出每个对象的名。对于普通的姓 名字符串,可以使用字符串工具的split()函数来处理。

例如:

>>> name = "Ma Longshuai"

>>> name.split()[-1]

'Longshuai'

于是可以在longshuai和xiaofang这两个Employe对象上:

print(longshuai.name.split()[-1])

print(xiaofang.name.split()[-1])

结果:

Longshuai

Xiaofang

与之类似的,如果想要为员工按10%加薪水,可以在每个Employe对象上:

xiaofang.pay *= 1.1

print(xiaofang.pay)

无论是截取name的名部分,还是加薪水的操作,都是Employe共用的,每个员工都可以这样来操作。所以,更合理的方式是将它们定义为类的方法,以便后续的代码复用:

class Employe():

def __init__(self, name, job=None, pay=0):

self.name = name

self.job = job

self.pay = pay

def lastName(self):

return self.name.split()[-1]

def giveRaise(self, percent):

self.pay = int(self.pay * (1 + percent))

if __name__ == "__main__":

longshuai = Employe("Ma Longshuai")

xiaofang = Employe("Gao Xiaofang", job="accountant", pay=15000)

print(longshuai.lastName())

print(xiaofang.lastName())

xiaofang.giveRaise(0.10)

print(xiaofang.pay)

上面的giveRaise()方法中使用了int()进行类型转换,因为整数乘以一个小数,返回结果会是一个小数(例如15000 * 0.1 = 1500.0)。这里我们不想要这个小数,所以使用int()转换成整数。

定义子类并重写父类方法

现在定义Employe的子类Manager。

class Manager(Employe):

Manager的薪水计算方式是在原有薪水上再加一个奖金白分别,所以要重写父类的giveRaise()方法。有两种方式可以重写:

完全否定父类方法

在父类方法的基础上进行扩展

虽然有了父类的方法,拷贝修改很方便,但第一种重写方式仍然是不合理的。合理的方式是采用第二种。

下面是第一种方式重写:

class Manager(Employe):

def giveRaise(self, percent, bonus=0.10):

self.pay = int(self.pay * (1 + percent + bonus))

这种重写方式逻辑很简单,但是完全否定了父类的giveRaise()方法,完完全全地重新定义了自己的方法。这种方式不合理,因为如果修改了Employe中的giveRaise()计算方法,Manager中的giveRaise()方法也要修改。

下面是第二种在父类方法基础上扩展,这是合理的重写方式。

class Manager(Employe):

def giveRaise(self, percent, bonus=0.10):

Employe.giveRaise(self, percent + bonus)

第二种方式是在自己的giveRaise()方法中调用父类的giveRaise()方法。这样的的好处是在需要修改薪水计算方式时,要么只需修改Employe中的,要么只需修改Manager中的,不会同时修改多个。

另外注意,上面是通过硬编码的类名Employe来调用父类方法的,虽然不适合后期维护。但好在并没有任何影响。因为调用时明确指定了第一个参数为self,而self代表的是对象自身,所以逻辑上仍然是对本对象的属性self.pay进行修改。

Python支持另一只更好的调用父类方法的方式:super()。这个函数有点复杂,但对于基本的调用父类方法来说,用法无比的简单。修改上面的Manager类:

class Manager(Employe):

def __init__(self, name, pay):

super().__init__(name, "mgr", pay)

def giveRaise(self, percent, bonus=0.10):

super().giveRaise(percent + bonus)

测试下:

if __name__ == "__main__":

wugui = Manager("Wu Xiaogui", "mgr", 15000)

wugui.giveRaise(0.1, 0.1)

print(wugui.pay)

一般在重写方法的时候,只要允许,就应该选择在父类基础上进行扩展重写。如果真的需要定义完全不同的方法,可以不要重写,而是在子类中定义新的方法。当然,如果真的有需求要重写,且又要否定父类方法,那也没办法,不过这种情况基本上都是因为在类的设计上不合理。

定制子类构造方法

对于子类Manager,每次创建对象的时候其实没有必要去传递一个参数"job=mgr"的参数,因为这是这个子类自然具备的。于是,在构造Manager对象的时候,可以让它自动设置"job=mgr"。

所以,在Manager类中重写__init__()。既然涉及到了重写,就有两种方式:(1)完全否定父类方法,(2)在父类方法上扩展。无论何时,总应当选第二种。

以下是Manager类的定义:

class Manager(Employe):

def __init__(self, name, pay):

Employe.__init__(self, name, "mgr", pay)

def giveRaise(self, percent, bonus=0.10):

Employe.giveRaise(self, percent + bonus)

现在构造Manager对象的时候,只需给name和pay就可以:

if __name__ == "__main__":

wugui = Manager("Wu Xiaogui", 15000)

wugui.giveRaise(0.1, 0.1)

print(wugui.pay)

子类必须重写方法

有些父类中的方法可能会要求子类必须重写。

本文的这个示例不好解释这一点。下面简单用父类Animal、子类Horse、子类Sheep、子类Cow来说明,这个例子来源于我写的面向对象相关的第一篇文章:从代码复用开始。

现在要为动物定义叫声speak()方法,方法的作用是输出"谁发出了什么声音"。看代码即可理解:

class Animal:

def __init__(self, name):

self.name = name

def speak(self):

print(self.name + " speak " + self.sound())

def sound(self):

raise NotImplementedError("you must override this method")

在这段代码中,speak()方法调用了sound()方法,但Animal类中的sound()方法却明确抛出异常"你必须自己实现这个方法"。

为什么呢?因为每种动物发出的叫声不同,而这里又是通过方法来返回叫声的,不是通过属性来表示叫声的,所以每个子类必须定义自己的叫声。如果子类不定义sound(),子类对象调用self.sound()就会搜索到父类Animal的名称空间上,而父类的sound()会抛出错误。

现在在子类中重写sound(),但是Cow不重写。

class Horse(Animal):

def sound(self):

return "neigh"

class Sheep(Animal):

def sound(self):

return "baaaah"

class Cow(Animal):

pass

测试:

h = Horse("horseA")

h.speak()

s = Sheep("sheepA")

s.speak()

c = Cow("cowA")

c.speak()

结果正如预期,h.speak()和s.speak()都正常输出,但c.speak()会抛出"you must override this method"的异常。

再考虑一下,如果父类中不定义sound()会如何?同样会在c.speak()时抛出错误。虽然都会终止程序,但是这已经脱离了面向对象的代码复用原则:对于对象公有的属性,都应该抽取到类中,对于类所公有的属性,都应该抽取到父类中。sound()显然是每种动物都应该具备的属性,要么定义为子类变量,要么通过类的方法来返回。

之前也提到过,如果可以,尽量不要定义类变量,因为这破坏了面向对象的封装原则,打开了"黑匣子"。所以最合理的方法,还是每个子类重写父类的sound(),且父类中的sound()强制要求子类重写。

运算符重载

如果用print()去输出我们自定义的类的对象,比如Employe对象,得到的都是一个元数据信息,比如包括类型和地址。

例如:

print(longshuai)

print(xiaofang)

## 结果:

我们可以自定义print()如何输出对象,只需定义类的__str__()方法即可。只要在类中自定义了这个方法,print()输出对象的时候,就会自动调用这个__str__()取得返回值,并将返回值输出。

例如,在输出每个Employe对象的时候,都输出它的name、job、pay,并以一种自定义的格式输出。

class Employe():

def __init__(self, name, job=None, pay=0):

self.name = name

self.job = job

self.pay = pay

def lastName(self):

return self.name.split()[-1]

def giveRaise(self, percent):

self.pay = int(self.pay * (1 + percent))

## 重载__str__()方法

def __str__(self):

return "[Employe: %s, %s, %s]" % (self.name, self.job, self.pay)

现在再print()输出对象,将得到这个对象的信息,而不是这个对象的元数据:

print(longshuai)

print(xiaofang)

## 结果:

[Employe: Ma Longshuai, None, 0]

[Employe: Gao Xiaofang, accountant, 15000]

实际上,print()总是会调用对象的__str__(),如果类中没有定义__str__(),就会查找父类中的__str__()。这里Employe的父类是祖先类object,它正好有一个__str__():

>>> object.__dict__["__str__"]

换句话说,当Employe中定义了__str__(),就意味着重载了父类object的__str__()方法。而这个方法正好是被print()调用的,于是将这种行为称之为"运算符重载"。

可能从print()上感受不到为什么是运算符,换一个例子就很好理解了。__add__()是决定加号+运算模式的,比如3 + 2之所以是5,是因为int类中定义了__add__()。

>>> a=3

>>> type(a)

>>> int.__dict__["__add__"]

这使得每次做数值加法运算的时候,都会调用这个__add__()来决定如何做加法:

实际上在类中定义构造函数__init__()也是运算符重载,它在每次创建对象的时候被调用。

还有很多运算符可以重载,加减乘除、字符串串联、大小比较等等和运算符有关、无关的都可以被重载。在后面,会专门用一篇文章来介绍运算符重载。

序列化

对象也是一种数据结构,数据结构可以进行序列化。通过将对象序列化,可以实现对象的本地持久性存储,还可以通过网络套接字发送给网络对端,然后通过反序列化可以还原得到完全相同的原始数据。

序列化非本文内容,此处仅是介绍一下该功能,后面我会写几篇专门介绍python序列化的文章。

python面对对象建立自己的电子宠物的编码_Python面向对象基础:编码细节和注意事项...相关推荐

  1. python面对对象建立自己的电子宠物的编码_父与子的编程之旅:与小卡特一起学Python(第3版)(全彩印刷)...

    Python青少年编程启蒙畅销书全新升级! 上一版豆瓣评分8.5分,被众多老师.家长推荐! 左耳朵耗子.爱编程的魏校长.周自恒倾力推荐! 1.第3版的示例使用Python 3 而不是Python 2, ...

  2. python面对对象建立自己的电子宠物的编码_一种基于Kinect技术的电子宠物的制作方法...

    本发明涉及电子宠物技术领域,特别涉及一种基于Kinect技术的电子宠物. 背景技术: 目前人工智能发展迅速,现在的电子宠物动作单一,并且不能够与人进行交互,对周围的环境不能够准确识别,面对用户的动作只 ...

  3. python遇到对象_### python面对对象小汇总 #####

    python面对对象小汇总 类的起名规则 # 所有的类名要求首字母大写,多个单词使用驼峰式命名法 如 ValueError # 类中方法: 普通方法 类方法 静态方法 魔术方法 class Phone ...

  4. Python - 面对对象(基础)

    目录 Python - 面对对象(基础) 一. 概述 二. 创建类和对象 三. 面向对象三大特征 封装 继承 多态 Python - 面对对象(基础) 一. 概述 面向过程:根据业务逻辑从上到下写垒代 ...

  5. python面对对象的编程语言_python面对对象编程

    一.创建类class ClassName: '类文档字符串,用于解释说明 'class_body #类体 下面来创建一个银行客户的例子class Customer: '编写银行的顾客类' name=' ...

  6. python和对象复习_python 面向对象基础和高级复习

    面向对象基础 面向对象编程 面向过程编程:类似于工厂的流水线 优点:逻辑清晰 缺点:扩展性差 面向对象编程:核心是对象二字,对象属性和方法的集合体,面向对象编程就是一堆对象交互 优点:扩展性强 缺点: ...

  7. python面向对象基础_python面向对象基础

    面向对象基础 一.面向对象和面向过程 面向过程: 优点:复杂问题流程化,进而简单化 确定:可扩展性差 面向对象: 优点:可扩展性高 缺点:编写复杂度高于面向过程 二.类与对象 类即类别.种类,是面向对 ...

  8. python面向对象六大原则_Python面向对象基础入门之编码细节与注意事项

    前言 在前面,我用了3篇文章解释python的面向对象: 本篇是第4篇,用一个完整的示例来解释面向对象的一些细节. 例子的模型是父类Employe和子类Manager,从类的定义开始,一步步完善直到类 ...

  9. python 输出字符串编码_Python print 字符串编码问题

    又一次, 被Python的编码问题搞得焦头烂额. 过去的记忆中, Python给我的印象之一就是让人痛不欲生的编码问题, 后来基本不碰Python很可能与此有关. 这次又用到Python了, 基本上, ...

最新文章

  1. [ffmpeg]安装
  2. java字符串的用法_Java字符串的重要方法的使用实例
  3. python 图像处理(从安装Pillow开始)
  4. mysql5.6 pid_MySQL5.6启动报错The server quit without updating PID file
  5. Intent 隐示意图
  6. [转】Python--遍历列表时删除元素的正确做法
  7. 二年级四则运算扩展,可以指定题目数量,并可支持真分数运算
  8. 动易CMS - 模板的一些常用标签
  9. 国外博士论文下载网址
  10. 使用Memberane Moniter监控HTTP SOAP requests
  11. 知到python课程答案-智慧树知到APPPython数据分析与数据可视化网课答案
  12. 智慧城市建设主要包括哪些方面
  13. Python机器学习、深度学习库总结
  14. Ubuntu 系统备份 恢复
  15. 吴恩达深度学习coursework1
  16. iPhone连接电脑WIN7不显示Apple iPhone便携式设备的解决办法
  17. python 做绘图工具
  18. 如何禁用Mac系统的Spotlight
  19. 小公司的程序员,老想跳槽怎么办?
  20. android Gui系统之SurfaceFlinger(1)---SurfaceFlinger概论

热门文章

  1. 7+ Taskbar Tweaker的使用
  2. 【机器学习】关于t-sne:降维、可视化
  3. 若依启动报错 .QRTZ_LOCKS‘ doesn‘t exist
  4. JAVA——实现古典问题之不死神兔问题
  5. 【实验课】一群人围一圈,隔一个杀一个,剩一个,最后活谁?
  6. 53岁周鸿祎考上清华博士;图灵奖得主杨立昆炮轰ChatGPT:五年内就没人用了;Red Hat恪守对开源的承诺|极客头条...
  7. GoogleSEO工作基本原理是什么?
  8. 邻居子系统之邻居项状态更新
  9. 设计师更高效_要成为更好的设计师,我们需要更多的时间进行游戏
  10. php macaw路由没进入路由5入口,构建路由 · composer-PHP框架 · 看云