首页 > 技术文章 > Python类的私有化属性与私有方法使用

dy99 2021-05-19 16:53 原文

   Python默认的成员方法和成员属性都是公开的,没有类似Java的public,private,protected等关键词来修饰。 在python中定义私有变量只需要在变量名或函数名前加上 "__"两个下划线,那么这个函数或变量就变成私有(方法也是一样,方法名前面加了2个下划线的话表示该方法是私有的,否则为公有的)。

1. 类的变量类型

  xx: 函数外为公有变量,函数内为局部变量
  _x: 单前置下划线,protected类型的变量,不能用于’from module import *,即保护类型只能允许其本身与子类进行访问。并未完全私有,只是表明不希望用户直接访问属性,实际上,实例._变量,可以被访问。
  _xx:双前置下划线,private类型的私有变量,无法在外部直接访问(名字重写所以访问不到),但可以在类内部使用私有方法。本质是因为 python 编译器做了一次名称变换,
  xx: 双前后下划线,用户名字空间的魔法对象或属性。例如:__init__、__new__ , __ 不要自己发明这样的名字
  xx: 单后置下划线,用于避免与Python关键词的冲突

2. 引入私有化场景

  属性赋值方法:在绑定属性时,如果我们直接把属性暴露出去,会出现属性随便更改的情况。
police = Role('Alex', 'police', 'AK47')  # 实例化一个游戏人物
police.life_value = 0   # 生命值置为0
 
  上面的情况很明显不符合实际,我们实例化出来的游戏对象还没开始,直接通过外部方法把生命值置为0,接下来game over!很明显是作弊,因此我们引入私有化的概念,无论是属性还是方法,在定义前加双下划线__,就可使得属性或方法只能在类内部通过某些特定的场景调用。
在下面的代码中,访问police.__life_value会提示"AttributeError: 'Role' object has no attribute '__life_value"。
class Role(object):
    def __init__(self, name, role, weapon, life_value=100, money=15000):
        self.name = name
        self._role = role   # 这是一个protected类型的属性
        self.weapon = weapon
        self.__life_value = life_value   # 这是一个私有类型的属性
        self.money = money

    def __shot(self):
        # 开了枪后要减子弹数
        print("shooting...")

    def got_shot(self):
        # 中枪后要减血
        self.__life_value -= 10
        print(f"ah...,I got shot...,剩余生命值为{self.__life_value}")

    def buy_gun(self, gun_name, gun_money):
        # 检查钱够不够,买了枪后要扣钱
        if self.money > gun_money:
            self.money -= gun_money
            print("just bought %s" % gun_name)
        else:
            print("余额不足,无法支付买枪费用")


police = Role('Alex', 'police', 'AK47')   # 生成一个角色
print(police.__life_value)
 
  无法检查参数范围:设置的属性不符合实际。直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改。
zhangsan = Student("DY","100")
zhangsan.score = 99999
 
实际开发中为了程序的安全,关于类的属性都会封装起来,Python中为了更好的保存属性安全,不能随意修改。一般属性的处理方式为:
    ①将属性定义为私有属性。
  ②提供一个setter或getter方法
为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:
class Student(object):

    def set_score(self, value):
        if not isinstance(value, int):
            raise ValueError('score must be an integer!')
        if value < 0 or value > 100:
            raise ValueError('score must between 0 ~ 100!')
        self._score = value
        
    def get_score(self):
         return self._score
    
 if __name__ == '__main__':
    s = Student()
 
私有化的使用场景:
  1. 属性不允许在外部做更改
  2. 方法在某种特定场景下才可以执行
  3. 检查参数范围:外部设置超出范围时校验

3. 私有化封装底层实现原理

  __shot方法加上双下划线后为什么对象外部访问提示“AttributeError: 'Role' object has no attribute '__shot'”。
事实上,对于以双下划线开头命名的类属性或类方法,Python 在底层实现时,将它们的名称都偷偷改成了 "_类名__属性(方法)名" 的格式。
所以__shot重写为:_Role__shot。也因此访问原来的方法名会提示类没有__shot属性,人家已经改名字了,当然访问不到了。
# 使用 dir函数看看实例对象police里面有哪些内容,如下图所示:
['_Role__life_value', '_Role__shot', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_role', 'buy_gun', 'got_shot', 'money', 'name', 'weapon']

#或可使用第二种方法
import inspect
# 使用inspect检查现场对象
methods = inspect.getmembers(police, predicate=inspect.ismethod)
print(methods)

执行结果:
[('_Role__shot', <bound method Role.__shot of <__main__.Role object at 0x000001D3F6FFCE48>>), ('__init__', <bound method Role.__init__ of <__main__.Role object at 0x000001D3F6FFCE48>>), ('buy_gun', <bound method Role.buy_gun of <__main__.Role object at 0x000001D3F6FFCE48>>), ('got_shot', <bound method Role.got_shot of <__main__.Role object at 0x000001D3F6FFCE48>>)]
 
私有化属性或方法有方式可以访问吗?

4. 访问私有化属性/方法

  上面有说属性前加双下划线无法访问的原因,是因为在Python编译器重写变量名/方法名,所以导致访问不到。
在规范上,这种双下划线的私有方法和私有属性是不应该在外部访问的,但是如果就是想强行访问还是有方法的:直接访问重写名称后的属性名或方法名。
police._Role__shot()  # 强行调用私有方法
print(police._Role__life_value)  # 强行调用私有属性
执行结果:
shooting... 
100

 

推荐阅读