封装、继承、多态

发布时间 2024-01-11 16:10:18作者: Formerly0^0

封装、继承、多态、反射

1.封装

  • 封装就是将属性隐藏,不让外界发现或使用
  • 将可以允许外界使用的内容通过接口开发,让用户通过接口使用
  • 隐藏属性的方法是通过 __变量名

1.1封装之隐藏属性

  • 隐藏数据属性
class Teacher:
    def __init__(self, name, age):
        # 将名字和年纪都隐藏起来
        self.__name = name
        self.__age = age

    # 对外提供访问老师信息的接口
    def tell_info(self):
        print('姓名:%s,年龄:%s' % (self.__name, self.__age))

    # 对外提供设置老师信息的接口,并附加类型检查的逻辑
    def set_info(self, name, age):
        if not isinstance(name, str):
            raise TypeError('姓名必须是字符串类型')
        if not isinstance(age, int):
            raise TypeError('年龄必须是整型')
        self.__name = name
        self.__age = age


# 实例化类得到对象
teacher = Teacher("serein", 18)
# 查看对象的信息
teacher.tell_info() # 姓名:serein,年龄:18

# 调用对象的方法修改对象的信息 , 符合要求就能修改成功
teacher.set_info('formerly', 22)

# 查看对象的信息
teacher.tell_info() # 姓名:formerly,年龄:22
  • 隐藏函数属性
class ATM:
    # 插卡
    def __card(self):
        print('插卡')

    # 身份认证
    def __auth(self):
        print('用户认证')

    # 输入金额
    def __input(self):
        print('输入取款金额')

    # 打印小票
    def __print_bill(self):
        print('打印账单')

    # 取钱
    def __take_money(self):
        print('取款')

    # 取款功能
    def withdraw(self):
        self.__card()
        self.__auth()
        self.__input()
        self.__print_bill()
        self.__take_money()


obj = ATM()
obj.withdraw()
# 插卡
# 用户认证
# 输入取款金额
# 打印账单
# 取款

1.2 property装饰器

  • property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值
  • 将函数属性,包装成数据属性
class Foo:
    def __init__(self, val):
        # 将属性隐藏
        self.__NAME = val

    @property
    def name(self):
        print(f'我在获取的时候被触发了')
        return self.__NAME

    @name.setter  # 设置值
    def name(self, value):
        print(f'我再修改的时候被触发了')
        # 在设定值之前进行类型检查
        if not isinstance(value, str):
            raise TypeError('%s must be str' % value)
        # 通过类型检查后,将值value存放到真实的位置self.__NAME
        self.__NAME = value

    @name.deleter  # 删除值
    def name(self):
        print(f'删除的时候调用了我')
        raise PermissionError('Can not delete')


f = Foo('serein')
# print(f.name)

# 触发name.setter装饰器对应的函数name(f,'formerly')
f.name = 'formerly'
print(f.name)
# 触发name.setter对应的的函数name(f,123),抛出异常TypeError
f.name = 123

# 触发name.deleter对应的函数name(f),抛出异常PermissionError
del f.name

2.继承

  • 子类可以继承父类中的方法和类变量(不是拷贝一份,父类的还是属于父类,子类可以继承而已)
  • 父类也可以叫基类,子类也可以叫派生类
class Base:

    def func(self):
        print("Base.func")

class Son(Base):
    
    def show(self):
        print("Son.show")
        
s1 = Son()
s1.show() # Son.show
s1.func() # 优先在自己的类中找,自己没有才去父类。
# Base.func

2.1 属性查找顺序

class Base:
    def f1(self):
        print('before')
        self.f2() # slef是obj对象(Foo类创建的对象) obj.f2
        print('base.f1')
        
	def f2(self):
        print('base.f2')
        
class Foo(Base):
    def f2(self):
        print('foo.f2')
        
obj = Foo()
obj.f1() # 优先去Foo类中找f1,因为调用f1的那个对象是Foo类创建出来的。


>>> before
>>> foo.f2
>>> base.f1

b1 = Base()
b1.f1()

>>> before
>>> base.f2
>>> base.f1

查找总结

  • 执行对象.方法时,优先去当前对象所关联的类中找,没有的话才去她的父类中查找。
  • Python支持多继承:先继承左边、再继承右边的。
  • self到底是谁?去self对应的那个类中去获取成员,没有就按照继承关系向上查找 。

2.2 经典类和新式类

# 经典类和新式类

# Python2区分经典类和新式类
# 在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
# 在python2中,显式地声明继承object的类,以及该类的子类,都是新式类

# Python3都是新式类
# 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类

2.3 隐藏属性

