关于类的继承和静态属性

在这一节,结合之前学过的内容,我们来看如何在Python中表示类之间的继承关系。

首先,基于上一节的例子,我们创建了三个文件:

  • Person.py
  • Employee.py
  • Main.py

现在,我们打开Employee.py,在其中给Person添加一个派生类Empolyee

from Person import Person

class Employee(Person):

在这里例子里,有三点值得我们注意:

第一、为了在Employee.py中使用Person,我们在文件开始使用了from Person import Person。由于Person定义在Person.py中,因此from后面的模块名是Person,从中,我们引入了class Person。虽然它们同名,大家要把它们分清楚。

第二、我们的类定义,要和import语句保持两个空行,这是Python中推荐的编码习惯。

第三、Python中,我们用(Person)这样的形式表示基类。在这个括号里,我们可以用逗号分隔多个基类。但经验告诉我们,多重继承在更多时候并不会像我们想象一样正常工作。因此,我们还是暂时只讨论单一继承的情况。

派生类的init方法

定义了派生类之后,第一件事,就是定义它的__init__方法,就像你已有的OO经验一样,在派生类的__init__方法里,我们要初始化派生类和基类两部分内容:

class Employee(Person):
    def __init__(self, work_id, name, age):
        Person.__init__(self, name, age)
        self.work_id = work_id

这里唯一要注意的是派生类调用基类__init__的方法,是通过类名调用的。然后,我们就可以这样来创建派生类对象了。在Main.py中,添加下面的代码:

from Employee import Employee

mars = Employee(11, 'mars', 30)

既然有了派生类,继续之前,我们介绍几个判断类关系的函数。要判断某个类是否是另外一个类的派生类可以使用issubclass

print(issubclass(Employee, Person))  # True

它的第一个参数是派生类类型,第二个参数是基类类型,最后,返回一个boolean值。要判断一个对象是否是某个类的对象,可以使用isinstance

print(isinstance(mars, Person))    # True
print(isinstance(mars, Employee))  # True

它的第一个参数是类对象,第二个参数是要判断所属的类型,同样返回一个boolean值。要注意的是,这两个函数都直接传给它们类型就好了,并不用给它们传递类型的字符串名称。

重写类方法

在Python里,重写基类方法和在派生类中定义方法是没有任何区别的,我们不用像Swift一样使用override关键字,只要重定义了,就算是重写了。

我们通过重写几个Python class中内置的方法,来理解这个过程。实际上,之前我们已经干过这个事了,就是在派生类中重写__init__。除此之外,class中还有以下可以重写的方法。

理解reprstr

每个Python class都包含了两个和类型的字符串表达方式有关的方法,叫做__repr____str__,默认情况下,它们返回的结果是相同的。在Main.py中,添加下面的代码:

print(mars.__repr__())
print(mars.__str__())

# <Employee.Employee object at 0x109563550>

执行一下就会看到,它们返回的都是类似上面注释中的结果。那么,为什么要有两个方法呢?也许你可以找到很多关于这个问题的讨论。但在我看来,理解下面这两点就足以让你用对它们了:

  • 首先,__str__是通过调用__repr__实现的,因此,它的默认实现确没什么特别的用途;
  • 其次,__repr__是给一个类型的开发者使用的,它应该包含关于类型的更多信息;而__str__的描述则是针对类型的使用者使用的,它应该尽可能易读、友好;

有了上面这两个原则之后,我们就可以试着改写__str__的实现,让它只返回类型名称本身,例如这样:

class Employee(Person):
    # ...

    def __str__(self):
        return "Employee"

稍后,我们还会看到更好的获取类型名称的方法,这里我们只是直接返回了Employee。重新执行下,就会发现,此时__repr____str__的结果不同了。

'''
<Employee.Employee object at 0x10e388518>
Employee
'''

重定义对象的比较行为

除了重定义类型的表达方式之外,我们还可以明确指定两个类对象的比较方法。在Python 2的时候,这是通过重写__cmp__方法实现的。但在Python 3中,已经不提倡这么做了。Python 3中提供了一组表意更明确的函数:

  • __eq__: equal
  • __ne__: not equal
  • __lt__: less than
  • __le__: less equal
  • __gt__: greater than
  • __ge__: greater equal

