美文网首页
1.15一个类的实例:继承、多态

1.15一个类的实例:继承、多态

作者: Benedict清水 | 来源:发表于2020-06-09 14:58 被阅读0次

前言

在本节中我们将编写两个类:

  • Person:一个创建并处理人员相关信息的类。
  • Manager:一个定制化的Person,修改了所继承的行为。

步骤一:创建实例

在python中,模块名使用小写字母开头,而类名使用大写字母开头,这是通用的惯例。我们新的模块文件的命名为person.py。

class Person: 

一个模块文件中可以编写任意多的函数和类,但是如果添加了太多与模块不相关的内容,那么我们person.py的名称就意义不大了。当模块拥有一个单一、内聚的用途的时候,他们工作的会更好。
编写构造函数
给实例属性赋初值得常见方法是在_init_构造函数方法中将初值赋给self。构造函数包含了每次创建一个实例时python都会自动运行的代码。

class Person:
    def __init__(self, name, job, pay):
        self.name = name
        self.job = job
        self.pay = pay

我们传入的数据作为构造函数方法的参数附加到一个实例上,并且将它们赋给self以长期持有。self就是新创建的实例对象,而name、job、pay则是状态信息,即保存在对象中供随后使用的描述性数据。
测试
编写测试代码:

class Person:
    def __init__(self, name, job, pay):
        self.name = name
        self.job = job
        self.pay = pay


if __name__ == "__main__":
    bob = Person("Bob Smith", "dev", "10k")
    sue = Person(name="Sue jones", job="test", pay="8k")
    print("{0} {1} {2}".format(bob.name, bob.job, bob.pay))
    print(sue.name, sue.job, sue.pay)

__name__模块检查用于解决只有当文件作为测试运行而不是导入的时候,才执行文件底部的测试语句。只有当把文件作为顶层脚本运行的时候才测试它,因为这个时候的__name__是__main__。但当做模块导入的时候,则__name__等于模块名称。
两种使用代码的方式:

C:\code>python person.py
Bob Smith dev 10k
Sue jones test 8k
Python 3.7.4 (default, Oct 12 2019, 19:06:48)
[Clang 11.0.0 (clang-1100.0.33.8)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import person
>>>

当被导入时,文件定义了类却没有使用它。当直接运行时,文件创建了类的两个实例,并且打印了每个实例的两个属性。

步骤二:添加行为方法

我们要把操作对象的代码编写到类方法中,而不是分散在整个整个程序中。这里就一种封装的思想,就是把操作逻辑的包装到接口之后,这样每种操作在我们的程序中只会编写一次,通过这种方式,如果将来需要修改,那么只需修改一个版本。

class Person:
    def __init__(self, name, job, pay):
        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__":
    bob = Person("Bob Smith", "dev", 10000)
    sue = Person(name="Sue jones", job="test", pay=8000)
    print("{0} {1} {2}".format(bob.name, bob.job, bob.pay))
    print(sue.name, sue.job, sue.pay)
    print(bob.lastName(), sue.lastName())
    sue.giveRaise(0.10)
    print(sue.pay)
C:\code>python person.py
Bob Smith dev 10K
Sue jones test 8000
Smith jones
8800

方法只是附加给类并旨在处理类的实例的常规函数。实例是方法调用的主体,并且会自动传给方法的self参数。

  • 在第一个调用 bob.lastName()中,bob是传给self的隐含主体。
  • 在第二个调用 sue.lastName()中,sue被传给了self。

步骤三:运算符重载

当我们需要跟踪一个对象的时候,我们发现实例对象默认的显示格式并不是很友好,它显示的是对象的类名以及在内存中的地址。

 print("bob:{}".format(bob))
 print("sue:{}".format(sue))
bob:<__main__.Person object at 0x1056621d0>
sue:<__main__.Person object at 0x105662210>

我们可以通过使用运算符重载来做出更友好的输出——在一个类中编写这样的方法,当方法在类的实例上运行的时候,方法拦截并处理内置的操作。我们可以使用排在__init__之后第二个常用的运算符重载方法__repr__,以及__repr__的孪生兄弟__str__。每次一个实例转化为其打印字符串的时候,该方法都会自动运行。打印一个对象会显示该对象的__str__或__repr__方法(要么是自己定义的,要么是从父类继承的)所返回的内容。

class Person:
    def __init__(self, name, job, pay):
        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))
    def __str__(self):
        return '[Person_str:%s, %s, %s]' % (self.name, self.job, self.pay)
    def __repr__(self):
        return '[Person_repr:%s, %s, %s]' % (self.name, self.job, self.pay)


if __name__ == "__main__":
    bob = Person("Bob Smith", "dev", 10000)
    sue = Person(name="Sue jones", job="test", pay=8000)
    print("bob:{}".format(bob))
    print("sue:{}".format(sue))

运行方式一

C:\code>python person.py
bob:[Person_str:Bob Smith, dev, 10000]
sue:[Person_str:Sue jones, test, 8000]

运行方式二

