Python面向对象三要素-封装(Encapsulation)

            Python面向对象三要素-封装(Encapsulation)

                                      作者:尹正杰

版权声明:原创作品,谢绝转载!否则将追究法律责任。

一.封装概述

  将数据和操作组织到类中,即属性和方法 
  
  将数据隐藏起来,给使用者提供操作(方法)。使用者通过操作就可以获取或者修改数据。getter和setter。 
  
  通过访问控制,暴露适当的数据和操作给用户,该隐藏的隐藏起来,例如保护成员或私有成员。

二.类属性的访问控制 

1>.抛出问题 

 1 #!/usr/bin/env python
 2 #_*_conding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie
 5 
 6 
 7 class Person:
 8     def __init__(self,name,age=18):
 9         self.name = name
10         self.age = age
11 
12     def growup(self,i=1):
13          if i > 0 and i < 150:  #控制逻辑
14              self.age += i
15 
16 
17 p1 = Person("jason")
18 print(p1.age)
19 
20 p1.growup(20)           #在我们控制逻辑的范围内
21 print(p1.age)
22 
23 p1.age = 9999           #直接修改对象的属性,超过了范围,并绕过了咱们控制逻辑,是不是很操蛋?Python提供了私有属性可以解决这个问题。
24 print(p1.age)
25 
26 
27 
28 #以上代码输出结果如下:
29 18
30 38
31 9999

2>.私有(Private)属性

 1 #!/usr/bin/env python
 2 #_*_conding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie
 5 
 6 
 7 class Person:
 8     def __init__(self,name,age=18):
 9         self.name = name
