提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
类的封装与继承
1. 类的封装
和其它面向对象的编程语言(如 C++、Java)不同,Python 类中的变量和函数,不是公有的(类似 public 属性),就是私有的(类似 private),这 2 种属性的区别如下:
- public:公有属性的类变量和类函数,在类的外部、类内部以及子类(后续讲继承特性时会做详细介绍)中,都可以正常访问;
- private:私有属性的类变量和类函数,只能在本类内部使用,类的外部以及子类都无法使用。
但是,Python 并没有提供 public、private 这些修饰符。为了实现类的封装,Python 采取了下面的方法:
- 默认情况下,Python 类中的变量和方法都是公有(public)的,它们的名称前都没有下划线(_);
- 如果类中的变量和函数,
其名称以双下划线“__”开头,
则该变量(函数)为私有变量(私有函数),其属性等同于 private。
除此之外,还可以定义以单下划线“_”开头的类属性或者类方法(例如 _name、_display(self)),这种类属性和类方法通常被视为私有属性和私有方法,虽然它们也能通过类对象正常访问,但这是一种约定俗称的用法,初学者一定要遵守。
封装的本质:
事实上,Python 封装特性的实现纯属“投机取巧”,之所以类对象无法直接调用以双下划线开头命名的类属性和类方法,是因为其底层实现时,Python 偷偷改变了它们的名称。
例如:Student类中有__name、__age属性,和__dispaly()方法,则无法用“对象名.__name”等方式调用,实际上,它们分别被改名成_Student__name、_Student__age、_Student__display()了,所以使用如“对象名._Student__name”,是可以访问这个私有变量的。
可以使用装饰器@property等来访问内部属性,详见下面链接(不做要求)
廖雪峰
C语言中文网
C语言中文网
2.类的继承
继承机制经常用于创建和现有类功能类似的新类,又或是新类只需要在现有类基础上添加一些成员(属性和方法),但又不想直接将现有类代码复制给新类。也就是说,通过使用继承这种机制,可以轻松实现类的重复使用。
子类继承父类时,只需在定义子类时,将父类(可以是多个)放在子类之后的圆括号里即可。语法格式如下:
class 类名(父类1, 父类2, ...):
#类定义部分
注意,如果该类没有显式指定继承自哪个类,则默认继承 object 类(object 类是 Python 中所有类的父类,即要么是直接父类,要么是间接父类)。 另外,Python 的继承是多继承机制(和 C++ 一样),即一个子类可以同时拥有多个直接父类。
使用多继承经常需要面临的问题是,多个父类中包含同名的类方法。对于这种情况,Python 的处置措施是:根据子类继承多个父类时这些父类的前后次序决定,即排在前面父类中的类方法会覆盖排在后面父类中的同名类方法。
举个例子:
class People:
def __init__(self):
self.name = People
def say(self):
print("People类",self.name)
class Animal:
def __init__(self):
self.name = Animal
def say(self):
print("Animal类",self.name)
#People中的 name 属性和 say() 会遮蔽 Animal 类中的
class Person(People, Animal):
pass
zhangsan = Person()
zhangsan.name = "张三"
zhangsan.say()
程序运行结果为:
People类 张三
可以看到,当 Person 同时继承 People 类和 Animal 类时,People 类在前,因此如果 People 和 Animal 拥有同名的类方法,实际调用的是 People 类中的。
虽然 Python 在语法上支持多继承,但逼不得已,建议大家不要使用多继承。
父类方法的重写:
举个例子,鸟通常是有翅膀的,也会飞,因此我们可以像如下这样定义个和鸟相关的类:
class Bird:
#鸟有翅膀
def isWing(self):
print("鸟有翅膀")
#鸟会飞
def fly(self):
print("鸟会飞")
但是,对于鸵鸟来说,它虽然也属于鸟类,也有翅膀,但是它只会奔跑,并不会飞。针对这种情况,可以这样定义鸵鸟类:
class Ostrich(Bird):
# 重写Bird类的fly()方法
def fly(self):
print("鸵鸟不会飞")
可以看到,因为 Ostrich 继承自 Bird,因此 Ostrich 类拥有 Bird 类的 isWing() 和 fly() 方法。其中,isWing() 方法同样适合 Ostrich,但 fly() 明显不适合,因此我们在 Ostrich 类中对 fly() 方法进行重写。
在上面 2 段代码的基础上,添加如下代码并运行:
class Bird:
#鸟有翅膀
def isWing(self):
print("鸟有翅膀")
#鸟会飞
def fly(self):
print("鸟会飞")
class Ostrich(Bird):
# 重写Bird类的fly()方法
def fly(self):
print("鸵鸟不会飞")
# 创建Ostrich对象
ostrich = Ostrich()
#调用 Ostrich 类中重写的 fly() 类方法
ostrich.fly()
运行结果为:鸵鸟不会飞
如何调用父类中被重写的方法?
事实上,如果我们在子类中重写了从父类继承来的类方法,那么当在类的外部通过子类对象调用该方法时,Python 总是会执行子类中重写的方法。
这就产生一个新的问题,即如果想调用父类中被重写的这个方法,该怎么办呢?直接"父类名.父类方法(实例对象)"即可
例如:
class Bird:
#鸟有翅膀
def isWing(self):
print("鸟有翅膀")
#鸟会飞
def fly(self):
print("鸟会飞")
class Ostrich(Bird):
# 重写Bird类的fly()方法
def fly(self):
print("鸵鸟不会飞")
# 创建Ostrich对象
ostrich = Ostrich()
#调用 Bird 类中的 fly() 方法
Bird.fly(ostrich)
输出:鸟会飞
此程序中,需要大家注意的一点是,使用类名调用其类方法,Python 不会为该方法的第一个 self 参数自定绑定值,因此采用这种调用方法,需要手动为 self 参数赋值。通过类名调用实例方法的这种方式,又被称为未绑定方法。
super()详解
在Python类的方法(method)中,==要调用父类的某个方法,==在Python 2.2以前,通常的写法如下(也就是上面讨论的如何调用父类中被重写的方法):
class A:
def __init__(self):
print "enter A"
print "leave A"
class B(A):
def __init__(self):
print "enter B"
A.__init__(self)
print "leave B"
>>> b = B()
enter B
enter A
leave A
leave B
即,使用非绑定的类方法(用类名来引用的方法),并在参数列表中,引入待绑定的对象(self),从而达到调用父类的目的。
这样做的缺点是,当一个子类的父类发生变化时(如类B的父类由A变为C时),必须遍历整个类定义,把所有的通过非绑定的方法的类名全部替换过来,例如:
class B(C): # A --> C
def __init__(self):
print "enter B"
C.__init__(self) # A --> C
print "leave B"
于是,为了提高效率,引入了super函数,它在python2和python3版本中调用方式不一样,如下:
python2中:super(Class, obj).__ init __(self,…)
python3中:super().__ init __(self,…)
例如:
class A(object): # A must be new-style class
def __init__(self):
print "enter A"
print "leave A"
class B(A): # A --> C
def __init__(self):
print "enter B"
super(B, self).__init__() #python3版本为:super().__init__()
print "leave B"
b = B()
则输出为:
enter B
enter A
leave A
leave B
一个使用super()的具体实例如下:
class People:
def __init__(self,name):
self.name = name
def say(self):
print("我是人,名字为:",self.name)
class Animal:
def __init__(self,food):
self.food = food
def display(self):
print("我是动物,我吃",self.food)
class Person(People, Animal):
#自定义构造方法
def __init__(self,name,food):
#调用 People 类的构造方法
super().__init__(name)
#super(Person,self).__init__(name) #执行效果和上一行相同
#People.__init__(self,name)#使用未绑定方法调用 People 类构造方法
#调用其它父类的构造方法,需手动给 self 传值
Animal.__init__(self,food)
per = Person("zhangsan","熟食")
per.say()
per.display()
运行结果为:
我是人,名字为: zhangsan
我是动物,我吃 熟食
- 注意:涉及到多继承时,在子类函数中,调用第一个父类的方法既可以使用未绑定方法,也可以使用super(),而调用除第一个以外的其它父类构造方法的方式只能使用未绑定方法。
也就是说,super()实际上只和第一个父类绑定。
另外,super()只能在新式类中使用。
在早期版本的 Python 中,所有类并没有一个共同的祖先 object,如果定义一个类,但没有显式指定其祖先,那么就被解释为旧式类,在python2中,如果一个类没有指明祖先,则需要将其显示指明继承自object,来达到成为新式类的目的。而在python3中,不再保留旧式类的概念,即使一个类没有显示声明继承自object,它仍然是一个新式类,不过最好写上集成自object以方便统一。