一、封装概念

  封装是面向对象的特征之一,是对象和类概念的主要特性。

  封装就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。

二、隐藏属性

  在python中用双下划线开头的方式将属性隐藏起来(设置成私有的)

  其实这仅仅这是一种变形操作,类中所有双下划线开头的名称如__x都会自动变形成:_类名__x的形式。

  1. class A:
  2. __x = 1 # _A__x = 1
  3.  
  4. def __init__(self, name):
  5. self.__name = name # self._A__name='egon'
  6.  
  7. def __foo(self): # _A__foo
  8. print('%s foo run' % self.__name)
  9.  
  10. def bar(self):
  11. self.__foo() # self._A__foo()
  12. print('from bar')
  13.  
  14. # 无法找到类的属性和函数:
  15. # print(A.__x)
  16. # print(A.__foo)
  17.  
  18. print(A.__dict__) # 可以查看到_A__foo;bar这两个函数
  19. # 输出:{'__module__': '__main__', '_A__x': 1, '__init__': <function A.__init__ at 0x101f211e0>, '_A__foo': <function A.__foo at 0x101f21378>, 'bar': <function A.bar at 0x101f212f0>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
  20.  
  21. a = A('egon')
  22. a._A__foo() # 通过这种方式可以访问类隐藏函数
  23. # 输出:egon foo run
  24. a.bar()
  25. """
  26. egon foo run
  27. from bar
  28. """

  可以看到类的属性和函数在前面加'__',在类定义阶段就发生了变形,变形后在外部就无法通过.__x或.__func来调用。

1、自动变形的特点

  1)类中定义的__x只能在内部使用,如self.__x,引用的就是变形的结果。

  2)这种变形其实正是针对外部的变形,在外部是无法通过__x这个名字访问到的。

  3)在子类定义的__x不会覆盖在父类定义的__x,因为子类中变形成了:_子类名__x,而父类中变形成了:_父类名__x,即双下滑线开头的属性在继承给子类时,子类是无法覆盖的。

  1. class Foo:
  2. def __func(self): # _Foo_func
  3. print('from foo')
  4.  
  5. class Bar(Foo):
  6. def __func(self): # _Bar__func
  7. print('from bar')
  8.  
  9. b = Bar()
  10. # b.func() # AttributeError:没有这个属性
  11. b._Bar__func() # 输出:from bar

2、变形需要注意的问题

  1)知道了类名和属性名就可以拼出名字:_类名__属性,然后就可以访问了

  1. class B:
  2. __x = 1
  3.  
  4. def __init__(self, name):
  5. self.__name = name
  6.  
  7. print(B._B__x)
  8. """
  9. 1
  10. """

  2)变形的过程只在类的定义时发生一次,定义后的赋值操作,不会变形

  1. >>> class A:
  2. ... def __init__(self):
  3. ... self.__X=10
  4. ...
  5. >>> a=A()
  6. >>> a.__dict__
  7. {'_A__X': 10}
  8. >>> a.__Y=2131
  9. >>> a.__dict__
  10. {'_A__X': 10, '__Y': 2131} # __Y没有变形

  3)在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有的

  1. class A:
  2. def __foo(self): # _A__foo
  3. print('A foo')
  4.  
  5. def bar(self):
  6. print('A.bar')
  7. self.__foo() # self._A__foo()
  8.  
  9. class B(A):
  10. def __foo(self): # _B__foo
  11. print('B.foo')
  12.  
  13. b = B()
  14. b.bar()
  15. """
  16. A.bar
  17. A foo # 只在自己类找方法不去其他类查找,子类不覆盖父类方法
  18. """
  1. #正常情况
  2. >>> class A:
  3. ... def fa(self):
  4. ... print('from A')
  5. ... def test(self):
  6. ... self.fa()
  7. ...
  8. >>> class B(A):
  9. ... def fa(self):
  10. ... print('from B')
  11. ...
  12. >>> b=B()
  13. >>> b.test()
  14. from B
  15.  
  16. #把fa定义成私有的,即__fa
  17. >>> class A:
  18. ... def __fa(self): #在定义时就变形为_A__fa
  19. ... print('from A')
  20. ... def test(self):
  21. ... self.__fa() #只会与自己所在的类为准,即调用_A__fa
  22. ...
  23. >>> class B(A):
  24. ... def __fa(self):
  25. ... print('from B')
  26. ...
  27. >>> b=B()
  28. >>> b.test()
  29. from A

