Python全栈工程师

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

2019-12-10  本文已影响0人  BeautifulSoulpy

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

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

参考:
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、实现温度的处理

上一篇下一篇

猜你喜欢

热点阅读