27.5-访问控制-猴子补丁-属性装饰器-对象销毁

推诿,是将成长的机会让给别人,担责,是将成长的体验留给自己。

当你开始成长的时候,这便是让别人对你放心的开始。

参考:
python面试题精讲——monkey patch(猴子补丁)

总结:

  1. 单下划线的方法只是开发者之间的约定,解释器不做任何改变。
    双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同类名_方法名 。
    方法变量都在类的 dict 中可以找到。
  1. 私有成员 的 总结:在不明白私有变量的意义之前,都不可以随便修改;
  2. 所有的类属性都可以修改;
  3. getter这个必须有,有了它至少是只读属性;
  4. 理论意义不大,多练才行;

在 Java 中,有 public (公共)属性 和 private (私有)属性,这可以对属性进行访问控制。

那么在 Python 中有没有属性的访问控制呢?

1. 访问控制

本来是想通过方法控制属性,但是由于属性在外部可以访问,或者说可见,就可以直接绕过方法,直接修改
这个属性。
Python提供了私有属性可以解决这个问题。

私有属性:使用双下划线开头的属性名,就是私有属性;

通过一个函数 growup 约束和调整 类属性 在一个合理的范围之内;

class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.age = age
        
    def growup(self,incr=1):
        if incr > 0 and incr < 100:
            self.age += incr
                    
tom = Person('tom')
tom.growup(20)   # 约束函数;

#tom.age = 2000   # 挡不住外部 访问属性
print(tom.age)
#---------------------------------------------------------------------------
38

私有变量的本质:

  1. 类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器会将其改名,转换名称为 类名_变量名 的名称,所以用原来的名字访问不到了。
  2. 私有属性也可以改: 知道了私有变量的新名称,就可以直接从外部访问到,并可以修改它, '_Person__age': 19, '__age': 1000;
class Person:
    def __init__(self,name,age=18):
        self.name = name
        self.__age = age   # 隐藏属性;
        
    def getage(self):   # 通过访问方法 来访问
        return self.__age
    
    def growup(self,incr=1):
        self.__age += incr
        return self.__age
    
tom = Person('tom')
#print(tom.age)   # 访问不到属性
#print(tom.__age)  # 也访问不到私有属性

print(tom.getage())   # 通过访问方法 来访问
tom.growup()
print(tom.getage())
tom.__age = 1000   # 动态增加了一个属性;
print(tom.getage())
print(tom.__age)   # 增加的属性;

print(tom.__dict__)
tom._Person__age = 30000  # 直接修改私有变量;
print(tom.getage())
#----------------------------------------------------------------------------------------------
18
19
19
1000
{'name': 'tom', '_Person__age': 19, '__age': 1000}
30000

保护变量

保护变量:在变量名前使用一个下划线,称为保护变量;

  1. 这个保护变量_age属性根本就没有改变名称,和普通的 属性height 没什么区别,解释器不做任何特殊处理。
  2. 这只是开发者共同的约定,看见这种变量,就如同私有变量,不要直接使用
class Person:
    def __init__(self,name,age=18):
        self._name = name   # 单下滑线开头的 叫保护变量;
        self.__age = age   # 隐藏属性;
        self.height = 170   
 
        
    def getage(self):   # 通过访问方法 来访问
        return self.__age
    
    def growup(self,incr=1):
        self.__age += incr
        return self.__age
    
    def getname(self):
        return self._name
    
tom = Person('tom')
print(tom.getname())
tom._name = 'jerry'
print(tom.getname())
print(tom.__dict__)
#----------------------------------------------------------------------------
tom
jerry
{'_name': 'jerry', '_Person__age': 18}

私有方法

参照保护变量、私有变量,使用单下划线、双下划线命名方法。

私有方法的本质

单下划线的方法只是开发者之间的约定,解释器不做任何改变。
双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同, ****类名****_****方法名
方法变量都在类的 dict 中可以找到。

私有成员的总结

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

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

2. 猴子补丁

Python中猴子补丁是什么?

