16 - 面向对象的三大特征

一.封装性

1.封装性的作用

  • 在实际开发中,对象的某些属性或者方法可能只希望在对象的内部别使用,而不希望在外部被访问到,这时就可以定义私有属性和私有方法。或者不希望外界随便改和查看,那么可以定义get、set方法,将一些限制条件加到方法中,外界传参如果不满足方法中的条件不赋值,满足条件才可修改

2.定义私有属性和私有方法

  • 在定义属性或方法时,在属性名或者方法名前增加两个下划线__,定义的就是私有属性或方法。

    class Person:
        def __init__(self,name,age):
            self.name = name
            self.age = age
            self.__money = 2000  #私有属性
    
        def __shopping(self, cost):  #私有方法
            self.__money -= cost  # __money 只能在对象内部使用
            print('还剩下%d元'%self.__money)
    
        def test(self):
            self.__shopping(200)  # __shopping 方法也只能在对象内部使用
    
    p = Person('张三',18)
    # print(p.__money)   这里会报错,不能直接访问对象内部的私有属性
    p.test()
    # p.__shopping()  这里会报错,__shopping 只能在对象内部使用,外部无法访问
    

3.定义方法来访问和修改私有变量

  • 使用定义getset方法这种方式来实现

    class Person:
        def __init__(self,name,age):
            self.name = name
            self.age = age
            self.__money = 2000  # __money 是私有变量,外部无法访问
    
        def get_money(self):  # 定义了get_money 方法,在这个方法里获取到 __money
            return self.__money 
    
        def set_money(self,money): # 定义了set_money 方法,在这个方法里,可以修改 __money
            if money < 0:
                print('不行!')
            else:
            	self.__money = money
    
    p = Person('王五',21)
    
    # 外部通过调用 get_money 和 set_money 这两个公开方法获取和修改私有变量
    print(p.get_money())
    p.set_money(8000)
    print(p.get_money())
    

4.直接访问私有变量

  • 使用方式:在私有属性名或方法名前添加 _类名

    class Person:
        def __init__(self,name,age):
            self.name = name
            self.age = age
            self.__money = 2000
    
        def __shopping(self, cost):
            self.__money -= cost
    
    
    p = Person('李四',20)
    print(p._Person__money)  # 使用对象名._类名__私有属性名 可以直接访问对象的私有属性
    p._Person__shopping(100)  # 使用对象名._类名__函数名 可以直接调用对象的私有方法
    print(p._Person__money)
    

在开发中,我们强烈不建议使用 对象名._类名__私有属性名 的方式来访问对象的私有属性!

二.property装饰器

  • 使用场景

    我们在类中定义get和set方法来访问和修改私有变量,使用时却每次都要对象名.get()对象名.set(),那么现在想要用访问和修改普通类的变量的方式对象.属性对象.属性 = 值一样的方式来对私有变量进行操作,但是实际上还是使用get和set方法,只是调用方式变了。就可以在类中定义get和set的上面加一个property装饰器

    class Person:
        def __init__(self, name):
            self.name = name
            self.__money = 2000
    
        @property
        def getMoney(self):
            return self.__money
    
        @property
        def setMoney(self,money):
            if self.__money <= 0:
                print('输入错误')
            else:
                self.__money = money
    
    p = Person('zjj')
    p.money = 1000
    print(p.money)
    

    最常用的定义方式:

    class Person:
        def __init__(self, name):
            self.name = name
            self.__money = 2000
    
        @property   #先定义get
        def money(self):
            return self.__money
    
        @money.setter   #这里就是set方法要依赖于get方法,所有要用money.setter来装饰
        def money(self,money):
            if self.__money <= 0:
                print('输入错误')
            else:
                self.__money = money
    
    p = Person('zjj')
    p.money = 1000
    print(p.money)
    

三.继承性

1.继承的作用

  • 继承可以使得子类具有父类的属性和方法或者重新定义、追加属性和方法等。
  • 在程序中,继承描述的是多个类之间的所属关系。(is a)

2.继承格式

  • class 类名(继承的父类)
class Animal:
    def __int__(self):
        pass
    """动物类"""
    def sleep(self):
        print('正在睡觉')

class Cat(Animal):  # 定义类时,在括号后面传入父类的类名,表示子类继承父类
    """Cat类继承自Animal类"""
    def __int__(self):
        pass

#Cat继承自Animal类,可以直接使用Animal类里的sleep方法
cat = Cat()
cat.sleep()

3.继承的注意点

