面向对象的三大特性之封装

一 封装

1 封装介绍

封装是面向对象三大特性最核心的一个特性

封装指的就是把数据与功能都整合到一起,针对封装到对象或者类中的属性,可以严格控制对它们的访问,分两步实现:隐藏与开放接口

2、隐藏属性

如何隐藏:

​ 在属性名前加前缀,就会实现一个对外隐藏属性效果。Python 的 class 机制采用了双下划线开头的方式将属性隐藏起来(设置成私有的),这其实仅仅只是一种变形的操作 ,类中所有双下划线的属性都会在类定义阶段检测语法时自动变成 _类名__属性的形式:

该隐藏需要注意的问题:

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

class Foo:
    __x = 1 # _Foo__x
    def __f1(self): #Foo_f1
      print('from test')

print(Foo.__dict__)
#{'__module__': '__main__', '_Foo__x': 1, '_Foo__f1': <function Foo.__f1 at 0x10ab1c400>, '__dict__': <attribute '__dict__' of 'Foo' objects>, '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None}

#print(Foo.__x) #报错,访问不到
print(Foo_Foo.__x) #1
print(Foo._Foo__f1) #<function Foo.__f1 at 0x10ab1c400>

2)这种隐藏是对外不对内,因为__ 开头的属性会在检查类体代码语法时统一发生变形

class Foo:
    __x = 1  # _Foo__x = 1

    def __f1(self):  # _Foo__f1
        print('from test')

    def f2(self):
        print(self.__x) # print(self._Foo__x)
        print(self.__f1) # print(self._Foo__f1)

# print(Foo.__x)  #type object 'Foo' has no attribute '__x'
print(Foo.__f1) #'Foo' has no attribute '__f1'
obj=Foo()
obj.f2() #<function Foo.__f1 at 0x107057400>

3) 这种变形操作只在检查类体语法的时候发生一次,之后定义的__开头的属性都不会变形

class Foo:
    __x = 1  # _Foo__x = 1

    def __f1(self):  # _Foo__f1
        print('from test')

    def f2(self):
        print(self.__x) # print(self._Foo__x)
        print(self.__f1) # print(self._Foo__f1)

Foo.__y=3
print(Foo.__dict__)
#{'__module__': '__main__', '_Foo__x': 1, '_Foo__
# f1': <function Foo.__f1 at 0x10793b400>, 
# 'f2': <function Foo.f2 at 0x10793b1e0>, 
# '__dict__': <attribute '__dict__' of 'Foo' objects>,
# '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__doc__': None, '__y': 3}


print(Foo.__y) #3
class Foo:
    __x = 1  # _Foo__x = 1

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

obj=Foo('egon',18)
print(obj.__dict__) #{'_Foo__name': 'egon', '_Foo__age': 18}
print(obj.name, obj.age) #'Foo' object has no attribute 'name'

3、开发接口

为何要隐藏属性

1)隐藏数据属性"将数据隐藏起来限制了类外部对数据的直接操作。

类内应该提供相应的接口来允许类外部间接地操作数据,接口之上可以附加额外的逻辑来对数据的操作进行严格地控制"

class People:
  	def __init__(self, name):
        sel.__name = name
    def get_name(self):
				#通过该接口就可以间接地访问到名字属性
    		print('看什么看')
        print(self.__name)
     def set_name(self, val):
      	 if type(val) is not str:
          	 print('渣渣, 必须传入字符串类型')
           	 return
         self.__name = val

obj = People('egon')
#print(obj.name) #无法直接用名字属性
obj.set_name('EGON')
obj.set_name(123123)
obj.get_name()
    

定义属性就是为了使用,所以隐藏并不是目的

2)隐藏函数/方法属性:目的是为了隔离复杂度

例如 ATM 程序的取款功能,该功能有很多其他功能组成,如插卡,身份认证,输入金额,打印小票,取钱等,而对使用者来说,只需要开发取款这个功能接口即可,其余功能我们可以隐藏起来。

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()