答:在Ruby、Python等动态编程语言中,猴子补丁仅指在运行时动态改变类或模块,为的是将第三方代码打补丁在不按预期运行的bug或者feature上 。在运行时动态修改模块、类或函数,通常是添加功能或修正缺陷。猴子补丁在代码运行时内存中发挥作用,不会修改源码,因此只对当前运行的程序实例有效。因为猴子补丁破坏了封装,而且容易导致程序与补丁代码的实现细节紧密耦合,所以被视为临时的变通方案,不是集成代码的推荐方式。

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

猴子补丁(Monkey Patch):
在运行时,对类的属性、方法、函数等进行动态替换。
其目的往往是为了通过替换、修改来增强、扩展原有代码的能力。
黑魔法,慎用;

例中,假设Person类get_score方法是从数据库拿数据,但是测试的时候,不方便。
为了测试时方便,使用猴子补丁,替换了get_score方法,返回模拟的数据

将类外面的普通方法依然可以在程序运行的时候动态赋值给类的某一个方法;

# t1
class Person:
    def get_score(self):
        return {'English':80,'Chinese':88,'History':90}

# t2
# 补丁函数;
def get_score(a): # self
    return {'name':a.__class__.__name__,'Chinese':100,'English':99}

from t1 import Person
from t2 import get_score

def monkeypath4Person():
    Person.get_score = get_score

monkeypath4Person()
tom = Person()
print(tom.get_score())
#-----------------------------------------------
{'name': 'Person', 'Chinese': 100, 'English': 99}

3. 属性装饰器(背)

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

# 属性装饰器
class Person:
    def __init__(self, name, age=18):
        self._name = name
        self.__age = age
    @property   # getter   属性读取访问器 # age = 
    def age(self):
        return self.__age
    
#     def get_age(self):   # 等价于上面的属性装饰器;
#         return self.__age
    @age.setter   #属性写入访问器
    def age(self,value):
        self.__age =  value
        
    @age.deleter   # 删除属性
    def age(self):
        #del self.__age
        pass
        
tom = Person('tom',18)
#print(tom._name,tom.get_age)
print(tom._name,tom.age)
#------------------------------------------
tom    18

特别注意:使用property装饰器的时候这三个方法同名
property装饰器
后面跟的函数名就是以后的属性名。它就是getter。这个必须有,有了它至少是只读属性

setter装饰器
与属性名同名,且接收2个参数,第一个是self,第二个是将要赋值的值。有了它,属性可写

deleter装饰器
可以控制是否删除属性。很少用
property装饰器必须在前,setter、deleter装饰器在后。
property装饰器能通过简单的方式,把对方法的操作变成对属性的访问,并起到了一定隐藏效果

这两种方式大多被遗忘了;

另一种写法1:
class Person:
    def __init__(self, name, age=18):
        self.name = name
        self.__age = age
    def getage(self):
        return self.__age
    
    def setage(self, age):
        self.__age = age
        
    def delage(self):
        # del self.__age
        print('del')
    age = property(getage, setage, delage, 'age property')
    
tom = Person('Tom')
print(tom.age)
tom.age = 20
print(tom.age)
del tom.age
#----------------------------------------
18
20
del

# 另一种写法2
class Person:
    def __init__(self,name,age):
        self._name = name
        self.__age = age
    age = property(lambda self:self.__age)
tom = Person('Tom',18)
print(tom.age)
#---------------------------------------------
18

4. 对象的销毁()

理解什么时候去执行;

类中可以定义_del_ 方法,称为析构函数(方法)
作用:销毁类的实例的时候调用,以释放占用的资源。其中就放些清理资源的代码,比如释放连接。
注意这个方法不能引起对象的真正销毁,只是对象销毁的时候会自动调用它。
使用del语句删除实例,引用计数减1。当引用计数为0时,会自动调用 del 方法。
注意:当Python解释器退出但对象仍然存活的时候,__del __并不会执行。

场景:关闭socket对象、文件对象。

由于Python实现了垃圾回收机制,不能确定对象何时执行垃圾回收。

