20. 面向对象之封装

1. 面向对象的三大特性

面向对象编程有三大特性:封装、继承、多态

2. 理论

2.1 概念

封装指的是把数据与功能都整合到一起,之前所说的”整合“二字其实就是封装的通俗说法。
除此之外,针对封装到对象或者类中的属性,可以严格控制对它们的访问,分两步实现:隐藏与开放接口

2.2 封装的目的

将某些属性和方法隐藏起来,在程序外部看不到,其它程序无法调用

保护数据,可以防止外部代码随意修改对象内部的数据

3. 如何进行封装---隐藏属性

3.1 隐藏属性方法

Python的Class机制采用双下划线开头的方式将属性隐藏起来(设置成私有的)

但其实这仅仅只是一种变形操作,类中所有双下滑线开头的属性都会在类定义阶段、检测语法时自动变成_类名__属性名的形式

3.2 隐藏属性访问

在类外部无法直接访问双下滑线开头的属性,但知道了类名和属性名就可以拼出名字:__类名__属性,然后就可以访问了,
如Person._Person__NAME,所以说这种操作并没有严格意义上地限制外部访问,仅仅只是一种语法意义上的变形

class Student:
    _ _school = 'MIT'  # 在需要被封装数据属性的位置,加_ _

    def _ _init_ _(self, name, age, grade):
        self.name = name
        self.age = age
        self.grade = grade

    def show_info(self):
        print(f'name is {self.name}, age is {self.age}, grade is {self.grade},'
              f'school is {self.__school}')

    def _ _lab(self):  # 在需要被封装函数属性的位置,加__
        print(f'{self.__school}的{self.name}正在做实验')


print(Student._ _dict_ _)  # '_Student_ _school': 'MIT'    类的属性字典变成变成 _类名__属性名的形式

stu1 = Student('neymar', 32, 2)
stu1.show_info()  # name is neymar, age is 32, grade is 2,school is MIT
print(stu1._ _dict_ _)  # {'name': 'neymar', 'age': 32, 'grade': 2}

stu1.school = 'California'  # 隐藏school数据属性后对象无法直接修改该属性
print(stu1._ _dict_ _)  # {'name': 'neymar', 'age': 32, 'grade': 2, 'school': 'California'}
print(Student._ _dict_ _)  # '_Student__school': 'MIT'

# 通过  类名._类名__属性名修改属性
Student._Student_ _school = 'Harvard'
print(Student._ _dict_ _)  # '_Student__school': 'Harvard'
print(stu1._Student_ _school)  # Harvard
stu1._Student_ _lab()  # Harvard的neymar正在做实验

 3.3 隐藏方法变形

在类内部是可以直接访问双下划线开头的属性的,因为在类定义阶段类内部双下划线开头的属性统一发生了变形。

class Student:
    __school = 'MIT'  # 本质上变形成了_Student__school

    def __init__(self, name, age, grade):
        self.__name = name  # 本质上变形成了self._Student__name
        self.age = age
        self.grade = grade

    def __lab(self):  # 本质上变形成了_Student__lab
        print(f'{self.__school}的{self.__name}正在做实验')

    def run(self):
        self.__lab()  # 本质上变形成了self._Student__lab())
        print(self.__school)  # 本质上变形成了self._Student__school

 3.4 变形操作只会发生一次

变形操作只在类定义阶段发生一次,在类定义之后的赋值操作,不会变形

class Student:
    _ _school = 'MIT'  # 本质上变形成了_Student_ _school

    def _ _init_ _(self, name, age, grade):
        self._ _name = name  # 本质上变形成了self._Student_ _name
        self.age = age
        self.grade = grade

    def _ _lab(self):  # 本质上变形成了_Student__lab
        print(f'{self._ _school}的{self._ _name}正在做实验')

    def run(self):
        self._ _lab()  # 本质上变形成了self._Student_ _lab())
        print(self._ _school)  # 本质上变形成了self._Student_ _school

Student._ _school = 'singapore'
print(Student._ _dict_ _)  # '_Student_ _school': 'MIT'  数据属性并没有发生修改

3.5 总结

(1)在类内部 将变量名 用双下划线 声明
在类初始化的时候就会由原来的变量名 __变量名 变换 变换成 _类名_ _变量名
(2)在类内部封装起来的属性
内部调用 self 去调用的时候直接 self.__变量名即可调用
如果是对象调用 隐藏起来的属性 那就需要 对象._类名__变量名 去调用
(3)在类内部封装属性后变形只会发生一次
当使用对象或者类.__变量名去修改属性的时候 修改的是 新的属性而不是原来的属性

4. 封装之后如何访问---开放接口

4.1 原理

将数据和功能隐藏起来就限制了类外部对数据和功能的直接操作,类内部应该提供相应的接口来允许类外部间接地操作数据和功能,接口之上可以增加额外的逻辑对数据和函数的操作进行严格限制

4.2 隐藏数据属性

class Student:

    def __init__(self, name, age, grade):
        self.__name = name
        self.__age = age
        self.__grade = grade

    def show_info(self):
        print(f'{self.__name}的年龄是{self.__age}年级是{self.__grade}')

    def set_info(self, name1, grade1):
        if not name1.startswith('mit_'):
            raise ValueError('名字必须以mit_开头')
        if not grade1.isdigit():
            raise ValueError('年级必须是数字')
        self.__name = name1  # 符合以上两个条件才允许修改属性
        self.__grade = grade1


stu1 = Student(name='alan', age=30, grade=1)
stu1.show_info()  # alan的年龄是30年级是1