总结:

隐藏属性与开放接口,本质就是为了明确类的内外,类内部可以修改封装内的东西而不影响外部调用者的代码;而类外部只需要拿到一个接口,只要接口名,参数不变,则无论设计值怎么改变内部实现代码,使用者均无需改变代码。

二 property

property 是一个装饰器,用来绑定给对象的方法,将方法伪造成一个数据属性

Ps:装饰器是在不修改被装饰对象源代码以及调用方式的前提下为被装饰对象添加新功能的可调用对象
案例一:

BMI指数是用来衡量一个人的体重与身高对健康影响的一个指标,计算公式为:

体质指数(BMI)=体重(kg)÷身高^2(m)
例:70kg÷(1.75×1.75)=22.86

代码一:

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)
      
      
obj1 = People('han', 70, 1.83)
print(obj1.bmi())

将 bmi 定义为函数的原因

1、从 bmi的公式上课,bmi 应该是触发功能计算得到的
2、bmi 是随着身高、体重的变化而动态变换的,不是一个固定的值,它每次都是需要临时计算得到的。
但是所以从使用者角度来看, bmi 听起来更像是一个数据属性,而非功能,所以从使用者角度出发,使用bmi 应该是调用数据属性,而不是通过调用方法名( ) 方式拿到bmi 的数据,因此我们可以使用装饰器---property,将对象的方法伪造成数据属性。

初版

代码二:使用 property 装饰器

class People:
  	def __init__(self, name, weight, height):
        self.name = name
        self.weight = weight
        self.height = height
    #将方法伪造成数据属性,可以通过对象.方法名运行拿到返回值  
    @property  # bmi = property(bmi)
    def bmi(self):
      	return self.weight/(self.height ** 2)
    
       
obj1 = People('han', 75, 1.85)
print(obj1.bmi) ##触发方法bmi的执行,将obj自动传给self,执行后返回值作为本次引用的结果
21.913805697589478

进阶版

案例二:使用 property 有效地保证了属性访问的一致性。

如果有一个数据属性比较重要,不想直接被使用者随意通过修改等操作数据,所以要对外界隐藏起来,同时开设接口,在接口上增添操作数据条件规范。

class People:
  	def __init__(self, name):
        self.__name = name #隐藏数据属性
    #开设接口
    def get_name(self, name):
      	return self__name
    def set_name(self, val):
      	if type(val) is not str:
          	print('必须传入 str 类型')
        		return
        self.__name = name
        
    def del_name(self):
      	print('不可以删除')
        # del self.__name
    
    #name:是想让使用者使用的名字    
    name = property(get_name, set_name, del_name) #装饰所有方法,同时返回给 name,当通过调用或者name时会自动触发对应的方法

obj1 = People('han')
#人类正常的思维逻辑
print(obj1.name)#han
obj1.name = 18 #打印:必须传入 str 类型
del obj1.name #打印:不可以删除

终版

案例三:

使用 property 可以有效地保证了属性,另外 property 还提供了设置和删除属性的功能

class People:
		def __init_(self, name):
        self.__name = name
    @property  # name= property(name)  装饰 name
    def name(self) #obj1.name
    		return self.__name
    
    @name.setter #通过装饰 name,给name 方法添加修改功能
    def name(self, val): #obj1.name = 'HAN'
    		if type(val) is not str:
           print('必须传入 str 类型')
           return 
       	self.__name = val
        
    @name.deleter #通过
    def name(self): #del obj1.name
      	print('不让删除')
        #del self.__name

        
obj1 = People('han')
#人类正常的思维逻辑
print(boj1.name) #触发查看功能
obj1.name = 18 #触发修改功能
del obj1.name #触发删除功能

property 其实也是一个类,通过装饰被装饰对象如(name), ==》name = procperty(name ) 得到一个带有装饰器功能的对象, @name.setter:其实是调用 带有装饰器功能的对象的绑定方法setter

原文地址:https://www.cnblogs.com/xy-han/p/12663148.html