方法私有的情况

三、封装的意义

  封装不是单纯意义的隐藏

1、封装数据属性

  将数据隐藏起来这不是目的。隐藏起来然后对外提供操作该数据的接口,然后我们可以在接口附加上对该数据操作的限制,以此完成对数据属性操作的严格控制。

提示:在编程语言里,对外提供的接口(接口可理解为了一个入口),可以是函数,称为接口函数,这与接口的概念还不一样,接口代表一组接口函数的集合体。

  1. class People:
  2. def __init__(self, name, age):
  3. self.__name = name
  4. self.__age = age
  5.  
  6. def tell_info(self):
  7. print('Name:<%s> Age:<%s>' % (self.__name, self.__age))
  8.  
  9. def set_info(self, name, age):
  10. if not isinstance(name, str):
  11. print('名字必须是字符串类型')
  12. return
  13. if not isinstance(age, int):
  14. print('年龄必须是数字类型')
  15. return
  16. self.__name = name
  17. self.__age = age
  18.  
  19. p = People('egon', 18)
  20.  
  21. # p.tell_info()
  22. """
  23. Name:<egon> Age:<18> # 封装数据,开放接口给外部访问
  24. """
  25.  
  26. # p.set_info('Egon', 38) # 修改数据只能通过接口来完成,可以通过接口完成各种限制
  27. # p.tell_info()
  28. """
  29. Name:<Egon> Age:<38>
  30. """
  31.  
  32. # p.set_info(123, 38)
  33. """
  34. 名字必须是字符串类型
  35. """
  36. p.set_info('egon', '')
  37. p.tell_info()
  38. """
  39. 年龄必须是数字类型
  40. Name:<egon> Age:<18>
  41. """

封装数据属性

2、封装方法

  隔离复杂度。

  1. class ATM:
  2. def __card(self):
  3. print('插卡')
  4. def __auth(self):
  5. print('输入取款金额')
  6. def __input(self):
  7. print('输入取款金额')
  8. def __print_bill(self):
  9. print('打印账单')
  10. def __take_money(self):
  11. print('取款')
  12.  
  13. def withdraw(self):
  14. self.__card()
  15. self.__auth()
  16. self.__input()
  17. self.__print_bill()
  18. self.__take_money()
  19.  
  20. a = ATM()
  21. a.withdraw()
  22. """
  23. 插卡
  24. 输入取款金额
  25. 输入取款金额
  26. 打印账单
  27. 取款
  28. """

封装方法

  由上例可以看到,取款是功能,而这个功能有很多功能组成:插卡、密码认证、输入金额、打印账单、取钱;对使用者来说,只需要知道取款这个功能即可,其余功能我们都可以隐藏起来,很明显这么做隔离了复杂度,同时也提升了安全性

