• 封装


    一、封装

    1. 什么是封装

    • 封装,就是把类属性的内容,对外隐藏,对内开放
    • 隐藏表示不能被使用,开放表示可以被使用

    2. 如何隐藏类的属性

    • 隐藏属性的语法:在属性开头前加__,比如__数据属性名或函数属性名,那么__数据属性名或函数属性名就是隐藏属性
    • 这种隐藏是对外不对内的,即在类的内部可以直接访问,而在类的外部则无法直接访问
    class People:
        __country='China'
        def __init__(self,name,age,sex):
            self.name=name
            self.age=age
            self.sex=sex
        def eat(self):
            print('eat......')
            print(People.__country)
    
    peo1=People('xut',19,'male')
    
    # 隐藏的数据属性不能在外部内使用
    peo1.__country
    # 需要间接的,利用类中未隐藏的属性去调用,对内开放
    peo1.eat()
    =============================================================
    AttributeError: 'People' object has no attribute '__country'
        
    eat......
    China
    

    隐藏的原理:

    • 这种隐藏只是语法上的变形操作
    • 内部能访问的原因:在检查语法时,会自动改变属性的语法,变成_类名__属性名
    • 这种变形,只在类定义阶段的语法检查时,发生一次,只能在类内定义,类外定义无效
    • 因此,如果是在类外,也可以通过_类名__属性名使用,但是这样就失去了隐藏的意义
    • 如果不想让子类的方法覆盖父类的,可以将该方法开头前加__
    • __init__用于初始化对象
    class People:
        __country='China'
        __n=100
        def __init__(self,name):
            self.__name=name
    
    peo1=People('xut')
    print(People.__dict__)
    print(peo1.__dict__)
    =============================================
    {'__module__': '__main__', '_People__country': 'China', '_People__n': 100, ...省略
     
    {'_People__name': 'xut'}
    
    • 如果不想让子类的方法覆盖父类的,可以将该方法开头前加__
    class Foo:
        def f1(self):
            print('Foo.f1')
        def f2(self):
            print('Foo.f2')
            self.f1()
    
    class Bar(Foo):
        def f1(self):
            print('Bar.f1')
    
    obj=Bar()
    obj.f2()
    ============================
    Foo.f2
    Bar.f1
    
    # 如果不想让子类的方法覆盖父类的,可以将该方法开头前加`__`
    class Foo:
        def __f1(self):     # _Foo__f1
            print('Foo.f1')
        def f2(self):
            print('Foo.f2')
            self.__f1()     # self._Foo__f1
    
    class Bar(Foo):
        def __f1(self):     # _Bar__f1
            print('Bar.f1')
    
    obj=Bar()
    obj.f2()
    ===========================================
    Foo.f2
    Foo.f1
    

    3. 为什么要封装

    ① 封装数据属性的目的

    • 定义属性的目的就是给类外部的使用者使用
    • 隐藏数据属性是为了不让外部使用者直接使用,需要类内部开辟一个接口
    • 外部的使用者,通过接口间接的操作隐藏的数据属性
    • 这样就可以在接口上,定制逻辑和规则,严格控制使用者对数据属性的操作

    ② 封装函数属性的目的

    • 隐藏函数属性,也是为了不让外部直接使用,需要类内部开辟一个接口
    • 接口内可以调用隐藏的函数属性,比如,将多个隐藏函数,放入到一个未隐藏函数中,由这个未隐藏的函数对外使用
    • 好处是隔离了复杂度
    #取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱
    #对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做
    #隔离了复杂度,同时也提升了安全性
    
    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()
    
    a=ATM()
    a.withdraw()
    

    4. 封装属性的使用

    • 先隐藏属性,再设计接口,才能控制使用者对属性的操作

    ① 定义查看接口

    class People:
        def __init__(self,name,age):
            self.__name=name
            self.__age=age
    # 对外开放接口,这个接口就是程序员自己设计的标准
        def tell_info(self):
            print('%s:%s' %(self.__name,self.__age))
    
    peo1=People('xut',18)
    
    peo1.tell_info
    =================================================
    xut:18
    

    ② 定义修改接口

    class People:
        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,x,y):
            self.__name=x
            self.__age=y
    
    peo1=People('xut',18)
    
    peo1.set_info('XDW',20)
    peo1.tell_info()
    =================================================
    XDW:20
    
    class People:
        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 type(name) is not str:
                print('用户必须为str类型')
                return
            if type(age) is not int:
                print('年龄必须为int类型')
                return
            self.__name=name
            self.__age=age
    
    peo1=People('xut',18)
    
    peo1.set_info(111,20)
    peo1.set_info('xde','xdw')
    ====================================================
    用户必须为str类型
    年龄必须为int类型
    
    class People:
        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 type(name) is not str:
                # raise 异常处理,主动报错
                raise TypeError('用户必须为str类型')
            if type(age) is not int:
                # raise 异常处理,主动报错
                raise TypeError('年龄必须为int类型')
            self.__name=name
            self.__age=age
    
    peo1=People('xut',18)
    
    peo1.set_info(111,20)
    peo1.set_info('xde','xdw')
    ======================================================
    TypeError: 用户必须为str类型
    TypeError: 年龄必须为int类型
    

    5. property

    • property内置函数,可以将类中函数伪装成数据属性的样子,达到查看、修改、删除
    • property可以将类内的函数伪装成一个数据属性去访问
    • 类加括号就是实例化的过程

    ① 未使用property

    • 没有使用伪装前,使用类似于特征类的属性,需要使用加括号使用
    class People:
        def __init__(self,name,weight,height):
            self.name=name
            self.weight=weight
            self.height=height
    
        def bmi(self):
            return self.weight / (self.height ** 2)
    
    peo1=People('xut',75,1.8)
    
    print(peo1.bmi())
    =====================================================
    23.148148148148145
    

    ② 使用@property伪装函数

    class People:
        def __init__(self,name,weight,height):
            self.name=name
            self.weight=weight
            self.height=height
        # 添加property内置装饰器
        @property
        def bmi(self):
            return self.weight / (self.height ** 2)
    
    peo1=People('xut',75,1.8)
    # 不用再bmi() 直接bmi就能触发功能,拿到返回值
    print(peo1.bmi)
    ===================================================
    23.148148148148145
    

    ③ 将接口伪装成属性

    • 伪装前,使用者需要使用tell_name()的形式
    class People:
        def __init__(self,name):
            self.__name=name
    
        def tell_name(self):
            return self.__name
    
    peo1=People('xut')
    print(peo1.tell_name())
    ================================
    xut
    
    class People:
        def __init__(self,name):
            self.__name=name
        # 伪装成属性
        @property
        def name(self):
            return self.__name
    
    peo1=People('xut')
    # 访问属性
    print(peo1.name)
    =================================
    xut
    

    @name.setter修改属性需求

    • 只要是被property装饰过的函数,就可以使用.setter修改属性
    class People:
        def __init__(self,name):
            self.__name=name
        @property
        def name(self):
            return self.__name
        # 修改属性
        @name.setter
        def name(self,name):
            if type(name) is not str:
                raise TypeError('名字必须为str类型')
            self.__name=name
            
    peo1=People('xut')
    peo1.name='davide'
    print(peo1.name)
    ===================================================
    davide
    

    @name.deleter删除属性需求

    • 只要是被property装饰过的函数,就可以使用.deleter修改属性
    class People:
        def __init__(self,name):
            self.name=name
        @property
        def name(self):
            return self.__name
        @name.setter
        def name(self,name):
            if type(name) is not str:
                raise TypeError('名字必须为str类型')
            self.__name=name
    # 可以删除属性
        @name.deleter
        def name(self):
            raise PermissionError('禁止删除')
    
    peo1=People('xut')
    del peo1.name
    ==========================================================
    PermissionError: 禁止删除
    

    Alt text

    ⑥ 古老的写法

    class People:
        def __init__(self,name):
            self.name=name
    
        def tell_name(self):
            return self.__name
    
        def set_name(self,name):
            if type(name) is not str:
                raise TypeError('名字必须为str类型')
            self.__name=name
    
        def del_name(self):
            raise PermissionError('禁止删除')
            
        name=property(tell_name,set_name,del_name)
    

    二、绑定方法与非绑定方法

    绑定方法:

    • 绑定给谁就应该由谁来调用,谁来调用就会将谁当作第一个参数自动传入

    1. 绑定方法分为两类

    ① 绑定给对象的方法

    • 在类内部定义的函数(没有被任何装饰器装饰的),默认就是绑定给对象

    ② 绑定给类的方法

    • 在类内部定义的函数,如果被装饰器@classmethod装饰,那就是绑定给类的,需要由类来调用,类来调用,就自动将类作为第一个参数传入

    ③ 绑定方法如何选择

    • 如果函数体代码,需要用到外部传入的类,则应该将该函数定义成绑定给类的方法
    • 如果函数体代码,需要用到外部传入的对象,则应该将该函数定义成绑定给对象的方法
    • 最终取决于用哪个,就用什么方法
    class Foo:
        @classmethod
        def f1(cls):
            print(cls)
    
        def f2(self):
            print(self)
    
    obj=Foo()
    print(obj.f2)
    print(Foo.f1)
    =====================================================================
    # 都是绑定方法
    <bound method Foo.f2 of <__main__.Foo object at 0x0000018D73E2BDA0>>
    <bound method Foo.f1 of <class '__main__.Foo'>>
    
    • 了解即可:绑定给类的,应该由类来调,但是对象也可以使用,但是自动传入的还是类
    class Foo:
        @classmethod
        def f1(cls):
            print(cls)
    
        def f2(self):
            print(self)
    
    obj=Foo()
    
    # 绑定给谁,就应该由谁使用
    print(Foo.f1)
    print(obj.f1)
    =================================================
    <bound method Foo.f1 of <class '__main__.Foo'>>
    <bound method Foo.f1 of <class '__main__.Foo'>>
    

    ④ 类实例化的应用场景:从配置文件中读取配置信息

    • 默认的实例化方式
    # 模拟配置文件
    import settings
    
    class Mysql:
        def __init__(self,ip,port):
            self.ip=ip
            self.port=port
            
        def tell_info(self):
            print('%s:%s' %(self.ip,self.port))
    # 默认的实例化方式:类名(内容)
    obj=Mysql('10.10.0.9',3307)
    obj1=Mysql(settings.IP,settings.PORT)
    
    • 从配置文件中读取配置,完成实例化
    setting配置文件:
    IP='10.0.0.1'
    PORT=3306
    
    # 模拟配置文件
    import setting
    
    class Mysql:
        def __init__(self,ip,port):
            self.ip=ip
            self.port=port
    
        def tell_info(self):
            print('%s:%s' %(self.ip,self.port))
    # 从配置文件实例化
        @classmethod
        def from_conf(cls):
            # 从配置文件中获取到IP和端口
            return cls(setting.IP,setting.PORT)
    
    # 一种新的实例化方式:从配置文件中读取配置,完成实例化
    obj1=Mysql.from_conf()
    obj1.tell_info()
    ==========================================================
    10.0.0.1:3306
    

    2. 非绑定

    • 既不与类绑定,又不与对象绑定,没有任何自动传值效果,就是一个普通函数,类和对象都可以调用
    • 类中定义的函数,如果被@staticmethod装饰器装饰过,那么该函数就变成非绑定方法

    根据不同应用场景选择绑定和非绑定

    • 如果函数体代码需要用外部传入的类,则应该将该函数定义成绑定给类的方法
    • 如果函数体代码需要用外部传入的对象,则应该将该函数定义成绑定给对象的方法
    • 如果函数体代码既不需要外部传入的类,也不需要外部传入的对象,则应该将该函数定义成非绑定方法,即普通函数
    import setting
    
    class Mysql:
        def __init__(self,ip,port):
            self.ip=ip
            self.port=port
    
        def tell_info(self):
            print('%s:%s' %(self.ip,self.port))
    
        @staticmethod
        def func():
            print('不绑定,普通函数')
    obj=Mysql('10.10.0.9',3307)
    
    obj.func()
    Mysql.func()
    print(obj.func)
    print(Mysql.func)
    =========================================================
    不绑定,普通函数
    不绑定,普通函数
    <function Mysql.func at 0x000001DD5C3DD2F0>
    <function Mysql.func at 0x000001DD5C3DD2F0>
    
    import uuid
    
    class Mysql:
        def __init__(self,ip,port):
            self.ip=ip
            self.port=port
            self.uid=self.creat_uid()
    
        def tell_info(self):
            print('%s:%s' %(self.ip,self.port))
    
        @staticmethod
        def creat_uid():
            return uuid.uuid1()
    obj=Mysql('10.10.0.9',3307)
    print(obj.uid)
    ==================================================
    e34837b6-7aa9-11e8-bb1f-b0359f445c6f
    
  • 相关阅读:
    ACM常用算法及练习(2)
    ACM常用算法及练习(1)
    ACM进阶计划
    《算法艺术与信息学竞赛》题目-提交方式对照表
    ACM之Java速成(4)
    ACM之Java速成(3)
    ACM之Java速成(2)
    ACM之Java速成(1)
    uva 11520
    uva 10755
  • 原文地址:https://www.cnblogs.com/itone/p/9248896.html
Copyright © 2020-2023  润新知