第9章 面向对象之三大特性
9.1 封装
将变量和函数写入类中的操作即为封装,即类中封装了属性和方法。
通过封装,我们可以将一些细节隐藏起来(私有),只暴露出必要的接口供调用者使用。
9.1.1 私有化
有时为了限制属性和方法只能在类内访问,外部无法访问;或父类中某些属性和方法不希望被子类继承。可以将其私有化。
1)单下划线:非公开API
大多数 Python 代码都遵循这样一个约定:有一个前缀下划线的变量或方法应被视为非公开的 API,例如 _var1。这种约定不具有强制力。
2)双下划线:名称改写
有两个前缀下划线,并至多一个后缀下划线的标识符,例如 __x,会被改写为 _类名__x。只有在类内部可以通过 __x 访问,其他地方无法访问或只能通过 _类名__x 访问。
9.1.2 私有属性
通过双下划线定义私有属性。
class Person:
def __init__(self, name):
self.__name = name
def get_name(self):
return self.__name
p = Person("张三")
print(p.get_name()) # 张三
print(p._Person__name) # 张三
print(p.__name) # 报错
9.1.3 私有方法
通过双下划线定义私有方法。
class Person:
# 定义私有方法
def __private_method(self):
print("private method")
# 定义实例方法,调用私有方法
def do_something(self):
self.__private_method()
p = Person()
p.do_something() # private method
p._Person__private_method() # private method
p.__private_method() # 报错
9.1.4 property
1)方法转换为属性
可通过 @property 装饰器将一个方法转换为属性来调用。转换后可直接使用 .方法名 来使用,而无需使用 .方法名()。
class Person:
def __init__(self, name):
self.name = name
@property
def eat(self):
print(f"{self.name} is eating...")
p = Person("张三")
p.eat # 张三 is eating...
2)只读属性
将方法名设置为去掉双下划线的私有属性名,方法中返回私有属性。
class Person:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
p = Person("张三")
print(p.name) # 张三
p.name = "李四" # 报错
3)读写属性
将方法名设置为去掉双下划线的私有属性名,使用 属性名.setter 装饰。
class Person:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
p = Person("张三")
print(p.name) # 张三
p.name = "李四"
print(p.name) # 李四
也可以在写方法中设置一些拦截条件来规范私有属性的写入。
class Person:
def __init__(self, name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
if name == "李四":
print("不许叫李四")
else:
self.__name = name
p = Person("张三")
print(p.name) # 张三
p.name = "李四" # 提示 "不许叫李四"
print(p.name) # 张三
p.name = "王五"
print(p.name) # 王五
4)注意
@property 装饰的方法不要和变量重名,否则可能导致无限递归。
class Person:
@property
def name(self):
return self.name
p = Person()
p.name # 报错:RecursionError: maximum recursion depth exceeded
9.2 继承
子类(派生类)继承父类(基类)中的属性和方法,实现代码重用。子类可以新增自己特有的方法,也可以重写父类的方法。
子类不能继承父类的私有属性和私有方法,因为存在名称改写,但是可以通过改写后的名称直接访问父类的私有成员,不过,这种做法违背了封装原则,不建议使用。
9.2.1 单继承
1)语法
class 类名(父类):
类体
在类名后括号内指定要继承的父类。
2)案例
class Person:
"""人的类"""
home = "earth" # 定义类属性
def __init__(self, name):
self.name = name # 定义实例属性
def eat(self):
print("eating...")
class YellowRace(Person):
"""黄种人"""
color = "yellow" # 定义类属性
class WhiteRace(Person):
"""白种人"""
color = "white" # 定义类属性
class BlackRace(Person):
"""黑种人"""
color = "black" # 定义类属性
y1 = YellowRace("张三")
print(y1.home) # earth
print(y1.color) # yellow
print(y1.name) # 张三
y1.eat() # eating...
w1 = WhiteRace("李四")
print(w1.home) # earth
print(w1.color) # white
print(w1.name) # 李四
w1.eat() # eating...
b1 = BlackRace("王五")
print(b1.home) # earth
print(b1.color) # black
print(b1.name) # 王五
b1.eat() # eating...
9.2.2 多继承
调用方法时先在子类中查找,若不存在则从左到右依次查找父类中是否包含方法。
1)语法
class 类名(父类1, 父类2, ...):
类体
2)案例
class Person:
"""人的类"""
home = "earth"
def __init__(self, name):
self.name = name
def eat(self):
print("eating...")
class YellowRace(Person):
"""黄种人"""
color = "yellow"
def run(self):
print("runing...")
class Student(Person):
"""学生"""
def __init__(self, name, grade):
self.name = name
self.grade = grade
def study(self):
print("studying...")
class ChineseStudent(Student, YellowRace): # 继承了Student和YellowRace
"""中国学生"""
country = "中国"
y1 = ChineseStudent("张三", "三年级")
print(y1.home, y1.color, y1.country, y1.name, y1.grade)
y1.eat()
y1.run()
y1.study()
9.2.3 复用父类方法
子类可以在类中使用 super().方法名() 或 父类名.方法名() 来调用父类的方法。
1)super().方法名()
class Person:
"""人的类"""
home = "earth"
def __init__(self, name):
self.name = name
def eat(self):
print("eating...")
class YellowRace(Person):
"""黄种人"""
color = "yellow"
def run(self):
print("runing...")
class Student(Person):
"""学生"""
def __init__(self, name, grade):
self.name = name
self.grade = grade
def study(self):
print("先吃再学")
super().eat() # 子类中调用父类的方法
print("studying...")
class ChineseStudent(Student, YellowRace):
"""中国学生"""
country = "中国"
y1 = ChineseStudent("张三", "三年级")
print(y1.home, y1.color, y1.country, y1.name, y1.grade)
y1.study()
2)父类名.方法名()
class Student(Person):
"""学生"""
def __init__(self, name, grade):
self.name = name
self.grade = grade
def study(self):
print("先吃再学")
Person.eat(self) # 子类中调用父类的方法
print("studying...")
9.2.4 方法解析顺序
方法解析顺序(MRO — Method Resolution Order)。可使用 类名.__mro__ 访问类的继承链来查看方法解析顺序。
class Person:
"""人的类"""
home = "earth"
def __init__(self, name):
self.name = name
def eat(self):
print("eating...")
class YellowRace(Person):
"""黄种人"""
color = "yellow"
def run(self):
print("runing...")
class Student(Person):
"""学生"""
def __init__(self, name, grade):
self.name = name
self.grade = grade
def study(self):
print("先吃再学")
Person.eat(self)
print("studying...")
class ChineseStudent(Student, YellowRace):
"""中国学生"""
country = "中国"
y1 = ChineseStudent("张三", "三年级")
print(ChineseStudent.__mro__)
# (<class 'ChineseStudent'>, <class 'Student'>, <class 'YellowRace'>, <class 'Person'>, <class 'object'>)
9.2.5 方法重写
在子类中定义与父类方法重名的方法,调用时会调用子类中重写的方法。
class Person:
home = "earth"
def __init__(self, name):
self.name = name
def eat(self):
print("eating...")
class Chinese(Person):
color = "yellow"
# 重写父类方法
def eat(self):
print("用筷子吃")
y1 = Chinese("张三")
y1.eat() # 用筷子吃
注意: 子类重写 __init__() 并调用时,不会执行父类的 __init__() 方法。如有必要,需在子类 __init__() 中使用 super().__init__() 来调用父类的 __init__() 方法。
class Person:
def __init__(self, name):
self.name = name
class Chinese(Person):
def __init__(self, name, area):
super().__init__(name) # 调用父类的__init__()
self.area = area
y1 = Chinese("张三", "北京")
print(y1.name, y1.area)
9.3 多态
同一事物在不同场景下呈现不同状态。
class Animal:
def go(self):
pass
class Dog(Animal):
def go(self):
print("跑")
class Fish(Animal):
def go(self):
print("游")
class Bird(Animal):
def go(self):
print("飞")
def go(animal):
animal.go() # 将不同的实例传入,执行不同的方法
dog = Dog()
fish = Fish()
bird = Bird()
go(dog) # 跑
go(fish) # 游
go(bird) # 飞