1)super关键字及__init____new__的说明
  • super()表示父类对象,然后super().属性或方法得到父类的属性和方法

  • 为什么要有super:

    当父类中定义了__init__方法,而子类中也定义了__init__方法,如果不使用super虽然不会报错,但是就体现不了继承性了,因为创建子类对象时会自动执行子类中的__init__方法,而不会去执行父类中的__init__,那么父类的一些在init方法中定义的属性就无法加载到子类。

  • super使用说明

    • 如果子类中不定义__init__,则会自动去调用父类中的__init__,将父类init中声明的属性加载到子类的空间中

    • 如果子类中定义了__init__

      • 不使用super,则调用自己的init方法,而不去调用父类的init,那么父类init中声明的属性就得不到
      • 使用super,则会调用自己的init时会去调用父类中的init,把父类init方法中声明的属性加载到子类空间中

    但是注意:子类只要继承父类那么就可以使用父类中的类属性(可以使用没有定义到方法中的属性,如下方的变量n)和父类中的非私有方法。原理就是当子类空间中没找到时会去父类中找。

    class Person:
        n = 100
        def __init__(self):
            self._name = 'zjj'
        def getName(self):
            return self._name
        def eat(self):
            print('{}吃!'.format(self))
        def __str__(self):
            return '人'
    
    class Student(Person):
        def __init__(self):
            self._id = 2019
    
        def study(self):
            print('学生学习!')
        def __str__(self):
            return '学生'
    
    class Teacher(Person):
        pass
    
    
    p = Person()
    s = Student()
    print(s.n) #100
    p.eat()  #人吃
    s.eat()  #学生吃 
    #s.getName()  #会报错,因为Student类中已经自己定义了__init__方法,那么在创建对象的时候就自动执行了此方法,把要加载的属性加载到内存中而此时此方法中没有super关键字来调用父类中的__init__方法,所以父类中的属性不会加载到子类空间中去,那么Student类中就没有name属性,而getName需要使用name属性所以会报错。
    t = Teacher()
    print(t.getName())  #如果Teacher类中没有写init方法,那么先看看自己类中是否有此方法,没有则在父类中找,没有则继续向父类的父类找,最后没有就报错    
    

    • 使用super时说明:
    class Person:
        def __init__(self,name):
            self._name = name
        def getName(self):
            return self._name
        def eat(self):
            print('{}吃!'.format(self))
    
    class Student(Person):
        def __init__(self,numid,name):
            self._id = numid
            super().__init__(name)  #父类中init方法中有参数就要传参
            #或者使用super(Student,self).__init__(name)  这里使用的是带参数的super多了一部判断self是否是Student类型的过程
    s = Student(2019,'zjj')
    print(s.getName())  #zjj
    

还可以使用父类名.__init__(self)来调用父类的init方法,但是注意这样只会调父类名只一个init()方法,而super()会调用所有的父类的init方法,可以解决多继承问题,会一次性的执行所有的父类的对应方法

2)子类重写父类的方法
  • 子类中和父类中有同名的方法,那么调用此方法,优先执行子类中的方法。

  • 父类提供的方法不能满足子类的需求,就可以在子类中定义一个同名的方法,实现更多的功能,就叫重写

3)多继承
  • python中一个类可以继承多个类

  • 格式:

    class 子类(父类1,父类2...):

  • 多继承的搜索顺序

    新式类:广度优先。以object为基类的类,推荐使用

    经典类:从左至右,深度优先。不以object为基类的类,不推荐使用

    在 Python3.x 以后定义类时,如果没有指定父类,这个类会默认继承自 object,所以,python3.x版本定义的类都是新式类。在Python2.x中定义类时,如果没有指定父类,则不会继承自object.

  • __mro__:Python中针对类提供了一个内置属性__mro__可以用来查看方法的搜索顺序。主要用于在多继承时判断方法属性的调用顺序

    class P1:  #默认以object为基类
        def foo(self):
            print('P1------foo')
        def bar(self):
            print('p1------bar')
    
    class P2:
        def foo(self):
            print('P2------foo')
    
    class C1(P1,P2):
        pass
    
    class C2(P1,P2):
        def bar(self):
            print('C2------bar')
    
    class D(C1,C2):
        pass
    
    d = D()
    d.foo()  #P1------foo
    d.bar()  #C2------bar
    print(D.__mro__)  #(<class '__main__.D'>, <class '__main__.C1'>, <class '__main__.C2'>, <class '__main__.P1'>, <class '__main__.P2'>, <class 'object'>)
    

    图示:

    image-20211011173055576

四.多态

  • python没有严格意义上的多态,因为在java中多态是:子类的对象赋给父类的引用----也就是说现在两个类有继承关系,如果一个方法的参数为一个类类型,那么可以传该类类型的变量进去,也可以传该类的子类的类型变量进去。只能是该类或该类的子类。但是python中,一个参数类型假设我要传一个类类型变量,那么只要是类类型都可以往里传,不需要一定满足是它的子类才能传。

    isinstance(obj,类名) --> 判断obj是不是类的对象或者判断obj是不是该类的子类对象

  • 举例:

    class Person:
        def __init__(self,name):
            self.name = name
    
        def feed_pet(self,pet):  #这个pet可以接收Pet类的子类,还可以接收其他类的变量
            #isinstance(obj,类名) --> 判断obj是不是类的对象或者判断obj是不是该类的子类对象
            if isinstance(pet,Pet):
                print('{}养的是宠物是{},昵称叫{}'.format(self.name,pet.kind,pet.name))
            else:
                print('不能养野兽')
    
    class Pet:
        def __init__(self,name):
            self.name = name
    
    class Cat(Pet):
        kind = '修猫'
        def __init__(self,name):
            super(Cat, self).__init__(name)
    
    class Dog(Pet):
        kind = '修狗'
        def __init__(self,name):
            super(Dog, self).__init__(name)
    
    class Tiger:
        kind = '修脑虎'
        def __init__(self,name):
            self.name = name
    
    c = Cat('富贵')
    d = Dog('大黄')
    t = Tiger('吼吼')
    p = Person('zjj')
    
    p.feed_pet(c)
    p.feed_pet(d)
    p.feed_pet(t)
    
posted @ 2021-10-14 19:21  深藏Bluee  阅读(148)  评论(0)    收藏  举报