面向对象的三大特性
一、封装
1.1 隐藏属性
封装是面向对象三大核心中最核心的一个特性。
封装就是将类的某些属性隐藏起来,然后通过接口对外开放,但是在外部不能直接进行查找属性。
在类内部的变量属性或者函数属性名前加上两个下划线。就实现了对该属性的隐藏,此时,通过外界就查不到该属性。
# 属性的隐藏
class Student:
__school = "清华"
def __init__(self,name,age):
self.__name = name
self.__age = age
def tell_info(self):
print("学校:%s 姓名:%s 年龄:%s"%(self.__school,self.__name,self.__age))
stu_1 = Student("tom",18)
print(stu_1.__school) # 这种方法是查看不到该学校属性的。
print(stu_1._Student__school) # 采用_类__属性的方法就可以查看了。
stu_1.__handsome = "帅" # 定义完毕类之后在添加__属性,该属性是不会隐藏起来的。
通过上述的查看方式,就大概看出来,这个隐藏其实是一种变形,这个阶段主要发生在定义阶段,当执行类内部的代码时候,遇到__属性
的方式就会将其变为_类__属性名
的形式。这样我们就可以在外界通过这种形式调用查看该属性。
由于是在定义阶段发生的变形,因此,在外界我们在重新指定对象的属性的时候,是不会在发生变形的。
这种隐藏的方式并不是完全的没办法访问,但是如果你还想调用查看的话,请一开始就不要搞这种隐藏,多此一举。这种隐藏其内部是可以使用的,因为其内部在定义的时候都发生了转变,所以内部使用的方式也变成了_类__属性
的方式。
那为什么要设置隐藏呢?
- 隐藏数据是为了限制外界对属性的直接修改。
- 通过接口操作可以在接口上附加额外的逻辑处理来控制隐藏数据。
1.2 property装饰器
property装饰器的作用就是把类内部的函数属性的调用方式转为数据属性的调用。
class People:
def __init__(self,name):
self.__name = name
@property
def get_name(self):
return self.__name
p1 = Prople("tom")
print(p1.get_name) # 此时get_name函数属性的调用就可以不用括号,而是采用数据属性的调用方式
当然还有一个更神奇的操作。
class People:
def __init__(self,name):
self.__name = name
def get_name(self):
return self.__name
def set_name(self,change_name):
self.__name = change_name
def del_name(self):
del self.__name
name = property(get_name,set_name,del_name)
p1 = People("tom")
# 调用了get_name的方法
print(p1.name)
# set_name的方法
p1.name = "Jack"
# del_name的方法
del p1.name
上述是老版的方法,下方是新版。
class People:
def __init__(self,name):
self.__name = name
@property
def name(self):
return self.__name
@name.setter
def name(self,change_name):
self.__name = change_name
@name.deleter
def name(self):
del self.__name
注意:这几个方法都是同一个名字,而且@property位于最上方。
二、继承
2.1 单继承
继承:主要是阐述子类(派生类)是父类(基类,超类)。就是一种什么是什么的关系。比如说猪是哺乳动物,那么哺乳动物的一些特征猪肯定有,我们在创建完哺乳动物类之后,在创建猪类,就可以继承哺乳动物类的属性,然后在创建猪类。这样就减少了代码的冗余。使结构更加清晰。
在Python2 中还有经典类和新式类。
- 经典类。没有继承了object类的子类、子子类。。。。。。。
- 新式类。继承了object类的子类、子子类。。。。。。。
但是Python3 中 已经默认全部都是新式类了。
单继承的话,当我们能使用子类的某些属性的时候,查找顺序就是子类--》父类--》父类的父类。。。
class Animal(Object): # 这个(Object)加不加都是默认为新式类。
def __init__(self,name):
self.name =name
def run(self):
print("动物开始跑")
def eat(self):
self.run()
print("跑完步,动物开始吃饭")
class Pig(Animal):
def__init__(self,name,weight): # 当子类有与父类同名的属性就会重写该属性。
Animal.__init__(self,name) # 这种可以实现对父类属性的调用。
self.weight =weight
def sleep(self):
print("猪开始睡觉了。")
def run(self):
print("猪跑不动,所以只能散散步。")
pig_1 = Pig("peiqi",500)
pig_1.eat() # 你猜会运行什么?
# 猪跑不动,所以只能散散步。
# 跑完步,动物开始吃饭
当佩奇执行eat属性的时候,本身并没有含有这个属性,就去Animal类中找到了eat方法。然后执行到了self.run()。那么此时的self其实已经是佩奇对象的本身了,因此,在这一步调用的实际是peiqi.run()。佩奇本身是含有这个属性的,因此执行的就是上述结果第一句。
父类也可以设置__属性
的方式进行隐藏。
2.2 多继承
python是支持多继承的 ,其查找属性的顺讯主要就是新式类和经典类在菱形问题上的不同。
如果ABC都有某一个方法,那么D在使用的时候会使用哪一个?根据mro列表,就可以得到查找属性的顺序。mro列表有以下几个原则
- 子类优先于父类。
- 多个父类按照继承时候的顺序。
- 一旦找到就使用。
如果在继承类中不是菱形问题,那么新式类和经典类的查找顺序是一样的。
2.3 mixins机制
存在即合理。多继承虽然可以减少代码的冗余,但是它也容易影响可读性和维护性。因为继承本质是一种是的关系,当子类不是父类但是仍然继承了其他父类的属性,那么就不便与人类理解。为此,Python推出了一种编程范式:mixins机制。这个就是多继承正确打开的方式。
一般情况下,某一个类赋予其子类一种函数属性,相当于对子类功能的一种拓展,这个时候这个类就以“Mixin”结尾,一般情况下,子类都是只继承一个父类(从属关系),其他拓展功能位于也是父类,但是位于父类之前,便于阅读。
class Vehicle:
pass
class FlyableMixin: # 只负责拓展飞的功能。
def fly(self):
pass
class CivilAircraft(FlyableMixin,Vehicle): # 民航飞机,飞的功能位于Vehicle之前。
pass
class Helicopter(FlyableMixin,Vehicle): # 直升飞机
pass
这就是MIXIN机制,它只是一种规范,当然也可以不按照这种规范写,但是这种写更便于阅读。
三、多态
多态就是同一种事物有多种不同的表现形式,比如说同样是狗类,但也分为二哈、中华田园犬、泰迪等等不同的类型。但是既然同属于狗类,那么这些子类一定有共同的特征。这样我们在使用这些特征的时候,不必考虑是什么子类,他肯定有这一个属性。
我们还可以利用一个函数当做统一的接口去使用这些属性。
多态性的本质就是在不同的类中定义有相同名字的属性,这样我们就可以用一个统一的接口去使用对象。
3.1 鸭子类型
如果看着像鸭子,走路、叫声都像鸭子,那么他就是个鸭子。
这时不需要考虑继承的关系,只要类之间有相同的属性,那么就可以不用考虑类型使用对象,这就是Python推崇的“鸭子类型”。