17. 面向对象的特征
一、面向对象的三大特征
面向对象的三大特征指的是 封装、继承、多态。
封装(encapsulation,有时称为数据隐藏)是处理对象的一个重要概念。从形式上看,封装就是将数据和行为组合在一个包中,并对对象的使用者隐藏具体的实现方式。
继承(inheritance)的基本思想是,可以基于已有的类创建新类。继承以存在的类就是复用(继承)这些类的方法,而且可以增加一些新的属性和方法,使新类能够适应新的情况。
多态(polynirphic)就是一个类的多种形态。
二、封装性
2.1、什么是封装性
所谓的封装,就是把客观事物封装成抽象概念的类,并且类可以把自己的数据和方法只向可信的类或对象开放,向没必要开放的类或对象异常信息。封装性就是隐藏内部对象的复杂性,只对外公开简单的接口。便于外部调用,从而提高系统的可扩展性、可维护性。通俗的来说,把该隐藏的隐藏起来,该暴露的暴露出来。
当我们创建一个类时,我们可以通过“对象.属性”的方式,对对象的属性进行赋值。此时,赋值操作要受到属性的数据类型和存储范围的制约。除此之外,没有其它制约条件。但是,在实际问题中,我们往往需要给属性赋值加入额外的限制条件。这个条件就不能在属性声明时体现,只能通过方法进行限制条件的添加。同时,我们需要避免用户在使用“对象.属性”的方式对属性进行赋值,则需要将属性声明为私有的(private)。此时,针对于属性就体现了封装性。
使用封装后,确实增加了类的定义的复杂程度,但是它也确保了数据的安全性。我们隐藏了属性名,使调用者无法任意修改对象中的属性。并且我们增加了 getter() 和 setter() 方法,可以很好的控制属性是否为只读的。如果希望属性是只读的,则可以直接去掉 setter() 方法,如果希望属性不能被外部访问,则可以直接去掉 getter() 方法。而且在使用 setter() 方法设置属性时,可以增加数据的验证,确保数据的值是正确的。在使用 getter() 方法获取属性,使用 setter() 方法设置属性时,可以在读取属性和修改属性的同时做一些其它的处理。
2.2、封装性的体现
我们将类的属性(xxx)隐藏,同时,提供的公共的方法类获取(getter())和设置(setter(self))此属性的值。在 Python 中,我们可以为对象的属性使用双下划线开头 __xxx
,双下划线开头的属性,是对象的隐藏属性,隐藏属性值只能在类的内部访问,无法通过对象访问。
class Person:
def __init__(self,name,age):
self.__name = name
self.__age = age
def get_name(self):
return self.__name
def set_name(self,name):
self.__name = name
def get_age(self):
return self.__age
def set_age(self,age):
if age >= 0:
self.__age = age
def __show_info(self):
print(f"name: {self.__name}, age: {self.__age}")
如果我们直接访问类中的隐藏属性,会报以下错误:
p1 = Person("Sakura",10)
print(p1.__name)
AttributeError: 'Person' object has no attribute '__name'
此时,我们可以通过公共的 getter() 方法和 setter() 方法调用类的私有属性:
p1 = Person("Sakura",10)
print("name: ", p1.get_name() , ", age: ", p1.get_age())
p1.set_age(12)
print("name: ", p1.get_name() , ", age: ", p1.get_age())
p1.set_age(-10)
print("name: ", p1.get_name() , ", age: ", p1.get_age())
其实,隐藏属性是假隐藏,只不过是 Python 自动为属性改了一个名字。实际上,Python 将名字修改为 _类型__属性名
。
p1 = Person("Sakura",10)
p1._Person__age = 12
print(p1._Person__age)
p1._Person__show_info()
使用双下划线开头的属性,实际上依然可以在外部访问。在开发中,我们可以将一些私有属性以下划线开头(实际上是公开的属性),告诉开发人员没有特殊需要不要修改私有属性。
2.3、property装饰器
property 装饰器,用来将一个 getter() 方法,转换为对象的属性,添加了 property 装饰器以后,我们就可以向调用属性一样使用 getter() 方法。使用 property 装饰的方法必须和属性名是一样的。setter() 方法的装饰器为:@属性名.setter;
class Person:
def __init__(self,name):
self.__name = name
@property
def name(self):
print("get_name(self)方法执行了")
return self.__name
@name.setter
def name(self,name):
print("set_name(self,name)方法执行了")
self.__name = name
@name.deleter
def name(self):
print("del_name(self,name)方法执行了")
p = Person("Sakura")
print(p.name,end="\n\n")
p.name = "Mikoto"
print(p.name,end="\n\n")
del p.name
在一些较老版本的 Python 解释器中,我们可以通过如下的方法实现。
class Person:
def __init__(self,name):
self.__name = name
def get_name(self):
print("get_name(self)方法执行了")
return self.__name
def set_name(self,name):
print("set_name(self,name)方法执行了")
self.__name = name
def del_name(self):
print("del_name(self,name)方法执行了")
name = property(get_name, set_name, del_name)
p = Person("Sakura")
print(p.name,end="\n\n")
p.name = "Mikoto"
print(p.name,end="\n\n")
del p.name
三、继承性
3.1、什么是继承性
继承,其基本思想就是基于某个父类的扩展,制定出一个新的子类,子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。
通过继承可以直接让子类获取父类的方法和属性,避免编写重复性的代码,并且也符合 OCP 原则。所以,我们经常需要通过继承来对一个类进行扩展。
3.2、继承性的应用
使用继承后,我们可以减少代码的冗余,提高了代码的复用性,并且使用继承后,便于功能的拓展。继承的出现让类与类之间产生了 is-a
的关系。
在 Python 中,继承性的格式如下:
class A(父类):
pass
其中,A 类代表 子类(派生类、subclass),B 类代表 父类(基类、superclass)。一旦 子类 A 继承 父类 B 之后,子类 A 中就获取 父类 B 中声明的所有的结构:属性、方法(包括特殊方法);子类继承父类以后,还可以声明自己特有的属性和方法,实现功能的拓展;
class Animal:
def run(self):
print("动物在跑")
def sleep(self):
print("动物在睡觉")
class Dog(Animal):
def bark(self):
print("汪汪汪")
dog = Dog()
dog.run()
dog.sleep()
dog.bark()
print()
# isinstance()检查一个对象是否是一个类的实例
# 如果这个类是这个对象的父类,也会返回true
print(isinstance(dog,Dog))
print(isinstance(dog,Animal))
print()
# issubclass()检查一个类是或否是另一个类的子类
print(issubclass(Dog,Animal))
如果在创建类时,省略了父类,则默认父类为 object,它是所有类的父类,所有类都继承 object;
3.3、方法重写
如果在子类中有和父类同名的方法,则通过子类实例调用方法时,会调用子类的方法而不是父类的方法,这个特点我们成为 方法重写(覆盖,override)。
当我们调用一个对象的方法时,它会优先去当前对象中寻找是否具有该方法,如果有则直接调用,如果没有则取当前对象的父类中寻找,如果父类中有则直接调用父类中的方法,如果没有则取父类中的父类中寻找,以此类推,直到找到 object,如果依然没有找到则报错。
class Animal:
def run(self):
print("动物在跑")
def sleep(self):
print("动物在睡觉")
class Dog(Animal):
def run(self):
print("狗在跑")
def bark(self):
print("汪汪汪")
dog = Dog()
dog.run()
dog.sleep()
dog.bark()
3.4、super()方法的使用
父类中所有结构:属性 和 方法(包括特殊方法)都会被子类继承。如果我们希望直接调用父类中的结构,我们可以使用 super() 方法获取当前类的父类,并且通过 super() 调用父类方法时,不需要传递 self。
class Animal:
def __init__(self,name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
self.__name = name
class Dog(Animal):
def __init__(self,name,age):
# 直接调用父类中的__init__(self)来初始化父类中定义的属性
super().__init__(name)
self.__age = age
@property
def age(self):
return self.__age
@age.setter
def age(self,age):
self.__age = age
dog = Dog("旺财",3)
print("name: ", dog.name, ", age: ", dog.age)
3.5、多重继承
在 Python 中是支持多重继承的,也就是我们可以为一个类同时指定多个父类。我们可以在类名的 () 后面添加多个类,来实现多重继承。多重继承会使子类同时拥有多个父类,并且或获取所有父类中的方法。如果多个父类中有同名的方法,则会先在第一个父类中寻找,然后找第二个,然后找第三个,...,前面父类的方法会覆盖后面父类的方法。
class A:
def test(self):
print("AAA")
class B:
def test(self):
print("BBB")
class C(A,B):
pass
# 类名.__bases__这个属性可以获取当前类的所有父类
print(C.__bases__)
# 我们可以通过类.mro()查看属性的查找顺序
print(C.mro())
c = C()
c.test()
super() 在调用父类的时候,它需要计算出当前到底调用哪个父类,在 Python 中实现这个功能的算法叫 C3 算法;
在 Python 中子类可以同时继承多个父类的属性,这样可以最大限度地重用代码,但是使用多继承会导致扩展性变差,并且有可能会导致棱形问题(钻石问题)。棱形问题(钻石问题)指的是一个子类继承的多个父类汇集到一个非 object 类的身上。
class A:
def test(self):
print("from A")
class B(A):
def test(self):
print("from B")
class C(A):
def test(self):
print("from C")
class D(B):
def test(self):
print("from D")
class E(C):
def test(self):
print("from E")
class F(A):
def test(self):
print("from F")
class G(D,E,F):
pass
# 类名.__bases__这个属性可以获取当前类的所有父类
print(G.__bases__)
# 我们可以通过类.mro()查看属性的查找顺序
print(G.mro())
obj = G()
obj.test()
如果真的涉及到一个子类不可避免使用多个父类的属性,应该使用 Mixins 机制。Mixin 类表示某一功能,而不是某个物品,Python 对于Mixin 类的命名方式一般以 Mixin,able,ible 为后缀。我们可以将相关的功能放在一个 Mixin 类中,如果有多个不同功能,那可以写多个 Mixin 类,一个类可以继承多个 Mixin 类,但应该只继承一个表示其归属含义的父类,以确保遵循继承的 "is-a" 原则。Mixin 类不应该依赖于子类的实现,子类即便没有继承这个 Mixin 类,也可以照常工作,只是缺少了某个功能而已。
class Vehicle:
pass
class FlyerMixin:
def fly(self):
pass
class CivilAircraft(FlyerMixin,Vehicle):
pass
class Helicopter(FlyerMixin,Vehicle):
pass
class Car(Vehicle):
pass
四、多态性
多态性指的是一个事物的多种形态;同样的行为(函数),传入不同的对象,得到不同的状态;多态性指的是可以在不考虑对象具体类型的情况下而直接使用对象。
它的格式如下:
class A:
def __init__(self,name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
self.__name = name
class B:
def __init__(self,name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self,name):
self.__name = name
a = A("Sakura")
b = B("Mikoto")
# 对于say_hello()这个函数来说,只要对象中含有name属性,它就可以作为参数传递
# 这个函数并不会考虑对象的类型,只要有name属性即可
def say_hello(obj):
print("你好,%s" %obj.name)
say_hello(a)
say_hello(b)
import abc
# 使用模块abc统一所有子类的标准
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod
def speak(self):
pass
class Dog(Animal):
def speak(self):
print("汪汪汪!")
class Cat(Animal):
def speak(self):
print("喵喵喵!")
def make_noise(animal):
animal.speak()
#animal = Animal() # 不能实例化抽象类自己
dog = Dog()
cat = Cat()
make_noise(dog)
make_noise(cat)
看上去调用相同的方法,但实际上需要这个看这个对象是父类还是子类创建的对象,如果是父类创建的对象,一定调用父类中定义的方法,如果是子类创建的对象,那么就要看子类是否重写了父类的方法,如果子类要是重写了父类的方法,那么会调用子类的方法,如果子类没有重写父类方法,那么会调用父类的方法。