# 对象无法直接修改类中数据属性
stu1.name = 'messi'
stu1.show_info()  # alan的年龄是30年级是1

# 想要修改类中数据属性,必须通过接口进行修改
stu1.set_info(name1='messi', grade1='2')  # ValueError: 名字必须以mit_开头
stu1.set_info(name1='mit_messi', grade1=2)
# AttributeError: 'int' object has no attribute 'isdigit'  int类型没有isdigit方法

stu1.set_info(name1='mit_messi', grade1='2')  # 修改成功
stu1.show_info()  # mit_messi的年龄是30年级是2

4.3 隐藏函数属性

# ATM程序的取款功能,由许多其它功能组成:插卡、输入密码、输入取款金额、取款、打印回执等
# 而对于使用者来说,只需要withdraw这个功能接口即可,其余功能都可以隐藏起来
class ATM:
    def __inert_card(self):
        print('插卡')

    def __input_pwd(self):
        print('输入密码')

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

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

    def __print_bill(self):
        print('打印回执')

    def withdraw(self):
        self.__inert_card()
        self.__input_pwd()
        self.__input_money()
        self.__take_money()
        self.__print_bill()

obj = ATM()
obj.withdraw()

5. property属性

5.1 引入

计算bmi

class Person:
    def __init__(self, name, height, weight):
        self.__name = name
        self.__height = height
        self.__weight = weight

    def bmi(self):
        info = f'{self.__name}的BMI数值是:{self.__weight / (self.__height ** 2)}'
        return info

person1 = Person(name='haaland', height=1.95, weight=75)
print(person1.bmi())  # haaland的BMI数值是:19.723865877712033
# bmi计算得到的结果是一个数字,应该和姓名、身高、体重一样是一个数据属性,而不是函数属性
# 给计算bmi的函数加一个property属性,将函数属性伪装成数据属性
class Person:
    def __init__(self, name, height, weight):
        self.__name = name
        self.__height = height
        self.__weight = weight

    @property
    def bmi(self):
        info = f'{self.__name}的BMI数值是:{self.__weight / (self.__height ** 2)}'
        return info


person1 = Person(name='haaland', height=1.95, weight=75)
# 函数属性变成了数据属性
print(person1.bmi)  # haaland的BMI数值是:19.723865877712033

5.2 为什么要用property

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

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

public     这种就是不封装,对外公开

protected     这种封装方式对外不公开,但对子类公开

private      这种方式对任何都不公开

 

python并没有在语法上将三种封装方式内建到class机制中,通过property方法可以实现

5.3 setter与deleter装饰器

setter与deleter装饰器一般与property结合使用

class Student:
    def __init__(self, name, age, grade):
        self.__name = name
        self.__age = age
        self.__grade = grade

    @property  # 将函数属性包装成数据属性
    def name_tool(self):
        return f'MIT_{self.__name}'

    @name_tool.setter  # 给以上包装后的数据属性设置值
    def name_tool(self, value):
        if not len(value) <= 3:
            raise ValueError('名字必须是三个字及以下')
        self.__name = value

    @name_tool.deleter  # 给以上包装后的数据属性删除值
    def name_tool(self):
        del self.__name


stu1 = Student(name='lavigne', age=19, grade=2)
# 获取完整名字    触发property,此时name_tool函数中的self都是对象stu1本身
print(stu1.name_tool)  # MIT_lavigne
print(stu1.__dict__)  # {'_Student__name': 'lavigne', '_Student__age': 19, '_Student__grade': 2}

# 修改名字      触发setter,所有self都是对象stu1本身
stu1.name_tool = 'lav'
print(stu1.__dict__)  # {'_Student__name': 'lav', '_Student__age': 19, '_Student__grade': 2}

# 删除名字      触发deleter,所有self都是对象stu1本身
del stu1.name_tool
print(stu1.name_tool)  # AttributeError: 'Student' object has no attribute '_Student__name'. Did you mean: '_Student__age'?
print(stu1.__dict__)  # {'_Student__age': 19, '_Student__grade': 2}

 5.4 使用property内置函数对外提供统一接口

class Student:
    def __init__(self, name, age, grade):
        self.__name = name
        self.__age = age
        self.__grade = grade

    def __full_name(self):
        return f'MIT_{self.__name}'

    def __set_name(self, value):
        if not len(value) <= 3:
            raise ValueError('名字必须是三个字及以下')
        print(self.__dict__)
        self.__name = value
        print(self.__dict__)

    def __del_name(self):
        print(self.__dict__)
        del self.__name
        print(self.__dict__)

    # 不用装饰器,使用property内置函数,对外提供统一接口
    name_tool = property(fget=__full_name, fset=__set_name, fdel=__del_name)


stu1 = Student(name='lavigne', age=20, grade=3)

# 获取完整名字    只需要调用接口即可,触发property第一个参数函数
print(stu1.name_tool)  # MIT_lavigne

# 修改名字      只需要调用接口即可,触发property第二个参数函数
stu1.name_tool = 'lav'
# {'_Student__name': 'lavigne', '_Student__age': 20, '_Student__grade': 3}
# {'_Student__name': 'lav', '_Student__age': 20, '_Student__grade': 3}

# 删除名字      只需要调用接口即可,触发property第三个参数函数
del stu1.name_tool
# {'_Student__name': 'lav', '_Student__age': 20, '_Student__grade': 3}
# {'_Student__age': 20, '_Student__grade': 3}

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2024-08-15 01:01  hbutmeng  阅读(3)  评论(0编辑  收藏  举报