返回顶部
首页 > 资讯 > 后端开发 > Python >Python面向对象基础:编码细节和注意
  • 529
分享到

Python面向对象基础:编码细节和注意

面向对象细节基础 2023-01-30 23:01:28 529人浏览 安东尼

Python 官方文档:入门教程 => 点击学习

摘要

在前面,我用了3篇文章解释python的面向对象: 面向对象:从代码复用开始 面向对象:设置对象属性 类和对象的名称空间 本篇是第4篇,用一个完整的示例来解释面向对象的一些细节。 例子的模型是父类Employe和子类Manag

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

  1. 面向对象:从代码复用开始
  2. 面向对象:设置对象属性
  3. 类和对象的名称空间

本篇是第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文件,得到结果:

<__main__.Employe object at 0x01321690>
<__main__.Employe object at 0x01321610>

添加方法

每个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()方法。有两种方式可以重写:

  1. 完全否定父类方法
  2. 在父类方法的基础上进行扩展

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

下面是第一种方式重写:

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)

## 结果:
<__main__.Employe object at 0x01321690>
<__main__.Employe object at 0x01321610>

我们可以自定义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__"]
<slot wrapper '__str__' of 'object' objects>

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

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

>>> a=3
>>> type(a)
<class 'int'>

>>> int.__dict__["__add__"]
<slot wrapper '__add__' of 'int' objects>

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

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

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

序列化

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

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

--结束END--

本文标题: Python面向对象基础:编码细节和注意