四、封装和扩展性

  封装在于明确区分内外,使得类实现者可以修改封装内的东西而不影响外部调用者的代码;而外部使用用者只知道一个接口(函数),只要接口(函数)名、参数不变,使用者的代码永远无需改变。

  1. class Room:
  2. def __init__(self, name, owner, weight, length, height):
  3. self.name = name
  4. self.owner = owner
  5.  
  6. self.__weight = weight
  7. self.__length = length
  8. self.__height = height
  9.  
  10. def tell_area(self):
  11. return self.__weight * self.__length
  12.  
  13. r = Room('卫生间', 'alex', 10, 10, 10)
  14.  
  15. print(r.tell_area()) # 不管是求面积还是体积,用户调用的方式不变

  由上例可以看出,只要接口这个基础约定不变,就不用担心代码的改动。

  1. #类的设计者
  2. class Room:
  3. def __init__(self,name,owner,width,length,high):
  4. self.name=name
  5. self.owner=owner
  6. self.__width=width
  7. self.__length=length
  8. self.__high=high
  9. def tell_area(self): #对外提供的接口,隐藏了内部的实现细节,此时我们想求的是面积
  10. return self.__width * self.__length
  11.  
  12. #使用者
  13. >>> r1=Room('卧室','egon',20,20,20)
  14. >>> r1.tell_area() #使用者调用接口tell_area
  15.  
  16. #类的设计者,轻松的扩展了功能,而类的使用者完全不需要改变自己的代码
  17. class Room:
  18. def __init__(self,name,owner,width,length,high):
  19. self.name=name
  20. self.owner=owner
  21. self.__width=width
  22. self.__length=length
  23. self.__high=high
  24. def tell_area(self): #对外提供的接口,隐藏内部实现,此时我们想求的是体积,内部逻辑变了,只需求修该下列一行就可以很简答的实现,而且外部调用感知不到,仍然使用该方法,但是功能已经变了
  25. return self.__width * self.__length * self.__high
  26.  
  27. #对于仍然在使用tell_area接口的人来说,根本无需改动自己的代码,就可以用上新功能
  28. >>> r1.tell_area()

封装与扩展

五、特性(property)

1、property概念

  property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值。

2、计算BMI指数示例

  下面以计算BMI指数为例:(bmi是计算而来的,但很明显它听起来像是一个属性而非方法,如果我们将其做成一个属性,更便于理解)

  BMI指数:(BMI是计算而来的,很明显它听起来像一个属性而非方法,如果我们将其作为一个属性,更便于理解)
  成人的BMI数值:
  过轻:低于18.5
  正常:18.5-23.9
  过重:24-27
  肥胖:28-32
  非常肥胖:高于32
  体质指数(BMI)= 体重(KG)/ 身高^2(M)
  EX:70KG / (1.75*1.75) = 22.86

(1)普通解决办法

  1. class People:
  2. def __init__(self, name, weight, height):
  3. self.name = name
  4. self.weight = weight
  5. self.height = height
  6.  
  7. p = People('jack', 48, 1.65)
  8. p.bmi = p.weight / (p.height ** 2)
  9.  
  10. print(p.bmi)
  11. """
  12. 17.63085399449036
  13. egon
  14. dragon
  15. 名字必须是字符串类型
  16. dragon
  17. 不允许删除
  18. """

(2)添加函数改写的方法

  1. class People:
  2. def __init__(self, name, weight, height):
  3. self.name = name
  4. self.weight = weight
  5. self.height = height
  6.  
  7. def bmi(self):
  8. print('===>')
  9. return self.weight / (self.height ** 2)
  10.  
  11. p = People('SH', 53, 1.70)
  12. print(p.bmi()) # bmi是一个名词,却使用bmi(),容易误解为一个动作

(3)添加property装饰器的方法

  1. class People:
  2. def __init__(self, name, weight, height):
  3. self.name = name
  4. self.weight = weight
  5. self.height = height
  6.  
  7. @property # 应用场景:有一个值是通过计算得来的,首选定义方法,运用property让使用者感知不到
  8. def bmi(self):
  9. print('===>')
  10. return self.weight / (self.height ** 2) # 必须有返回值
  11.  
  12. p = People('SH', 53, 1.70)
  13. print(p.bmi) # 使用者像访问数据属性一样访问bmi,方法被伪装
  14. """
  15. ===>
  16. 18.339100346020764
  17. """
  18. p.height = 1.82
  19. print(p.bmi)
  20. """
  21. ===>
  22. 16.000483033450067
  23. """
  24. p.bmi = 333 # 报错,看起来像数据属性,其实还是一个方法,不能赋值