10         """
11             私有变量的本质: 类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器会将其改名,转换名称为"_类名__变量名"的名称,所以用原来的名字访问不到了。
12         """
13         self.__age = age            #使用双下划线开头的属性名,就是私有属性
14 
15     def growup(self,i=1):
16          if i > 0 and i < 150:      #控制逻辑
17              self.__age += i
18 
19     def getage(self):               #我们只对外提供访问"__age"的方法
20         return self.__age
21 
22 p1 = Person("jason")
23 print(p1.getage())
24 print(p1.__dict__)
25 
26 p1.growup(120)
27 print(p1.getage())
28 
29 print(Person.__dict__)
30 print(p1.__dict__)
31 
32 #p1.__age == 9999                   #我们发现现在无法访问到"__age"这个属性啦,会抛出"AttributeError"异常
33 p1._Person__age = 9999              #既然我们知道了私有变量的新名称,就可以直接从外部访问到,并修改它。因此尽管是私有属性我们依旧是可以对其进行更改,但建议大家不要去修改,因为这样就违背了私有变量但属性啦,但你如果一定要改的话你得知道去哪改哟。
34 print(p1.getage())
35 print(p1.__dict__)
36 
37 
38 
39 #以上代码输出结果如下:
40 18
41 {'name': 'jason', '_Person__age': 18}
42 138
43 {'__module__': '__main__', '__init__': <function Person.__init__ at 0x10075f950>, 'growup': <function Person.growup at 0x10075fae8>, 'getage': <function Person.getage at 0x10075fb70>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
44 {'name': 'jason', '_Person__age': 138}
45 9999
46 {'name': 'jason', '_Person__age': 9999}

3>.保护(protected)属性

 1 #!/usr/bin/env python
 2 #_*_conding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie
 5 
 6 
 7 class Person:
 8     def __init__(self,name,age=18):
 9         self.name = name
10         """
11             在变量名前使用一个下划线,称为保护变量。
12             可以看出,这个_age属性根本就没有改变名称,和普通的属性一样,解释器不做任何特殊处理。 
13             这只是开发者共同的约定,看见这种变量,就如同私有变量,不要直接使用。
14         """
15         self._age = age
16 
17 
18 p1 = Person("jason")
19 
20 print(p1._age)
21 print(p1.__dict__)              #
22 
23 
24 
25 #以上代码输出结果如下:
26 18
27 {'name': 'jason', '_age': 18}

4>.私有方法

 1 #!/usr/bin/env python
 2 #_*_conding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie
 5 
 6 
 7 """
 8 私有方法的本质
 9     单下划线的方法只是开发者之间的约定,解释器不做任何改变。
10     双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同, 即"_类名__方法名" 。
11     方法变量都在类的 __dict__ 中可以找到。
12 """
13 class Person:
14     def __init__(self, name, age=18):
15         self.name = name
16         self._age = age
17 
18     """
19         参照保护变量、私有变量,使用单下划线、双下划线命名方法。
20     """
21     def _getname(self):
22         return self.name
23 
24     def __getage(self):
25         return self._age
26 
27 
28 jason = Person('Jason')
29 print(jason._getname())  # 没改名
30 #print(jason.__getage()) # 无此属性
31 print(jason.__dict__)
32 print(jason.__class__.__dict__)
33 print(jason._Person__getage()) # 改名了
34 
35 
36 
37 #以上代码执行结果如下:
38 Jason
39 {'name': 'Jason', '_age': 18}
40 {'__module__': '__main__', '__init__': <function Person.__init__ at 0x10215f950>, '_getname': <function Person._getname at 0x10215fae8>, '_Person__getage': <function Person.__getage at 0x10215fb70>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
41 18

5>.私有成员的总结

  在Python中使用 _单下划线 或者 __ 双下划线来标识一个成员被保护或者被私有化隐藏起来。 
 
  但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员。Python中没有绝对的安全的保护成员或者私有成员。 

  因此,前导的下划线只是一种警告或者提醒,请遵守这个约定。除非真有必要,不要修改或者使用保护成员或者私有成员,更不要修改它们。

三.补丁

  可以通过修改或者替换类的成员。使用者调用的方式没有改变,但是,类提供的功能可能已经改变了。

  猴子补丁(Monkey Patch):
    在运行时,对属性、方法、函数等进行动态替换。
    其目的往往是为了通过替换、修改来增强、扩展原有代码的能力。
    黑魔法,慎用。
 1 #!/usr/bin/env python
 2 #_*_conding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie
 5 
 6 class Person:
 7     def get_score(self):
 8         #模拟下面的字典是从数据库拿的某个学生成绩(基本是及格的不多呀)。
 9         ret = {"English":37,"Chinese":66,"History":52}
10         return ret
test2.py
1 #!/usr/bin/env python
2 #_*_conding:utf-8_*_
3 #@author :yinzhengjie
4 #blog:http://www.cnblogs.com/yinzhengjie
5 
6 
7 def get_score(self):
8     return dict(name=self.__class__.__name__,English=98, Chinese=96, History=95)
test3.py
 1 #!/usr/bin/env python
 2 #_*_conding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:http://www.cnblogs.com/yinzhengjie
 5 
 6 from test2 import Person
 7 from test3 import get_score
 8 
 9 def monkey_patch_for_Person():
10     Person.get_score = get_score
11 
12 monkey_patch_for_Person()       #打补丁操作
13 
14 if __name__ == '__main__':
15     print(Person().get_score())
16 
17 
18 
19 #以上代码输出结果如下:
20 {'name': 'Person', 'English': 98, 'Chinese': 96, 'History': 95}

四.属性装饰器

一般好的设计是:
  把实例的某些属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性。

1>.自定义getter和setter方法

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:https://www.cnblogs.com/yinzhengjie
 5 #EMAIL:y1053419035@qq.com
 6 
 7 class Person:
 8     def __init__(self,name,age=18):
 9         self.name = name
10         self.__age = age
11     
12     """
13         我们自定义age和set_age方法操作属性,的确可以实现私有变量的管理。那有没有简单的方式呢?
14     """
15     def age(self):
16         return self.__age
17 
18     def set_age(self,age):
19         self.__age = age
20 
21 
22 jason = Person("Jason")
23 print(jason.age())
24 
25 jason.set_age(20)
26 print(jason.age())
27 
28 
29 
30 #以上代码输出结果如下:
31 18
32 20

2>.property装饰器

 1 # !/usr/bin/env python
 2 # _*_coding:utf-8_*_
 3 # @author :yinzhengjie
 4 # blog:https://www.cnblogs.com/yinzhengjie
 5 # EMAIL:y1053419035@qq.com
 6 
 7 class Person:
 8     def __init__(self, name, age=18):
 9         self.name = name
10         self.__age = age
11 
12     """
13         特别注意:
14             使用property装饰器的时候这三个方法同名。
15             property装饰器必须在前,setter,deleter装饰器在后。
16             property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果。
17 
18         property装饰器:
19             后面跟的函数名是以后的属性名。它就是getter。这个必须有,有了它至少是只读属性。
20 
21         setter装饰器:
22             与属性名同名,且接收2个参数,第一个self,第二个是将要赋值的值。有了它,属性可写。
23 
24         deleter装饰器:
25             可以控制是否删除属性,很少用,咱们了解即可。
26     """
27 
28     @property
29     def age(self):
30         return self.__age
31 
32     @age.setter
33     def age(self, age):
34         self.__age = age
35 
36     @age.deleter
37     def age(self):
38         del self.__age
39         print("del")
40 
41 
42 jason = Person("Jason")
43 print(jason.age)
44 
45 jason.age += 10
46 print(jason.age)
47 
48 del jason.age
49 
50 # 以上代码输出结果如下:
51 18
52 28
53 del
 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:https://www.cnblogs.com/yinzhengjie
 5 #EMAIL:y1053419035@qq.com
 6 
 7 class Person:
 8     def __init__(self,name,age=18):
 9         self.name = name
10         self.__age = age
11 
12     def getage(self):
13         return self.__age
14 
15     def setage(self,age):
16         self.__age =age
17 
18     def delage(self):
19         del self.__age
20         print("del")
21 
22     age = property(getage,setage,delage,"age property")
23 
24 jason = Person("Jason")
25 print(jason.age)
26 
27 jason.age = 20
28 print(jason.age)
29 
30 del jason.age
31 
32 #以上代码输出结果如下:
33 18
34 20
35 del
property的另外一种写法(第二种写法)
 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:https://www.cnblogs.com/yinzhengjie
 5 #EMAIL:y1053419035@qq.com
 6 
 7 class Person:
 8     def __init__(self,name,age=18):
 9         self.name = name
10         self.__age = age
11 
12     age = property(lambda self:self.__age)          #这种写法只能设置只读属性,无法让属性可写。
13 
14 jason = Person("Jason")
15 print(jason.age)
16 
17 
18 #以上代码输出结果如下:
19 18
仅设置只读的装饰器

五.对象的销毁

 1 #!/usr/bin/env python
 2 #_*_coding:utf-8_*_
 3 #@author :yinzhengjie
 4 #blog:https://www.cnblogs.com/yinzhengjie
 5 #EMAIL:y1053419035@qq.com
 6 
 7 import time
 8 
 9 class Person:
10     def __init__(self,name,age=18):
11         self.name = name
12         self.__age = age
13 
14     """
15         类中可以定义"__del__"方法,称为析构函数(方法)。
16         作用:销毁类的实例的时候调用,以释放占用资源。其中就放些清理资源的代码,比如释放连接。
17         注意这个方法不能引起真正销毁,只是对象销毁的时候会自动调用它。
18         
19         由于Python实现了引用计数的垃圾回收机制,不能确定对象何时执行垃圾回收。
20         我们可以使用del语句删除实例,引用计数减1。当引用计数为0时,会自动调用"__del__"方法。
21         由于垃圾回收对象销毁时,才会真正清理对象,还会在回收对象之前自动调用"__del__"方法,除非你明确自己的目的,建议不要手动调用这个方法。
22     """
23     def __del__(self):
24         print("delete {}".format(self.name))
25 
26 
27 if __name__ == '__main__':
28     jason = Person("Jason")
29     jason.__del__()
30     jason.__del__()
31     jason.__del__()
32     jason.__del__()
33     print("=========start=========")
34     jason2  = jason
35     jason3 = jason2
36     print(1,"del")
37     del  jason
38     time.sleep(2)
39 
40     print(2,"del")
41     del jason2
42     time.sleep(2)
43     print("*" * 50)
44 
45     del jason3      #注释一下看看效果
46     time.sleep(3)
47     print("=========end========")
48     
49     
50     
51 #以上代码执行结果如下:
52 delete Jason
53 delete Jason
54 delete Jason
55 delete Jason
56 =========start=========
57 1 del
58 2 del
59 **************************************************
60 delete Jason
61 =========end========

六.方法重载(overload)  

  其他面向对象的高级语言中,会有重载的概念。

  所谓重载,就是同一个方法名,但是形式参数个数、类型不一样,就是同一个方法的重载。
  Python没有重载!
  Python不需要重载! Python中,方法(函数)定义中,形参非常灵活,不需要指定类型(就算指定了也只是一个说明而非约束),参数个数也不固定(可变参数)。一个函数的定义可以实现很多种不同形式实参的调用。所以Python不需要方法的重载。
  或者说Python语法本身就实现了其它语言的重载。
原文地址:https://www.cnblogs.com/yinzhengjie/p/11161519.html