class Foo:
    # 变形为_Foo__fa
    def __f1(self):
        print('Foo.f1')

    def f2(self):
        print('Foo.f2')
        # 变形为self._Foo__fa,因而只会调用自己所在的类中的方法
        # _Foo__f1
        self.__f1()
        #
        self._Bar__f1()


class Bar(Foo):
    # 变形为_Bar__f1
    def __f1(self):
        print('Bar.f1')

# _Bar__f1
b = Bar()
# 在父类中找到f2方法,进而调用b._Foo__f1()方法,同样是在父类中找到该方法
b.f2()

# 隐藏属性 : 隐藏给当前类,除了当前类,其他继承的子类均不能找到我隐藏的属性

2.4 抽象和继承

# 继承和抽象
# 继承描述的是子类与父类之间的关系
# 抽象即抽取类似或者说比较像的部分

# 抽象分成两个层次
#   将奥巴马和梅西这俩对象比较像的部分抽取成类;
#   将人,猪,狗这三个类比较像的部分抽取成父类。

# 继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构。
# 抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类

# 继承与重用性

# 抽象出  猫 和 狗 两个类

# 猫类 : 喵喵叫、吃、喝、拉、撒、睡
# 狗类 : 汪汪叫、吃、喝、拉、撒、睡

# 猫和狗有大量相同的内容
class Cat:

    def call(self):
        print '喵喵叫'

    def eat(self):
       pass

    def drink(self):
        pass

    def shit(self):
        pass

    def pee(self):
        pass

class Dog:

    def call(self):
        print '汪汪叫'

    def eat(self):
        pass

    def drink(self):
        pass

    def shit(self):
        pass

    def pee(self):
        pass

# 吃、喝、拉、撒是猫和狗都具有的功能,而我们却分别的猫和狗的类中编写了两次
# 如果使用 继承 的思想去实现功能
# 动物:吃、喝、拉、撒
# 猫:喵喵叫(猫继承动物的功能)
# 狗:汪汪叫(狗继承动物的功能)

class Animal(object):
    # 吃喝拉撒
    def eat(self):
        print(f'{self.name} 正在吃饭 ')

    def drink(self):
        print(f'{self.name} 正在喝水 ')

    def shit(self):
        print(f'{self.name} 正在尿尿 ')

    def pee(self):
        print(f'{self.name} 正在大便 ')


# 猫类
class Cat(Animal):
  	# 初始化猫猫名字
    def __init__(self, name):
        self.name = name
	
    def cry(self):
        print(f'{self.name} 正在喵喵叫 ')


# 狗类
class Dog(Animal):
    def __init__(self, name):
        self.name = name

    def cry(self):
        print(f'{self.name} 正在汪汪叫 ')

# 实例话对象
cat_one = Cat(name='小黄')
# 调用父类方法
cat_one.eat()  # 小黄 正在吃饭

# 实例话对象
cat_two = Cat(name='小花')
# 调用父类方法
cat_two.drink()  # 小花 正在喝水

# 实例话对象
dog_one = Dog(name='旺财')
# 调用父类方法
dog_one.eat()  # 旺财 正在吃饭

2.5 继承顺序

2.5.1 非菱形结构继承顺序
  • Python中子类可以同时继承多个父类,如A(B,C,D)
  • 如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

2.5.2 菱形结构继承顺序

  • 如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先
  • 深度优先,当类是经典类时,多继承情况下,在查找属性不存在时,会按照深度优先的方式查找下去

  • 广度优先,当类是新式类时,多继承情况下,在查找属性不存在时,会按照广度优先的方式查找下去

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):
    pass

f1 = F()
f1.test()

# 新式类继承顺序:F->D->B->E->C->A
# 经典类继承顺序:F->D->B->A->E->C

2.6 派生

  • 派生是继承的延伸
  • 派生是指,子类继承父类,派生出自己的属性和方法,并且重用父类的属性和方法
  • Super()超类
class Animal(object):
    # 初始化方法
    def __init__(self):
        self.name = 'xxx'
        self.animal_class = ['Cat', 'Dog']

    def eat(self):
        print(f'{self.name} 正在吃饭!')


class Cat(Animal):
    # 我自己的初始化方法
    def __init__(self, name):
        # 如果我定义了animal_class,会覆盖掉父类里面的 animal_class
         	self.animal_class = ['Cat', 'Dog','zzp']
        # super 超级 , 将父类里面的 init 方法加载到我当前类中
        # 【1】方式一:调用super()会得到一个特殊的对象
        # 该对象专门用来引用父类的属性,且严格按照MRO规定的顺序向后查找
        	super().__init__()
        # 【2】方式二:指定类的加载 指定类 里面的 __init__() 方法
        # Animal.__init__(self)
        	self.name = name
        # super().__init__()


