Python 是面向对象的语言,因此支持面向对象编程的三大特性。
面向对象,具有三个特性:
- 封装
- 继承
- 多态
什么是封装:
是对具体对象的一种抽象:把部分属性限制在某一区域内,其他程序无法调用(即:私有化)
为什么要进行封装?有啥用?
1、保护隐私(把不想别人知道的东西封装起来)
2、隔离复杂度(比如:电脑、手机,我们看见的就是一个商品,其实里面有很多电器元件等,对用户来说,我们不需要清楚里面都有哪些元件,电脑把那些电器元件封装在黑匣子里,提供给用户的只是个按钮接口,通过键盘就能实现对电脑的操作。)
封装的两个层面:
封装其实分为两个层面,但无论哪种层面的封装,都要对外界提供好访问你内部隐藏内容的接口(接口可以理解为入口,有了这个入口,使用者无需且不能够直接访问到内部隐藏的细节,只能走接口,并且我们可以在接口的实现上附加更多的处理逻辑,从而严格控制使用者的访问)
第一个层面的封装(什么都不用做):
创建类和对象会分别创建二者的名称空间,我们只能用类名.或者obj.的方式去访问里面的名字,这本身就是一种封装。
第二个层面的封装:
类中把某些属性和方法隐藏起来(或者说定义成私有的),只在类的内部使用、外部无法访问,或者留下少量接口(函数)供外部访问。
实例:
class person:
# 私有属性
def __init__(self, name, age):
self.__name = name
self.age = age
# 私有方法
def __getName(self):
print(f'{self.name},{self.age}')
# 实例方法
def printInfo(self):
print(f'我是{self.__name},今年{self.age}岁。')
# 创建对象,实例化对象
a = person('崔东山', '18')
a.printInfo()
# a.__getName() # 私有方法,无法直接调用
# a._person__getName() # 报错:属性错误:AttributeError: 'person' object has no attribute 'name'
什么是继承:
在生活中,常见的有子承父业..
那么在代码中:
一个新类继承自一个设计好的类,就具备了已有类的特征。
已有的类,称之为父类或基类
新的类,称之为子类或派生类
继承的分类:
- 单继承
- 多继承
单继承:
子类只继承一个父类。【深度优先机制】
当对象调用方法时,查找顺序从自身类找,若未找到则去父类找;
父类无,再去父类的父类找,直到object类,若还无,则报错。
代码实例:
class grandFather:
def __init__(self):
print('父类的父类')
def sleep(self):
print('父类的父类,睡觉')
# 继承父类
class father(grandFather):
def eat(self):
print('父类:吃')
def drink(self):
print('父类:喝')
# 继承父类
class son(father):
def study(self):
print('我在学习Python')
a = son()
a.study()
a.eat()
a.sleep()
多继承:子类继承了多个父类,并且具有它们的特征。
情景一:左优原则
class func1:
def run(self):
print('父类1-run')
class func2:
def run(self):
print('父类2-run')
# 继承多个父类:拥有相同方法时,左边优先执行
class son(func1, func2):
pass
b = son()
b.run()
情景二:一条道走到黑,先执行左边,去找父类,父类没有去父类的父类。
(左边一条路走到黑)
class firstLevel:
def sleep(self):
print('1.0:sleep')
class secondLevel(firstLevel):
def run(self):
print('2.0,run')
class thirdStage:
def sleep(self):
print('2.1,sleep')
class fourthStage(secondLevel, thirdStage):
pass
c = fourthStage()
c.sleep() # 最后找到的是class firstLevel()
情景三:左优 + 根最后执行
class grandFather:
def play(self):
print('嗨起来')
class father(grandFather):
def rap(self):
print('一起跳起来')
class father1(grandFather):
def dance(self):
print('一起来跳舞吧')
# 如果是同根的话,根是最后执行
class son(father, father1):
pass
d = son()
d.dance()
print(son.__mro__) # 通过mro方法可以查看程序执行情况或继承顺序
什么是重写?
当子类与父类拥有同名称的方法时,子类对象调用该方法 优先执行自身 的方法。
那么实际上就是子类的方法 覆盖 父类的方法,也称为 重写。
实际的开发中,遵循开放封闭原则。并不会完全的重写父类的方法,而是希望同时实现父类的功能。
代码实例:
class A:
def __init__(self):
print('AAA')
def demo(self):
print('aaa')
class B(A):
def __init__(self):
print('BBBbbb')
def demo(self): # 与父类拥有相同名称的方法
print('bbb')
# 需要重写谁,就super().实例方法(),具备父类的方法
super().demo()
super().__init__()
e = B()
e.demo()
什么是多态:
一类事物有多种形态,一个抽象类有多个子类(因而多态的概念依赖于继承);
不同的子类对象调用相同的方法,产生不同的执行结果(增加代码的灵活度);
实现多态的步骤:
定义一个父类,实现某个方法;
定义多个子类,在子类中重写父类的方法,每个子类方法实现不同的功能;
假设定义一个函数,需要一个base类型的对象的参数,那么调用函数的时候,传入base类不同的子类对象,那么这个函数就会执行不同的功能。
步骤解析:
1、定义一个父类(Base),实现某个方法(func)
2、定义多个子类,在子类中重写父类的方法(func),每个子类 func() 方法实现不同的功能;
3、假设定义一个函数,需要一个 Base类型的对象的参数,那么调用函数时,传入Base 类中不同子类的对象,那么这个函数就会执行不同的功能,这即是多态的体现。
代码实例:
class animal(object):
def func(self):
print('动物类')
class cat(animal):
def func(self):
print('喵~')
class dog(animal):
def func(self):
print('汪~')
class hero(animal):
def func(self):
print('英雄类,而非动物类')
# 定义一个方法/函数
def demo(test): # 传入形参接收
test.func() # 对象名.方法名
# 传入不同的对象,产生不同的结果
# 调用更加灵活,更容易写出通用的代码
demo(dog()) # 传入的对象(实参)
demo(cat()) # 传入的对象
demo(hero()) # 传入的对象
注意点:Python(弱类型语言)中函数的参数是没有类型限制的,所以多态在python中的体现并不是很严谨。多态的概念是应用于Java和C#这一类强类型语言中,而Python崇尚“鸭子类型”。
鸭子类型(duck typing):
编程语言具有类型概念,例如 Python 中有数字类型、字符串类型、布尔类型,或者更加复杂的结构,例如元组 tuple 、列表 list 、集合 set 和字典类型 dict 等等。
根据如何将类型解析并赋值给各种构造(例如变量,表达式,函数,函数参数等),编程语言可以归类为“鸭子类型”,“结构化类型”或“标称类型”。
本质上,分类决定了对象如何被解析并推断为具体的类型。
鸭子类型( duck typing )语言使用鸭子测试来评估:
对象是否可以被解析为特定的类型。Python就是其中一种。
这个概念的名字来源于由詹姆斯·惠特科姆·莱利提出的鸭子测试。“鸭子测试”可以这样表述:
“如果看起来像鸭子,叫起来像鸭子,那么它一定是鸭子。”
概念: 鸭子类型在程序设计中是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。
在鸭子类型中,关注点在于对象的行为,能做什么;而不是关注对象所属的类型。
例如,在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的"走"和"叫"方法。
如果这些需要被调用的方法不存在,那么将引发一个运行时错误。任何拥有这样的正确的"走"和"叫"方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。
鸭子类型通常得益于"不"测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。
在常规类型中,我们能否在一个特定场景中使用某个对象取决于这个对象的类型,而在鸭子类型中,则取决于这个对象是否具有某种属性或者方法——即只要具备特定的属性或方法,能通过鸭子测试,就可以使用。
代码实例1:
# 定义一个计算函数,接收3个入参
def calculate(a, b, c):
return (a + b) * c
# 分别计算3种情况的结果
result1 = calculate(1, 2, 3)
result2 = calculate([1, 2, 3], [4, 5, 6], 2)
result3 = calculate("打工人", "打工魂", 3)
# 打印3种结果
print(result1, result2, result3, sep='\n')
输出结果:
9
[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]
打工人打工魂打工人打工魂打工人打工魂
实例解析:每次调用 calculate() 都使用的是不同的对象(数字、列表和字符串),并且它们在继承关系中没有联系。只要输入对象支持 + 和 * 方法,操作就能成功。
代码实例2:
# 鸭子类
class Duck:
def quack(self):
print("这鸭子正在嘎嘎叫")
def feathers(self):
print("这鸭子拥有白色和灰色的羽毛")
# 人类
class Person:
def quack(self):
print("这人正在模仿鸭子")
def feathers(self):
print("这人在地上拿起1根羽毛然后给其他人看")
# 函数/接口
def in_the_forest(duck):
duck.quack()
duck.feathers()
if __name__ == '__main__':
donald = Duck() # 创建一个Duck类的实例
john = Person() # 创建一个Person类的实例
in_the_forest(donald) # 调用函数,传入Duck的实例
in_the_forest(john) # 调用函数,传入Person的实例
输出结果:
这鸭子正在嘎嘎叫
这鸭子拥有白色和灰色的羽毛
这人正在模仿鸭子
这人在地上拿起1根羽毛然后给其他人看
小结:
鸭子类型是一个与动态类型( dynamic typing )相关的概念,其中关注的是它定义的方法,而不太关注对象的类型或所属类。当使用鸭子类型时,无需检查其类型。相反,需要检查给定方法或属性珍惜是否存在。
鸭子类型语言为程序员提供了最大的灵活性,程序员只需写最少量的代码。但是这些语言可能并不安全,可能会产生运行时错误,使用时需要格外注意。
class Duck:
def test(self):
print('小鸭子,嘎嘎嘎...')
class Bird:
def test(self):
print('鸟儿叽叽喳喳')
class Chicken:
def test(self):
print('小鸡咯咯哒')
def inTheForest(a):
a.test()
duck = Duck()
bird = Bird()
chicken = Chicken()
for i in [duck, bird, chicken]:
inTheForest(i)