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
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)
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
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,个人微信: "JasonYin2020"(添加时请备注来源及意图备注,有偿付费)
当你的才华还撑不起你的野心的时候,你就应该静下心来学习。当你的能力还驾驭不了你的目标的时候,你就应该沉下心来历练。问问自己,想要怎样的人生。