c = Cat(name='小花')
print(c.animal_class) # ['Cat', 'Dog'] # 定义在超类前
print(c.name)	# 小花

# 如果子类初始化方法的属性值定义在 super().__init__()前面的话,打印属性值时,出现的是父类的属性值,super()调用时,冲刷了子类定义的属性值
# 反知子类初始化方法的属性值定义在 super().__init__()后面的话,打印属性值时,出现的是子类自己定义的属性值,定义的属性值冲刷了super继承的父类的属性值

2.7 组合

  • 在一个类中以另外一个类的对象作为数据属性,称为类的组合
  • 组合与继承都是用来解决代码的重用性问题
class Course:
    def __init__(self, name, period, price):
        self.name = name
        self.period = period
        self.price = price

    def tell_info(self):
        print(f'当前课程名字 {self.name} 当前课程周期 {self.period} 当前课程价格 {self.price}')


class Date:
    def __init__(self, year, mon, day):
        self.year = year
        self.mon = mon
        self.day = day

    def tell_birth(self):
        print(f'当前生日 {self.year} 年 {self.mon} 月 {self.day} 日')


class People:
    school = '清华大学'

    def __init__(self, name, sex, age):
        self.name = name
        self.sex = sex
        self.age = age


# Teacher类基于继承来重用People的代码
# 基于组合来重用Date类和Course类的代码
class Teacher(People):
    # 老师是人
    def __init__(self, name, sex, age, title, year, mon, day):
        # 初始化老师的名字,性别,年龄
        super().__init__(name, age, sex)
        # 老师有生日
        self.birth = Date(year, mon, day)
        # 老师有课程,可以在实例化后,往该列表中添加Course类的对象
        self.courses = []

    def teach(self):
        print(f'当前老师正在授课 {self.name}')


# # 实例化Date得到对象
# data = Date(year=2000,mon=9,day=25)
# print(data.tell_birth())


python = Course('python', '3mons', 3000.0)
linux = Course('linux', '5mons', 5000.0)
teacher1 = Teacher('serein', 'male', 18, '金牌讲师', 1999, 12, 17)

#  teacher1有两门课程
teacher1.courses.append(python)
teacher1.courses.append(linux)
for i in teacher1.courses:
    i.tell_info()

teacher1.birth.tell_birth()

teacher1.teach()

# teacher1.courses.append(python)
# teacher1.courses.append(linux)
#
# # 重用Date类的功能
# teacher1.birth.tell_birth()
#
# # 重用Course类的功能
# for obj in teacher1.courses:
#     obj.tell_info()

# 当前生日 1987 年 3 月 23 日
# 当前课程名字 python 当前课程周期 3mons 当前课程价格 3000.0
# 当前课程名字 linux 当前课程周期 5mons 当前课程价格 5000.0
2.7.1组合封装的区别
  • 组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同
  • 继承主要是用来提取所有子类共有的功能的,总结成一个父类
    • 通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物
    • 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人
  • 组合就相当于将不同的数据类型,包括八大基本数据类型,包括我们的对象,包括我们的类总结到一起,可以在一个类的对象里面调用其他类的对象和方法
    • 用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系
    • 比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...

2.8 抽象类

  • 在python中,利用abc模块实现抽象类
  • 和 Java的接口一样:我在父类接口中定义了几个方法,只要你这个类继承了我的接口,你就必须在子类给我实现这几个方法,可以不写内容,但必须写方法,否则报错
#  抽象类的书写步骤
# 【1】创建一个父类
# 【2】必须导入 import abc
# 【3】父类必须继承一个属性 class Parent(metaclass=abc.ABCMeta):
# 【4】在想要子类中必须实现的方法上面加一个 装饰器 : @abc.abstractmethod
# 【5】子类继承父类,子类必须重写父类中被添加 了 @abc.abstractmethod 的方法
import abc


# 定义父类,父类必须继承 metaclass=abc.ABCMeta
class Parent(metaclass=abc.ABCMeta):
    # 在子类中必须实现的方法: read 和 write
    # 在子类中可以不实现的的方法:check

    # 声明子类中必须实现的方法,要加装饰器 @abc.abstractmethod
    @abc.abstractmethod
    def read(self):
        ...

    # 声明子类中必须实现的方法,要加装饰器 @abc.abstractmethod
    @abc.abstractmethod
    def write(self):
        ...

    def check(self):
        ...


# 创建一个子类

class SonTwo(Parent):
    def read(self):
        ...

    def write(self):
        ...

class SonOne(Parent):
    ...
 
som_two = SonTwo()
son_one = SonOne()
# TypeError: Can't instantiate abstract class SonOne with abstract methods read, write

