python学习笔记DAY16(面向对象编程)

面向对象编程

一、封装的概念

  1. 面向对象编程有三大特性:封装,继承,多态。其中最重要的特性就是封装
  2. 封装:就是把数据与功能都整合到一起,上一节我们一直提到的整合,就是封装的通俗讲法。
  3. 针对封装到对象或者类中的属性,我们还可以严格控制它们的访问,分两步实现(隐藏与开放接口)

二、隐藏属性

  1. 在属性前面加上 __ 就可以实现对外隐藏属性。
class Foo:
    __x = 1
    def __f1(self):
        print(self)
print(Foo.__x)   # 报错  AttributeError: type object 'Foo' has no attribute '__x' 找不到对应属性   

# 我们来查看到底发生了什么事情
print(Foo.__dict__)
# {'__module__': '__main__', '_Foo__x': 1, '_Foo__f1': <function Foo.__f1 at 0x000001EDC95AC5E0>...}
# 查看之后发现,属性是存在的,只是属性名发生了变化(在属性的前面加了“_类名”)
# 所以:
print(Foo._Foo__x)   # 1   遵循隐藏后的变化,这样是可以访问到的

总结:给属性加 __ 这种操作并没有严格意义上限制外部访问,只是一种语法意义上的变形

  1. 这种隐藏是对内不对外的(因为__开头的属性在,检查代码语法的时候会统一发生变形)
  2. 在 obj.f2() 运行 的时候,类中的代码已经运行完毕了。而检查语法是运行之前就完成的,所以改变形都变形过了
  3. 这种变形只在在检查语法阶段发生一次,之后再定义 __ 开头的属性,都不会发生变形。(比如类体外的Foo.__x = 5)
class Foo:
    __x = 1                   # 当 __x 变成  _Foo__x  时
    def __init__(self):
        self.__x = 10  

    def __f1(self):           # 当 __f1 变成  _Foo__f1  时
        print("正在运行f1")

    def f2(self):       # 类体内非隐藏属性
        self.__f1()     # 类体内调用隐藏属性       其实:这里的_f1 变成也了  _Foo__f1 

obj = Foo()
obj.f2()      # 正在运行f1

三、开放接口

定义属性就是为了使用,隐藏不是目的。(所以隐藏有更深的用意)

实际应用1:

  • 将数据隐藏起来限制外部对数据的直接操作,然后提供给外部可以访问属性的接口,接口可以附加额外的逻辑对外部操作进行严格的控制
# 设计者
class People:
    def __init__(self,name,age):
        self.__name = name          # 使用者无法访问隐藏属性
        self.__age = age
    def get_info(self):
        # 通过该查看接口可以访问被隐藏的属性
        print(f"名字:{self.__name},年龄:{self.__age}")

    def set_info(self,name,age):
        if not isinstance(name,str):  # 判断得到的数据是否是str类型,如果不是,给出错误提示
            raise TypeError("名字必须是字符串类型")
        if not isinstance(age,int):  # 判断得到的数据是否是int类型,如果不是,给出错误提示
            raise TypeError("年龄必须是整型")
        self.__name = name   # 使用者传入数据都符合条件之后,允许对属性进行修改
        self.__age = age

# 使用者
obj = People("nida",18)
obj.get_info()             # 名字:nida,年龄:18

obj.set_info(18,18)        # TypeError: 名字必须是字符串类型
obj.set_info("nida","18")  # TypeError: 名字必须是字符串类型
obj.set_info("nida2",19)    # 传入符合条件的参数
obj.get_info()              # 名字:nida2,年龄:19 改动成功

备注:这个特性非常重要,设计者可以对这个接口设置各种条件,来进行过滤。(可以让你访问,但是你要先符合我的条件)

实际应用2:

  • 把内部的逻辑函数全部隐藏,只对外提供一个开发接口,这样使用者使用起来就非常简洁,目的是为了隔离复杂度。
class ATM:
    def __card(self):
        print("插卡")
    def __auth(self):
        print("用户认证")
    def __input(self):
        print("输入取款金额")
    def __money(self):
        print("取款")
    def withdraw(self):
        print("取款功能")
        self.__card()
        self.__auth()
        self.__input()
        self.__money()

obj = ATM()
obj.withdraw()   # 取款功能/插卡/用户认证/输入取款金额/取款

总结:隐藏属性与开放接口,本质就是为了明确的区分内外。内部可以修改封装内的东西而不影响外部调用者的的代码。而类外部只需要一个接口,只要接口名,参数不变,无论设计者如何改变内部实现代码,使用者都不用改变自己的代码。

四、property

知识回顾:装饰器,是在不修改被装饰对象源代码及调用方式的前提下为被装饰对象,添加功能

property就是一个装饰器

实际应用:

BMI是 用来衡量一个人的体重与身高对健康影响的一个指标(计算公式:体质指数(BMI)=体重(kg)÷身高^2(m))

  • 身高和体重是不断变化的,所以每次想查看BMI,都需要通过计算才能得到,但是很明显BMI听起来更像是一个特征而非功能,为此python专门提供了一个装饰器property,可以将类中的函数“伪装”成对象的数据属性,对象在访问该特殊属性时会触发功能的执行,然后将返回值作为本次访问的结果:
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)

obj =  People("nida",50,1.6)
print(obj.bmi)     # 访问函数就像访问属性一样的感觉
  • 使用property有效的保证了属性访问的一致性,另外property还提供设置和删除的属性功能:
class Foo:
    def __init__(self,name):
        self.__name = name
    @property
    def name(self):              # 查看名字属性
        return self.__name
    @name.setter
    def name(self,new_name):     # 修改名字属性
        if not isinstance(new_name,str):
            raise TypeError("名字必须是字符串")
        self.__name = new_name
    @name.deleter
    def name(self):              # 删除名字属性
        raise TypeError("不允许删除")

obj = Foo("nida")
print(obj.name)                  # nida
obj.name = "yaoyao"
print(obj.name)                  # yaoyao
del obj.name                     # TypeError: 不允许删除

备注:通过property(把全部功能函数全部取名与第init内参数名一致,然后在函数上加上对应的property语法),即可将查看/修改/删除等功能函数,伪装的跟没有被隐藏过一样。

上一篇:Java的新项目学成在线笔记-day16(四)


下一篇:day16:io流的相关概念及其应用