Python笔记六(面向对象的三大特性)

Posted xingyemdd

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python笔记六(面向对象的三大特性)相关的知识,希望对你有一定的参考价值。

类的三大特性:继承 多态 封装

一、继承

1、什么是继承

继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类,父类又可称为基类或超类,新建的类称为派生类或子类。

为什么会有继承? 解决代码的冗余问题。

python中类的继承分为:单继承和多继承

Parent类 —— 父类 基类 超类 
Son类     —— 子类 派生类 

class ParentClass1: #定义父类
    pass

class ParentClass2: #定义父类
    pass

class SubClass1(ParentClass1): #单继承,基类是ParentClass1,派生类是SubClass
    pass

class SubClass2(ParentClass1,ParentClass2): #python支持多继承,用逗号分隔开多个继承的类
    pass

查看继承

#__base__只查看从左到右继承的第一个父类,__bases__则是查看所有继承的父类
print(SubClass2.__base__)
#>>><class \'__main__.ParentClass1\'>
print(SubClass1.__bases__)
#>>>(<class \'__main__.ParentClass1\'>,)
print(SubClass2.__bases__)
#>>>(<class \'__main__.ParentClass1\'>, <class \'__main__.ParentClass2\'>)

#提示:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
ParentClass1.__bases__
#>>>(<class \'object\'>,)
ParentClass2.__bases__
#>>>(<class \'object\'>,)

2、继承与抽象(先抽象再继承)

抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度)

继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。

抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类

3、继承与重用性

在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时

我们不可能从头开始写一个类B,这就用到了类的继承的概念。

通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用