3.多态

  • 多态是面向对象编程中的一个概念,指的是同一种操作在不同的对象上有不同的行为。简而言之,多态允许不同类的对象对相同的消息作出响应,表现出不同的行为

3.1鸭子类型

  • 鸭子类型是一种动态类型的概念,它关注对象的行为而非其类型。
  • 鸭子类型的核心思想是:如果一个对象走起路来像鸭子、游起泳来像鸭子,那么它就可以被视为鸭子,而不管它是否真正是鸭子
  • 在鸭子类型中,关注的是对象是否具有某些方法或行为,而不是关注对象的具体类型
class Animal:
    def make_sound(self):
        pass
      
      
class Dog:
    def sound(self):
        return "Woof!"

class Cat:
    def sound(self):
        return "Meow!"

class Duck:
    def sound(self):
        return "Quack!"

def make_sound(animal):
    return animal.sound()

dog = Dog()
cat = Cat()
duck = Duck()

print(make_sound(dog))  # 输出 "Woof!"
print(make_sound(cat))  # 输出 "Meow!"
print(make_sound(duck))  # 输出 "Quack!"

方法补充issubclass 和 isinstance

# 检查对象类型 isinstance
print(isinstance('serein',str)) # True
print(isinstance('serein',int)) # False

class People(object):
     ...
p = People()
print(isinstance(People,object)) # True
print(isinstance(p,object)) # True


# 判断一个类是否是一个类的子类的
class Person(object):
    ...
class Student(Person):
    ...
class Teacther:
    ...
print(issubclass(Student,Person)) # True
print(issubclass(Teacther,Person)) # False
print(issubclass(Person,object)) # True
print(issubclass(Teacther,object)) # True

4. 反射

  • 反射是一种程序可以访问、检测和修改其本身状态或行为的能力
  • 在 Python 中,反射主要指通过字符串的形式操作对象的属性
  • 通过字符串的形式操作对象相关的属性,python中的一切事物都是对象(都可以使用反射)
# 反射方法的四个方法
# 1.获取对象的属性值,如果属性不存在,可提供默认值。
# getattr(object, name,[default])

# 2.判断对象是否具有指定属性
# hasattr(object, name)

# 3.设置对象的属性值
# setattr(object, name, value)

# 4.删除对象的属性
# delattr(object, name)
 
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def run(self):
        print("人能跑 ...")

    @staticmethod
    def read():
        ...

person = Person('serein', 18)

# 1.getattr(object, name[, default])
# 获取对象的属性值,如果属性不存在,可提供默认值。
# 获取到对象中的值,如果存在则返回当前值
print(getattr(person,'name'))
# 获取对象中的值,如果不存在则报错
print(getattr(person,'sex'))
# 获取对象中的值,如果不存在则报错,但是可以指定不存在的时候返回的默认值
print(getattr(person,'sex',None))
# **** 如果获取的是对象中的函数,那会直接返回函数的内存地址,可以直接调用该函数
res = getattr(person,'run')
print(res())


# 2.hasattr(object, name)
# 判断对象是否具有指定属性
# 获取到对象中的指定属性,如果存在,就返回 True
print(hasattr(person, 'name')) # True
# 获取到对象中的指定属性,如果不存在,就返回 False
print(hasattr(person, 'sex')) # False


# 3.setattr(object, name, value)
# 设置对象的属性值
# 先用 hasattr 判断当前对象是否具有该属性
# 如果不存在我则新增 setattr
# 如果存在直接获取  getattr
def func(obj=None, name=None, value=None):
    if hasattr(obj, name):
        return getattr(obj, name)
    else:
        setattr(obj, name, value)
        print(f'设置完成')
        return func(obj=obj, name=name, value=value)

# 获取到一个已经存在的值
print(func(obj=person, name='name'))  # serein
# 获取一个不存在的值
print(func(obj=person, name='gender', value='male'))  # male



# 4.delattr(object, name)
# 删除对象的属性
# 只能删除对象中的数据属性,无法删除函数属性
print(hasattr(person, 'run')) # True
delattr(person, 'run')
print(hasattr(person, 'run')) # 删除后报错

# 类也是对象
# 只有类经过初始化以后。,才会将 init 中属性加载到对象中
# 类没有初始化的时候,是不会存在init里面的属性的
print(Person.name)
print(hasattr(Person,'name'))
Person.run(person)
print(hasattr(Person,'run'))
Person.read()
print(hasattr(Person,'read'))

# 反射当前模块中的成员

import sys

def d1():
    ...
print(sys.modules[__name__])

print(hasattr(sys.modules[__name__],'sys'))
print(hasattr(sys.modules[__name__],'d1'))