Python 进阶_OOP 面向对象编程_类和继承

时间:2023-02-15 16:16:22

目录

类是实例的抽象, 实例是类的具体化.
Python 中的类分为 经典类新式类. 前者现在已经很少用到了, 其特点就是可以不需要继承任何父类;后者则刚好相反, 形式类必须至少有一个父类, 如果没有特定的父类需要继承时, 至少需要继承基类 object, 在后面给出例子.

最简单的类

一个最简单的类可以不具有任何的函数, 仅仅是用作生成一个命名的空间.

In [1]: class MyData(object):
...: pass
...:

In [2]: mydata = MyData()

In [3]: mydata.x = 1

In [4]: mydata.y = 2

In [5]: mydata.x + mydata.y
Out[5]: 3

这样的类作为容器对象来共享命名空间, 类 MyData 的实例 mydata 将 mydata.x 和 mydata.y 关联到同一个命名空间中, 所以也只能通过实例名称结合句点标识符 ‘.’ 来调用, 这就是所谓的使用类作为命名空间容器. 需要注意的是, x 和 y 并不是类的属性而是实例的属性.

类方法

类方法定义在类的代码块中, 只能被类的实例调用, 所以在定义了一个类方法之后, 如果想调用它需要通过下面几个步骤:
1. 创建一个实例对象
2. 用实例对象结合句点标识符调用此类方法

class MyData(object):
def print_foo(self):
print "You invoked print_foo()!"

if __name__ == '__main__':
mydata = MyData()
mydata.print_foo()

上面的函数声明中有一个 self 形参, 这是所有类函数都必须带有的形参, 表示类的实例对象. 类似与 Java 的 this 关键字. 在实例化对象后(生成类实例的过程), 使用类的实例对象调用类函数时, Python 解析器会自动的将类实例对象的引用传递给 self 形参, 所以我们不需要显示的传递该形参的实参. 所以当我使用类 MyData 的实例对象 mydata 调用类函数 print_foo() 时, 并没由传入任何的实参.

那么为什么需要传递类的实例对象给 self 形参呢?
就像之前提到的, 类函数只能通过类实例来调用, 实际上在类定义中我们会频繁的使用到 self 关键字, 这是为了保证类定义内的属性和函数都是被类的实例化对象所调用的, 而且可以实现创建多个不同的类对象。支撑了封装(保证数据的隔离是安全)的实现. self 关键字还有许多需要注意的地方, 这个我们以后再聊.

构造器 __init__()

类函数 __init__() 是 Python 类中预定义的方法,需要被重载才会生效。以双下划线 “__” 开头和结尾, 在 Python 中使用这种命名方式的方法会被理解为是一种特殊方法, Python 的特殊方法功能非常丰富, 种类也很多, 在声明变量名的时候要注意不要和特殊方法重名.

通常,构造器用于在 实例化对象被创建后,返回这个实例之前 的这段时间里,执行一些特定的任务或设置。例如:初始化实例对象属性(以 self 关键字调用的属性)。它在实例化一个新的对象时被自动调用,所以除了初始化实例属性之外,还常被用于运行一些初步的诊断代码。其调用的具体步骤:
1. 创建类的实例化对象
2. Python 解析器检查类是否实现了构造器
3. 若有,则执行构造器的实现,且要求创建对象的时候传入对应的实参。实例对象会传递给第一个形参 self 。
4. 若没有,则直接返回实例对象

一般建议在构造器中设定需要初始化的实例属性

创建一个类

class Add*Entry(object):
"""Address book entry class."""

def __init__(self, name, phone):
self.name = name
self.phone = phone
print "Create instance for:", self.name

def updatePhone(self, new_phone):
self.phone = new_phone
print "Update the phone# for:", self.phone

实例化一个对象

In [3]: jmilkfan = AddrBookEntry('fanguiju', '123-123-123')
Create instance for: fanguiju

NOTE: 在实例化有一个类对象时,需要传递类构造函数中的所有形参,否则会出现 TypeError,所以说构造器的作用之一就是初始化类实例对象所需要的实例属性。

调用实例的方法和属性

通过实例化所得到的对象,拥有在类定义中所有使用 self 关键字来调用的属性和方法。这些被称之为实例化对象的属性和方法。

In [4]: jmilkfan.name
Out[4]: 'fanguiju'

