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