3、property好处

  将一个类的函数定义成特性以后,对象再去使用的时候obj.name,根本无法察觉自己的name是执行了一个函数然后计算出来的,这种特性的使用方式遵循了统一访问的原则。总结来说:把一些计算得到的属性伪装得像数据属性一样被用户访问。

六、property其他用法(set\get\delete方法)

  在C++里一般会将所有的所有的数据都设置为私有的,然后提供set和get方法(接口)。Python中通过property来实现这个功能:

  1. class People:
  2. def __init__(self, name):
  3. self.__name = name
  4.  
  5. def get_name(self):
  6. return self.__name
  7.  
  8. p = People('egon')
  9. print(p.get_name()) # 输出:egon

  在上面的代码中,People的name属性被封装,不能直接访问,因此给类添加了一个get_name()方法来查看内部已经封装的属性。

  但是这种这种情况下,用户的属性调用方式发生了改变。可以通过@property解决该问题。

  1. class People:
  2. def __init__(self, name):
  3. self.__name = name
  4.  
  5. @property
  6. def name(self):
  7. return self.__name
  8.  
  9. p = People('egon')
  10. print(p.name) # 输出:egon

  调用方式已经和普通属性相同,但是在上面bmi的代码中可以看到,这种情况下是不能对隐藏属性赋值的。要实现赋值,需要运用@name.setter装饰器(name被property装饰后才可用)进行如下修改:

  1. class People:
  2. def __init__(self, name):
  3. self.__name = name
  4.  
  5. @property
  6. def name(self):
  7. # print('getter')
  8. return self.__name
  9.  
  10. @name.setter
  11. def name(self, val):
  12. # print('setter', val)
  13. if not isinstance(val, str):
  14. print('名字必须是字符串类型')
  15. return
  16. self.__name=val
  17.  
  18. @name.deleter
  19. def name(self):
  20. # print('deleter')
  21. print('不允许删除')
  22.  
  23. p = People('egon')
  24. print(p.name) # 输出:egon
  25. p.name = 'dragon'
  26. print(p.name) # 输出:dragon # name修改成功
  27.  
  28. p.name = 123 # 输出:名字必须是字符串类型(报错)
  29. print(p.name) # 输出:dragon
  30.  
  31. del p.name # 输出:不允许删除(报错)

  如代码所示,实现了name属性的修改和删除,@name.deleter和@name.setter都是基于name被@property装饰才可用的。