首先来看,当我们不定义这些方法的时候,比较两个Employee对象,执行的是比较对象引用的操作:

mars = Employee(11, 'mars', 30)
eleven = Employee(11, 'mars', 30)

print(mars == eleven)  # false

显然,marseleven引用的是不同的对象,因此这个比较结果肯定是False。这时,如果我们希望按照对象的值比较,认为只要姓名、年龄和工号相等,那么两个Employee对象就相等,就可以重写__eq__方法:

class Employee(Person):
    # ...
    def __eq__(self, other):
        return self.name == other.name and \
               self.age == other.age and \
               self.work_id == other.work_id

可以看到,__eq__有两个参数,self可以理解为==的左操作数,other==的右操作数。而它的比较逻辑,就是逐个比较Employee的每个属性,很简单。

定义好__eq__之后,之前的比较结果,就变成True了。

定义Class attributes

在这一节最后,我们来看如何给class添加静态属性,在Python里,这叫做class attribute

之前我们提到过,所有直接定义在__init__方法里的属性,都是类对象属性,它们都是绑定在某个对象上的。如果我们把属性定义在__init__外面,这个属性就被所有的类对象共享了。例如,我们添加一个统计所有员工对象数量的counter

class Employee(Person):
    counter = 0

可以看到,counter的初始化是在定义的时候完成的。这样,counter就会被所有的类对象共享了,我们可以在每次__init__方法被调用的时候,把它加1:

def __init__(self, work_id, name, age):
    Person.__init__(self, name, age)
    self.work_id = work_id
    Employee.counter += 1

此时,由于我们有marseleven两个Employee对象,访问counter的时候,得到的值,就是2了:

mars = Employee(11, 'mars', 30)
eleven = Employee(11, 'mars', 30)

print(Employee.counter)  # 2

在上面的例子里可以看到看到,访问class attribute的时候,我们要使用ClassName.Attribute这样的形式。

看到这,你可能会想,这个counter可以随便被人修改啊,用它来计数并不靠谱。没错,默认情况下,class的所有的属性和方法都是可以被外部访问的。如果我们要隐藏这个属性,可以在它前面使用两个下划线:

class Employee(Person):
    __counter = 0

    def __init__(self, work_id, name, age):
        Person.__init__(self, name, age)
        self.work_id = work_id
        Employee.__counter += 1

然后,在Main.py里,无论你用哪种方式访问counter

print(Employee.counter)
print(Employee.__counter)

都会触发AttributeError异常,并提示我们对应的counter属性不存在。

不过,你也别太当真,因为这至多也就是一层设计意图上的保障罢了,它只能提醒你自己,__counter只能用在Employee内部。

因为,Python中有一个魔术前缀,就是一个下划线加上类名,对于我们的例子来说,就是_Employee,通过它,你就可以访问到私有成员了。于是,在Main.py里,我们把代码改成这样:

print(Employee._Employee__counter)  # 2

就可以恢复执行了,而且,读写均可 :-)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容

  • 1/579文件IO文件介绍I/O流流的概念读写文件文件备份IO介绍大家应该听说过一句话:“好记性不如烂笔头”。不仅...
    清清子衿木子水心阅读 1,432评论 0 1
  • 5继承 5.1 类、超类和子类 重用部分代码,并保留所有域。“is-a”关系,用extends表示。 已存在的类被...
    我快要上天啦阅读 775评论 1 3
  • 写在前面的话 代码中的# > 表示的是输出结果 输入 使用input()函数 用法 注意input函数输出的均是字...
    FlyingLittlePG阅读 2,755评论 0 8
  • 目录 一,Python 面向对象 二,Python 正则表达式 三,Python CGI编程 四,Python 操...
    weiwei_js阅读 217评论 0 0
  • 要点: 函数式编程:注意不是“函数编程”,多了一个“式” 模块:如何使用模块 面向对象编程:面向对象的概念、属性、...
    victorsungo阅读 1,504评论 0 6