class Animal:
    \'\'\'
    人和狗都是动物,所以创造一个Animal基类
    \'\'\'
    def __init__(self, name, aggressivity, life_value):
        self.name = name  # 人和狗都有自己的昵称;
        self.aggressivity = aggressivity  # 人和狗都有自己的攻击力;
        self.life_value = life_value  # 人和狗都有自己的生命值;

    def eat(self):
        print(\'%s is eating\'%self.name)

class Dog(Animal):
    pass

class Person(Animal):
    pass

egg = Person(\'egon\',10,1000)
ha2 = Dog(\'二愣子\',50,1000)
egg.eat()
ha2.eat()

提示:用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大生了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.

4、派生

当然子类也可以添加自己新的属性或者在自己这里重新定义这些属性(不会影响到父类),需要注意的是,一旦重新定义了自己的属性且与父类重名,那么调用新增的属性时,就以自己为准了。

在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值。

通过继承建立了派生类与基类之间的关系,它是一种\'是\'的关系,比如白马是马,人是动物。

当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如教授是老师

class Animal:
    def __init__(self, name, hp, dps):
        self.name = name
        self.hp = hp
        self.dps = dps
    def eat(self):
        print(\'%s吃药回血了\'%self.name)

class Person(Animal):
    def __init__(self, name, hp, dps,sex):
        # 第一种写法:Animal.__init__(self, name, hp, dps)
        # 另一种方法如下,相当于super(Person,self).__init__(name,hp,dps),Person,self是python解释器自动加上的
        super().__init__(name,hp,dps)
        self.sex = sex           # 派生属性
    def attack(self,dog):
        dog.hp -= self.dps
        print(\'%s打了%s,%s掉了%s点血,剩余%s点血\' % (self.name, dog.name, dog.name, self.dps, dog.hp))

class Dog(Animal):
    def __init__(self,name,hp,dps,kind):
        super().__init__(name, hp, dps)
        self.kind = kind         # 派生属性
    def bite(self,person):
        person.hp -= self.dps
        print(\'%s打了%s,%s掉了%s点血,剩余%s点血\' % (self.name, person.name, person.name, self.dps, person.hp))
alex = Person(\'alex\',250,5,\'N/A\')
ha2 = Dog(\'哈士奇\',15000,200,\'藏獒\')
print(alex.__dict__)
print(ha2.__dict__)
ha2.eat()
alex.eat()
ha2.bite(alex)
alex.attack(ha2)
# >>>{\'name\': \'alex\', \'hp\': 250, \'dps\': 5, \'sex\': \'N/A\'}
# >>>{\'name\': \'哈士奇\', \'hp\': 15000, \'dps\': 200, \'kind\': \'藏獒\'}
# >>>哈士奇吃药回血了
# >>>alex吃药回血了
# >>>哈士奇打了alex,alex掉了200点血,剩余50点血

 5、钻石继承

1)继承顺序

python两种类:
经典类 py3已经灭绝了 在python2里还存在,在py2中只要程序员不主动继承object,这个类就是经典类 —— 深度优先
新式类 python3所有的类都是新式类,所有的新式类都继承自object —— 在多继承中遵循广度优先算法

class A(object):
    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(D,E):
    # def test(self):
    #     print(\'from F\')
    pass
f1=F()
f1.test()
print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性

#新式类继承顺序:F->D->B->E->C->A
#经典类继承顺序:F->D->B->A->E->C
#python3中统一都是新式类
#pyhon2中才分新式类与经典类

super和找父类这件事是两回事
在单继承中 super就是找父类
在多级承中 super的轨迹 是根据整个模型的起始点而展开的一个广度优先顺序 遵循mro规则

class A:
    def f(self):
        print(\'in A\')

class B(A):
    def f(self):
        print(\'in B\')
        super().f()

class C(A):
    pass
    def f(self):
        print(\'in C\')
        super().f()

class D(B,C):
    def f(self):
        print(\'in D\')
        super().f()

d = D()
d.f()
print(D.mro())
#执行结果
# >>>in D
# >>>in B
# >>>in C
# >>>in A
# [<class \'__main__.D\'>, <class \'__main__.B\'>, <class \'__main__.C\'>, <class \'__main__.A\'>, <class \'object\'>]

 

2)继承原理

python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如

>>> F.mro() #等同于F.__mro__
[<class \'__main__.F\'>, <class \'__main__.D\'>, <class \'__main__.B\'>, <class \'__main__.E\'>, <class \'__main__.C\'>, <class \'__main__.A\'>, <class \'object\'>]

为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配这个属性的类为止。
而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
1.子类会先于父类被检查
2.多个父类会根据它们在列表中的顺序被检查
3.如果对下一个类存在两个合法的选择,选择第一个父类

3)继承小结

继承的作用:

减少代码的重用、提高代码可读性、规范编程模式

名词:

抽象:抽象即抽取类似或者说比较像的部分。是一个从具题到抽象的过程。
继承:子类继承了父类的方法和属性
派生:子类在父类方法和属性的基础上产生了新的方法和属性

钻石继承:

新式类:广度优先
经典类:深度优先

二、多态

多态:多态指的是一类事物有多种形态。

python中不需要程序员自己实现多态,在其他强数据类型舒颜的面向对象中,我要传递一个参数必须指定这个参数的数据类型。但是,往往在这个地方需要传递的不止一种类型,建立一个父类,让所有要传递的数据类型都继承这个父类,在参数指定数据类型的时候指定父类名就可以了

#动物有多种形态:人,狗,猪
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
    @abc.abstractmethod
    def talk(self):
        pass

class People(Animal): #动物的形态之一:人
    def talk(self):
        print(\'say hello\')

class Dog(Animal): #动物的形态之二:狗
    def talk(self):
        print(\'say wangwang\')

class Pig(Animal): #动物的形态之三:猪
    def talk(self):
        print(\'say aoao\')

多态性:是指在不考虑实例类型的情况下使用实例

peo=People()
dog=Dog()
pig=Pig()

#peo、dog、pig都是动物,只要是动物肯定有talk方法
#于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()

#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
    obj.talk()

好处:

(1)增加了程序的灵活性
  以不变应万变,不论对象千变万化,使用者都是同一种形式去调用
(2)增加了程序额可扩展性
  通过继承animal类创建了一个新的类,使用者无需更改自己的代码,还是用func(animal)去调用

三、封装

1、介绍

【封装】

广义上的封装 :把变量和函数都放在类中。
狭义上的封装 :把一些变量 或者 方法 隐藏起来,不对外公开。

隐藏对象的属性和实现细节,仅对外提供公共访问方式。

【好处】 

1)将变化隔离; 

2)便于使用;

3)提高复用性; 

4)提高安全性;

【封装原则】

1)将不需要对外提供的内容都隐藏起来;

2)把属性都隐藏,提供公共方法对其访问。

2、私有变量和私有方法

在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)

1)私有变量

#其实这仅仅这是一种变形操作
#类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式:

class A:
    __N=0 #类的数据属性就应该是共享的,但是语法上是可以把类的数据属性设置成私有的如__N,会变形为_A__N
    def __init__(self):
        self.__X=10 #变形为self._A__X
    def __foo(self): #变形为_A__foo
        print(\'from A\')
    def bar(self):
        self.__foo() #只有在类内部才可以通过__foo的形式访问到.

#A._A__N是可以访问到的,即这种操作并不是严格意义上的限制外部访问,仅仅只是一种语法意义上的变形

这种自动变形的特点:

1.类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。

2.这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。

3.在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。

 这种变形需要注意的问题是:

1.这种机制也并没有真正意义上限制我们从外部直接访问属性,知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了,如a._A__N

2.变形的过程只在类的内部生效,在定义后的赋值操作,不会变形

面试题1:

#面试题
#观察下列程序执行的结果(类加载的过程中就会把属性和方法加载到内存)
class Person:
    __country = \'中国\'
    print(__country)
#>>>中国

2)私有方法

在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

#正常情况
>>> class A:
...     def fa(self):
...         print(\'from A\')
...     def test(self):
...         self.fa()
... 
>>> class B(A):
...     def fa(self):
...         print(\'from B\')
... 
>>> b=B()
>>> b.test()
from B
 

#把fa定义成私有的,即__fa
>>> class A:
...     def __fa(self): #在定义时就变形为_A__fa
...         print(\'from A\')
...     def test(self):
...         self.__fa() #只会与自己所在的类为准,即调用_A__fa
... 
>>> class B(A):
...     def __fa(self):
...         print(\'from B\')
... 
>>> b=B()
>>> b.test()
from A

静态属性 、 对象属性、 方法(动态属性) 前面加上双下划线都会变成私有的。
私有的特点:就是只能在类的内部调用,不能在类的外部使用。

class Person:
    __country = \'中国\'
    def __init__(self,name,pwd):
        self.name = name
        self.__pwd = pwd      # 私有的对象属性
    def login(self):
        print(self.__dict__)
        if self.name == \'alex\' and self.__pwd == \'alex3714\':
            print(\'登录成功\')

alex = Person(\'alex\',\'alex3714\')
alex.login()
print(alex.__dict__)
# >>>{\'name\': \'alex\', \'_Person__pwd\': \'alex3714\'}
# >>>登录成功
# >>>{\'name\': \'alex\', \'_Person__pwd\': \'alex3714\'}
#在类外部访问私有属性会报错
print(alex.__pwd)#>>>AttributeError: \'Person\' object has no attribute \'__pwd\'

#密码加密
class Person:
    def __init__(self):pass
    def __制造密码转换(self,inp):
        print(\'eating\')
    def 注册(self):
        inp = input(\'pwd>>>\')
        加密之后的密码 = self.__制造密码转换(inp)

面试题:

#面试题  写出下面两段代码输出结果
class Foo:
    def __init__(self):
        self.__func()
    def __func(self):
        print(\'in Foo\')

class Son(Foo):
    def __func(self):
        print(\'in son\')

s = Son()
#>>>in Foo

class Foo:
    def __init__(self):
        self.func()    # self._Foo__func
    def func(self):
        print(\'in Foo\')

class Son(Foo):
    def func(self):    # _Son__func
        print(\'in son\')

s = Son()
#>>>in son

3)封装与扩展性

封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。这就提供一个良好的合作基础——或者说,只要接口这个基础约定不变,则代码改变不足为虑。

#类的设计者
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
        return self.__width * self.__length


#使用者
>>> r1=Room(\'卧室\',\'egon\',20,20,20)
>>> r1.tell_area() #使用者调用接口tell_area


#类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
class Room:
    def __init__(self,name,owner,width,length,high):
        self.name=name
        self.owner=owner
        self.__width=width
        self.__length=length
        self.__high=high
    def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
        return self.__width * self.__length * self.__high


#对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
>>> r1.tell_area()

四、三个装饰器函数:classmethod   staticmethod   property

1、property

 

property:是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值。

作用:将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。

面向对象的封装有三种方式:

【public】这种其实就是不封装,是对外公开的。

【protected】这种封装方式对外不公开,但对朋友(friend)或者子类(形象的说法是“儿子”,但我不知道为什么大家 不说“女儿”,就像“parent”本来是“父母”的意思,但中文都是叫“父类”)公开。

【private】这种封装对谁都不公开。

例1:计算BIM指数

# BMI指数(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)
#
# 成人的BMI数值:
# 过轻:低于18.5
# 正常:18.5-23.9
# 过重:24-27
# 肥胖:28-32
# 非常肥胖, 高于32
#   体质指数(BMI)=体重(kg)÷身高^2(m)
#   EX:70kg÷(1.75×1.75)=22.86

class People:
    def __init__(self,name,weight,height):
        self.name=name
        self.weight=weight
        self.height=height
    @property
    def bmi(self):
        return self.weight / (self.height**2)

p1=People(\'egon\',75,1.85)
print(p1.bmi)

#执行结果
#>>>21.913805697589478
BMI指数

例2:计算圆的面积和周长

# 圆形类
from math import pi
class Circle:
    def __init__(self,r): # 圆的半径radius
        self.r = r

    @property
    def area(self):
        return self.r ** 2 * pi # 计算面积

    @property
    def perimeter(self):
        return self.r * 2 * pi # 计算周长

c = Circle(3)
print(c.area) # 可以向访问数据属性一样去访问area,会触发一个函数的执行,动态计算出一个值
print(c.perimeter) # 同上
# 结果
# 28.274333882308138
# 18.84955592153876

# print(c.area()) #加括号会报错 TypeError: \'float\' object is not callable
圆形类

python并没有在语法上把它们三个内建到自己的class机制中,在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)去设置和获取,在python中通过property方法可以实现。

一个静态属性property本质就是实现了get,set,delete三种方法:

#方法一classFoo:@propertydefAAA(self):print(\'get的时候运行我啊\')@AAA.setterdefAAA(self,value):print(\'set的时候运行我啊\')@AAA.deleterdefAAA(self):print(\'delete的时候运行我啊\')#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleterf1=Foo()f1.AAAf1.AAA=\'aaa\'delf1.AAA#方法二classFoo:defget_AAA(self):print(\'get的时候运行我啊\')defset_AAA(self,value):print(\'set的时候运行我啊\')defdelete_AAA(self):print(\'delete的时候运行我啊\')AAA=property(get_AAA,set_AAA,delete_AAA)#内置property三个参数与get,set,delete一一对应f1=Foo()f1.AAAf1.AAA=\'aaa\'delf1.AAA
get、set、delete

例3:商品打折

class Goods:

    def __init__(self):
        # 原价
        self.original_price = 100
        # 折扣
        self.discount = 0.8

    @property
    def price(self):
        # 实际价格 = 原价 * 折扣
        new_price = self.original_price * self.discount
        return new_price

    @price.setter
    def price(self, value):
        self.original_price = value

    @price.deleter
    def price(self):
        del self.original_price


obj = Goods()
obj.price         # 获取商品价格
obj.price = 200   # 修改商品原价
print(obj.price)
del obj.price     # 删除商品原价
class Goods

 

2、classmethod

# 如果某一个类中的方法 并没有用到这个类的实例中的具体属性
# 只是用到了类中的静态变量 就使用类方法
class Person:
    Country = \'中国人\'
    # @classmethod       #把func变成了一个类方法
    def func(cls):     # 不需要传self,cls是指向类的内存空间
        print(\'当前的角色的国籍是%s\'%cls.Country)
#使用classmethod后,之前的方法可以调用类中的方法
alex = Person()
alex.func()
#使用classmethod后,现在可以使用类直接调用方法
Person.func()

 

3、staticmethod