In [5]: jmilkfan.phone
Out[5]: '123-123-123'

In [6]: jmilkfan.updatePhone("456-456-455")
Update the phone# for: 456-456-455

In [7]: jmilkfan.phone
Out[7]: '456-456-455'

创建子类

子类的创建通过集成父类的方法来实现,子类拥有父类所有公开的属性和方法。并且在子类中还可以对这些继承而来的方法和属性进行重载(在不改变属性和方法名的前提之下,修改属性值或方法的内部实现),而不会影响到父类的定义。所以子类除了集成父类的属性和方法之外,还可以修改继承所得到的属性和方法,也可以定义新的属性和方法。这样的话就大大增加了代码的重用。

NOTE:子类应该拥有自己的构造器,若有,则在实例化子类对象时,除了要传入子类对象所需要初始化的实参之外,还应传入实例化父类对象所需要的实参,且需要在子类构造器中显式的调用父类构造器来完成传递这些实参;若没有,则默认使用父类的构造器。

class EmpAddressBookEntry(AddrBookEntry):
"Employee address book entry class"

def __init__(self, name, phone, id, email):
AddrBookEntry.__init__(name, phone)
self.id = id
self.email = email
print "Create instance for:", self.name

def updateEmail(self, new_email):
self.email = email
print "Update the Email# for:", self.email

因为子类重载了父类的构造器,所以必须显式(className.__init__())的写出父类构造器才会被调用。

而且需要注意的是:我们还需要显式的将 self 传递给父类构造器,在子类中的 self 表示子类的实例化对象。所以如果希望子类的实例化对象能够调用父类的属性和方法的话就需要将表示子类实例化对象的 self 传递给父类,来替换父类的实例化对象,从而实现了将子类的实例化对象绑定到父类。这样才真正的实现了继承。
NOTE
1. 在子类的声明语句中继承了父类 AddrBookEntry
2. 在子类的构造器中调用了父类的构造器
3. 在子类中定义了新的子类属性和方法

使用 super() 来调用父类的构造器

一般来说我们很少使用 AddrBookEntry.__init__(name, phone) 这种方式来调用父类的构造器,因为 Python 支持多继承,所以我们希望可以有方法让子类自动的找到父类,这个方法就是 super() .

In [33]: class EmpAddressBookEntry(AddrBookEntry):
...: "Employee address book entry class"
...:
...: def __init__(self, name, phone, id, email):
...: super(EmpAddressBookEntry, self).__init__(name, phone)
...: self.id = id
...: self.email = email
...: print "Create instance for:", self.name
...:
...: def updateEmail(self, new_email):
...: self.email = new_email
...: print "Update the Email# for:", self.email

super(EmpAddressBookEntry, self) 语句返回了类 EmpAddressBookEntry 的父类,并且通过句点标识符来调用且传递了实参数给父类的构造器。

实例化子类对象

In [16]: jmilk_fan = EmpAddressBookEntry('fanguiju', '123-123-123-123', 1, 'fanguiju@gmail.com')
Create instance for: fanguiju
Create instance for: fanguiju

实例化子类对象时,需要一同传递实例化父类对象的实参(name, phone)。

调用子类的属性和方法

Out[17]: 'fanguiju'

In [18]: jmilk_fan.phone
Out[18]: '123-123-123-123'

In [19]: jmilk_fan.id
Out[19]: 1

In [20]: jmilk_fan.email
Out[20]: 'fanguiju@gmail.com'

In [22]: jmilk_fan.updatePhone("456-456-456")
Update the phone# for: 456-456-456

In [23]: jmilk_fan.phone
Out[23]: '456-456-456'

In [29]: jmilk_fan.email
Out[29]: 'fanguiju@163.com'

因为子类 EmpAddressBookEntry 继承了父类 AddrBookEntry 所以子类也就拥有了父类的属性和方法,可以通过子类对象或直接在子类定义中调用。

类/属性/方法的命名规则

  • class: 大写字母开头的驼峰规则,EG class MyClass()

  • def: 小写字母开头的下划线驼峰规则,EG def set_my_function()。方法名应当指出对对象执行的操作,为名称和动词的结合。

  • 属性: 全小写字母的下划线驼峰规则,EG my_name =。属性名一般使用名称,代表一个对象的称呼。