本文链接: https://lsjlt.com/news/180428.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Python面向对象基础:编码细节和注意
    在前面,我用了3篇文章解释python的面向对象: 面向对象:从代码复用开始 面向对象:设置对象属性 类和对象的名称空间 本篇是第4篇,用一个完整的示例来解释面向对象的一些细节。 例子的模型是父类Employe和子类Manag...
    99+
    2023-01-30
    面向对象 细节 基础
  • Python面向对象编程基础
    面向对象编程是Python中的核心之一,面向对象的核心并不是概念,语法,使用有多么复杂,而是一种编程思想,并不是掌握了类创建与使用就真正掌握了面向对象编程,这需要在不断工作与练习中逐步提升;抛去代码,我们先来看现实世界的基本概念: 类: 我...
    99+
    2023-01-31
    面向对象 基础 Python
  • Python基础(六)——面向对象编程
      这一部分难得和 Java 较为一致,直接写个例子: 1 class Stu: 2 def __init__(self, name, id): # 构造方法 3 self.name = name 4 ...
    99+
    2023-01-31
    面向对象 基础 Python
  • Python面向对象基础
    NOTE:重要强调:    Python的作用域和命名空间(1)命名空间 是从命名到对象的映射    ①内置命名空间    ②全局命名空间:模块    ③本地命名空间:模块中的函数和类(2)作用域   是一个 Python 程序可以直接访...
    99+
    2023-01-30
    面向对象 基础 Python
  • Python基础之面向对象基础
    面向对象编程(Object-Oriented Programming,简称OOP)是一种编程思想,它将程序中的数据和操作封装成对象,...
    99+
    2023-09-23
    Python
  • python基础:面向对象详解
    目录1.私有方法2.私有属性3.类部调用私有属性和私有方法  4.子类不能继承父类私有属性和方法  5.修改私有属性的值  ...
    99+
    2024-04-02
  • Python全栈之面向对象基础
    目录1. 面向对象oop了解2. 对象的相关操作小提示:3. 类的相关操作4. 类对象的删除操作小提示:5. 小练习小提示:答案:总结1. 面向对象oop了解 # ### oop...
    99+
    2024-04-02
  • python基础之函数和面向对象详解
    目录函数函数参数变量作用域内嵌函数和闭包lambda 表达式面向对象三大特性类、类对象 和 实例对象类属性 和 对象属性私有魔法方法基本的魔法方法算术运算符属性访问 描述符...
    99+
    2024-04-02
  • 面向对象编程的基础:PHP中类和对象的构建
    在当今 Web 开发处于顶峰的数字时代,开发人员必须深入了解面向对象编程 (OOP) 及其如何应用于 PHP。PHP 是一种服务器端脚本语言,已经发展了多年,面向对象的 PHP 是现代 Web 开发的一个基础。在这篇文章中,我们将深入研究面...
    99+
    2023-11-07
    php PHP面向对象编程
  • python基础之面对对象基础类和对象的概念
    简称oop 复习 面向对象编程,简称oop [object oriented programming] 是一种python的编程思想 面...
    99+
    2024-04-02
  • Python面向对象基础举例分析
    本篇内容主要讲解“Python面向对象基础举例分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Python面向对象基础举例分析”吧!1. 面向对象oop了解# ### oo...
    99+
    2023-06-21
  • Python基础:一起来面向对象 (二)
    实例 搜索引擎   一个搜索引擎由搜索器、索引器、检索器和用户接口四个部分组成   搜索器就是爬虫(scrawler),爬出的内容送给索引器生成索引(Index)存储在内部数据库。用户通过用户接口发出询问(query),询问解析后送达检索...
    99+
    2023-01-31
    面向对象 基础 Python
  • PHP学习笔记:面向对象编程基础
    导言:面向对象编程(Object-Oriented Programming,简称OOP)是一种编程的思维方式,通过将问题分解为多个对象并定义对象之间的交互,来解决复杂的编程问题。PHP作为一门功能强大的编程语言,也支持面向对象编程。本文将介...
    99+
    2023-10-21
    PHP 基础 面向对象编程(OOP)
  • Python基础之面向对象进阶详解
    目录面向对象三大特征介绍继承语法格式查看类的继承层次结构object根类dir()查看对象属性str()方法的重写多重继承MRO()super()获得父类定义多态特殊方法和重载运算符...
    99+
    2024-04-02
  • Python编程基础之类和对象
    目录零、本讲学习目标一、面向对象(一)程序员“面向对象”(二)两种编程思想实现五子棋(三)面向过程 vs. 面向对象 (四)面向对象三大特点1、封装...
    99+
    2024-04-02
  • Java 封装与继承:面向对象的编程基础
    封装 封装是指将一个对象的内部细节与其外部接口分离。通过封装,我们可以控制对对象内部状态的访问,从而提高代码的安全性、可读性和维护性。 作用域:封装允许我们定义成员变量和方法的访问修饰符(如 private、protected 和 pu...
    99+
    2024-03-12
    面向对象编程(OOP)是计算机科学中广泛使用的一种编程范式 它以对象为基础。封装和继承是 OOP 中至关重要的概念 它们允许代码的可重用性、可维护性和灵活性。
  • Java面向对象基础知识之抽象类和接口
    抽象类(abstract): 抽象类不能创建实例,它只能作为父类被继承。抽象类是从多个具体类中抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出一个抽象类,以这个抽...
    99+
    2024-04-02
  • python面向对象基础之常用魔术方法
    目录一、类和对象二、魔法方法三、理解self四、练习对战一、类和对象 通俗理解:类就是模板,对象就是通过模板创造出来的物体 类(Class)由3个部分构成: 类的名称: 类名 类的...
    99+
    2024-04-02
  • Python基础09 面向对象的进一步拓展
    我们熟悉了对象和类的基本概念。我们将进一步拓展,以便能实际运用对象和类。调用类的其它信息上一讲中提到,在定义方法时,必须有self这一参数。这个参数表示某个对象。对象拥有类的所有性质,那么我们可以通过self,调用类属性。...
    99+
    2023-06-02
  • Java面向对象基础知识之委托和lambda
    委托定义类型,类型指定特定方法签名。可将满足此签名的方法(静态或实例)分配给该类型的变量,然后(使用适当参数)直接调用该方法,或将其作为参数本身传递给另一方法再进行调用。以下示例演示...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作