Python 3.7.4 (default, Oct 12 2019, 19:06:48)
[Clang 11.0.0 (clang-1100.0.33.8)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from person import Person
>>> bob = Person("Bob Smith", "dev", 10000)
>>> bob
[Person_repr:Bob Smith, dev, 10000]
>>> print(bob)
[Person_str:Bob Smith, dev, 10000]

程序提示:调用print函数的时候使用__str__而交互式命令行调用__repr__。

步骤四:继承,编写子类(多态)

场景:我们来定义一个管理人员,管理人员每次涨工资的时候,除了像往常一样接受传入的百分比外,还会获得一个默认10%的额外奖金。即:如果一个管理人员加薪10%,那么它实际得到20%的增长。

class Manager(Person):
    def giveRaise(self, percent, bonus=.10):

现在有两种方式编写Manager的定制方法:一种好的方式和不好的方式。

  • 不好的方式
class Manager(Person):
    def giveRaise(self, percent, bonus=.10):
        self.pay = int(self.pay * (1 + percent + bonus))
  • 好的方式
class Manager(Person):
    def giveRaise(self, percent, bonus=.10):
        Person.giveRaise(self, percent + bonus)

不好的方式是直接复制粘贴代码,基本会使未来的维护工作翻倍。通过类直接调用有效的避开了继承的限制,从而能将调用直接定位到类树的某个特定版本。对于通过类名调用的方式,你需要自己给self传入一个实例,Manger类中对于giveRaise这样的一个内部代码,self已经是调用的主体,因此直接可以通过self传入对应的实例。
下面是本模块完整的代码:

class Person:
    def __init__(self, name, job, pay):
        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))

    def __str__(self):
        return '[%s_str:%s, %s, %s]' % (self.__class__.__name__, self.name, self.job, self.pay)

    def __repr__(self):
        return '[%s_repr:%s, %s, %s]' % (self.__class__.__name__, self.name, self.job, self.pay)


class Manager(Person):
    def giveRaise(self, percent, bonus=.10):
        Person.giveRaise(self, percent + bonus)


if __name__ == "__main__":
    sue = Person(name="Sue jones", job="test", pay=10000)
    print("sue:{}".format(sue))
    sue.giveRaise(.10)
    print("sue:{}".format(sue))
    tom = Manager("Tom Jones", "mgr", 10000)
    print("tom:{}".format(tom))
    tom.giveRaise(.10)
    print("tom:{}".format(tom))
C:\code>python person.py
sue:[Person_str:Sue jones, test, 10000]
sue:[Person_str:Sue jones, test, 11000]
tom:[Manager_str:Tom Jones, mgr, 10000]
tom:[Manager_str:Tom Jones, mgr, 12000]

我们从上面的结果可以看到,sue被分配到person中的giveRaise方法,而tom被分配到Manager中的giveRaise的方法。根据所传递的对象的类型,将会自动运行合适的版本,这就是多态

步骤五:定制和调用父类的构造函数

我们创建Manager对象的时候,为它提供一个mgr的参数似乎是没有意义的,这已经包含在类中了。我们可以重新定制Manager中的__init__方法,提供mgr字符串。

class Person:
    def __init__(self, name, job, pay):
        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))

    def __str__(self):
        return '[%s_str:%s, %s, %s]' % (self.__class__.__name__, self.name, self.job, self.pay)

    def __repr__(self):
        return '[%s_repr:%s, %s, %s]' % (self.__class__.__name__, self.name, self.job, self.pay)


class Manager(Person):
    def __init__(self, name, pay):
        Person.__init__(self, name, 'mgr', pay)  # 调用父类的方法
    
    def giveRaise(self, percent, bonus=.10):
        Person.giveRaise(self, percent + bonus)


if __name__ == "__main__":
    sue = Person(name="Sue jones", job="test", pay=10000)
    print("sue:{}".format(sue))
    sue.giveRaise(.10)
    print("sue:{}".format(sue))
    tom = Manager("Tom Jones", 10000)  # 可以减少“mgr”参数
    print("tom:{}".format(tom))
    tom.giveRaise(.10)
    print("tom:{}".format(tom))

相关文章

  • 1.15一个类的实例:继承、多态

    前言 在本节中我们将编写两个类: Person:一个创建并处理人员相关信息的类。 Manager:一个定制化的Pe...

  • python基础-09-面向对象、装饰器

    面向对象 类概念 类的定义 类的实例化 类和实例的属性 类的私有变量 数据封装 继承 多态 多继承 类的特殊方法 ...

  • 【第 11 天】继承 与 多态

    继承 与 多态 继承 子类继承父类,子类的实例即是子类的类型也是父类的类型。判断是否为某个类型(的实例)可以用函数...

  • 【python面试指北】3.类和面向对象

    面向对象编程 封装、继承、多态 组合与继承 优先使用组合(has a)而非继承(is a) 类变量和实例变量的区别...

  • 16 多态

    面向对象我们讲过了封装和继承,其中继承就有extends,implements 多态是指一个实例对象继承自父类或者...

  • js面向对象实现面向对象(二)

    上一篇讲到js实现对类对封装,本篇介绍类的继承,多态。 类的继承 类式继承 类式继承方式是将父类的实例赋在子类的原...

  • 重拾python第六天

    今天复习了面向对象编程中的类和实例、访问限制、继承和多态

  • 你不知道的JavaScript(上卷):第四章:混合对象类

    这一章到底在说什么? 面向类的设计模式:实例化、继承、多态。 具体怎么说的? 4.1:类理论 什么是类:类/继承表...

  • 对像和类

    Java 对象和类 多态 继承 封装抽象 Java对象和类 类对象实例方法消息解析代码: 11 运行结果: 小狗的...

  • 6.3 面向对象的三大特性(多态性)

    定义 (继承的基础上) 通过类实例化生成的对象具有多种形态 1.引用的多态 2.方法的多态

网友评论

      本文标题:1.15一个类的实例:继承、多态

      本文链接:https://www.haomeiwen.com/subject/yiojtktx.html