封装

一、封装

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
原文地址:https://www.cnblogs.com/itone/p/9248896.html