面向对象三大特性——封装(含property)的更多相关文章

  1. [.net 面向对象编程基础] (11) 面向对象三大特性——封装

    [.net 面向对象编程基础] (11) 面向对象三大特性——封装 我们的课题是面向对象编程,前面主要介绍了面向对象的基础知识,而从这里开始才是面向对象的核心部分,即 面向对象的三大特性:封装.继承. ...

  2. python 面向对象三大特性(封装 多态 继承)

    今天我们来学习一种新的编程方式:面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)注:Java和C#来说只支持面向对象编程,而python比较灵活即支持面 ...

  3. 深入理解Java面向对象三大特性 封装 继承 多态

    1.封装 封装的定义: 首先是抽象,把事物抽象成一个类,其次才是封装,将事物拥有的属性和动作隐藏起来,只保留特定的方法与外界联系 为什么需要封装: 封装符合面向对象设计原则的第一条:单一性原则,一个类 ...

  4. Python()- 面向对象三大特性----封装

    封装: [封装]         隐藏对象的属性和实现细节,仅对外提供公共访问方式.[好处] 1. 将变化隔离: 2. 便于使用:3. 提高复用性: 4. 提高安全性:[封装原则]      1. 将 ...

  5. day36 类的三大特性---封装以及Property特性

    目录 类的封装 如果真的要拿 类的property特性 setter & deleter 类属性用法 类与对象的绑定方法和非绑定方法 对象方法&类方法&静态方法 隐藏模块内的函 ...

  6. Python入门-面向对象三大特性-封装

    一.封装 封装,顾名思义就是将内容封装到某个地方,以后再去调用被封装在某处的内容. 所以,在使用面向对象的封装特性时,需要: 将内容封装到某处 从某处调用被封装的内容 第一步:将内容封装到某处 sel ...

  7. Python面向对象三大特性(封装、继承、多态)

    封装 类中把某些属性和方法隐藏起来,或者定义为私有,只在类的内部使用,在类的外部无法访问,或者留下少量的接口(函数)供外部访问:从上一篇文章中的私有属性与私有方法中的代码体现了该特性. class m ...

  8. php部分--面向对象三大特性-封装(另加连续调用的一个例子)、继承(重写、重载的例子)、多态;

    一.封装性: 目的:为了使类更加安全. 做法:1设置私有成员 2在类中建方法,访问私有成员 3在方法里边加控制(if) 私有成员访问的两种方法: 方法一:set(可写) get(可读)做方法(可读可写 ...

  9. JAVA基础——面向对象三大特性:封装、继承、多态

    JAVA面向对象三大特性详解 一.封装 1.概念: 将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问. 2.好处: 只能通过规定的方法访问数据. ...

随机推荐

  1. Effective Java 3rd.Edition 翻译

    推荐序 前言 致谢 第一章 引言 第二章 创建和销毁对象 第1项:用静态工厂方法代替构造器 第2项:遇到多个构造器参数时要考虑使用构建器 第3项:用私有构造器或者枚举类型强化Singleton属性 第 ...

  2. 通用动态树(Link-Cut Tree)模板

    一个没有维护任何东西的动态树模板 忘了怎么写可以直接来粘 int ch[300010][2], fa[300010], st[300010]; bool lazy[300010]; bool nroo ...

  3. C++_基础4-分支语句和逻辑运算符

    这一部分截取自<C++ Primer Plus>,内容比较简单,很多只取了一些主题关键词,有空再补充: 设计智能程序的一个关键是使程序具有决策能力. 前面一种方式是循环——程序决定是否继续 ...

  4. C++_基础1-基本数据类型

    面向对象(OOP)的本质是设计并扩展自己的数据类型.设计自己的数据类型就是让类型与数据匹配. 如果正确做到这一点,就会发现以后使用数据会容易很多.然而创建自己的类型之前,必须了解并理解C++内置类型. ...

  5. matlab中的linkage和cluster函数

    Linkage: Agglomerative hierarchical cluster tree(凝聚成层次聚类树) 语法: 解释: Z=linkage(x),返回Z,是一个X矩阵中行的分层聚类树(用 ...

  6. Angular 组件 mat-paginator 自定义详细用法

    Demo: https://stackblitz.com/edit/angular-5mgfxh?file=main.ts 官方文档: https://material.angular.io/comp ...

  7. RESTful和SOAP的区别

    参考:[接口开发]浅谈 SOAP Webserver 与 Restful Webserver 区别 目录 一.Web Service 二.SOAP 三.REST 四.RPC 客户端和服务器端的通讯方式 ...

  8. API 接口设计工具 --Swagger

      swagger-editor,无法启动GUI软件,在线版的FQ也打不开   null

  9. 多重if 与 switch case的区别

    多重if:可以做等值操作也可以做区间操作 switch case:只能做等值操作

  10. 2019.03.27 读书笔记 关于GC垃圾回收

    在介绍GC前,有必要对.net中CLR管理内存区域做简要介绍: 1. 堆栈:用于分配值类型实例.堆栈主要操作系统管理,而不受垃圾收集器的控制,当值类型实例所在方法结束时,其存储单位自动释放.栈的执行效 ...