class Person:
    def __init__(self, name, age):  # 初始化
        self._name = name
        self.__age = age

    def __del__(self):  # 销毁函数

        print('del')


tom = Person('tom', 18)  # 实例化
tom.__del__()
tom.__del__()
tom.__del__()
tom.__del__()
tom.__del__()

print('================')
#--------------------------------------------------------
del
del
del
del
del
=================
del      #对象消亡,垃圾回收做的;


垃圾回收对象销毁时,才会真正清理对象,还会在回收对象之前自动调用 _del_ ;除非你明确知道自己的目的,建议不要手动调用这个方法;

class Person:
    def __init__(self, name, age):  # 初始化
        self._name = name
        self.__age = age

    def __del__(self):  # 销毁函数

        print('del')

tom = Person('tom', 18)  # 实例化
# tom.__del__()   # 没有必要手动调用;
# tom.__del__()
# tom.__del__()
# tom.__del__()
# tom.__del__()
print('================')
tom1 = tom
tom2 = tom1

del tom
print('-'*50)
import time
time.sleep(1)

del tom1
print('-'*50)
import time
time.sleep(1)

del tom2
print('-'*50)
import time
time.sleep(1)

print('+++++++++++++++++++++++++++++++++')
#--------------------------------------------------------------------------------------
================
--------------------------------------------------
--------------------------------------------------
del
--------------------------------------------------
+++++++++++++++++++++++++++++++++

5.方法重载(overload)

其他面向对象的高级语言中,会有重载的概念。
重载,就是同一个方法名,但是参数数量、类型不一样,就是同一个方法的重载

Python没有重载! 因为参数不需要指定类型
Python不需要重载!
Python中,方法(函数)定义中,形参非常灵活,不需要指定类型(就算指定了也只是一个说明而非约束),参数个数也不固定(可变参数)。一个函数的定义可以实现很多种不同形式实参的调用。所以Python不需要方法的重载。或者说Python本身就实现了其它语言的重载。

6.封装

面向对象的三要素之一,封装Encapsulation

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

练习

1、随机整数生成类
可以指定一批生成的个数,可以指定数值的范围,可以调整每批生成数字的个数;


# 1.常规思路;实例方法
class RandomNum:
    def __init__(self,start=1,stop=100,patch=10):  # 配置基本参数;
        self.start = start
        self.stop = stop
        self.patch = patch
    
    def generate(self):  # 生成函数;
        import random
        return [random.randint(self.start,self.stop) for _ in range(self.patch)]
    
rn = RandomNum()
print(rn.generate())  # 生成随机数;
#---------------------------------------------------------------
[85, 64, 74, 45, 99, 66, 52, 29, 84, 25]


# 类方法实现; 常规管理;
class RandomNum:
    @classmethod
    def generate(cls,start=1,end=100,num=10):
        import random
        return [random.randint(start,end) for _ in range(num)]
print(RandomNum.generate())



2、打印坐标
使用上题中的类,随机生成20个数字,两两配对形成二维坐标系的坐标,把这些坐标组织起来,并打印输出;




3、车辆信息
记录车的品牌mark、颜色color、价格price、速度speed等特征,并实现增加车辆信息、显示全部车辆信息的功能


4、实现温度的处理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,384评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,845评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,148评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,640评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,731评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,712评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,703评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,473评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,915评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,227评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,384评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,063评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,706评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,302评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,531评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,321评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,248评论 2 352

推荐阅读更多精彩内容

  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,739评论 0 10
  • 这是16年5月份编辑的一份比较杂乱适合自己观看的学习记录文档,今天18年5月份再次想写文章,发现简书还为我保存起的...
    Jenaral阅读 2,746评论 2 9
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • 面向对象 一种认识世界,分析世界的方法论。将万事万物抽象为类 类class 类是抽象的概念,是万事万物的抽象,是一...
    vampire6阅读 436评论 0 0
  • 我和妈妈去游乐场了,在那里,我玩了滑梯和蹦蹦床,还有海盗船,我玩的可开心了
    韩策阅读 109评论 0 0