最近在写一个功能,要在paramiko包的基础上封装一层,提供更多的上传下载功能。
代码包本身在发布前会打成pyd文件给用户使用,封装的时候没有使用直接的继承关系,而是在一个新的独立类中封装了一个connection对象作为类实例属性值,类似于下边这样:

class SFTP:
	def __init__(self,conn):
		self.conn = conn

然后实际使用的时候发现有个问题,就是用户其实可以通过dir的方式查到SFTP实例对象的属性值,本来加密的初衷就是不想让用户知道类的内部细节,只给用户几个接口说明;如果用户通过dir拿到所有实例属性,还是可以有进一步操作的。
虽然python中有_的约定,也有__的方式隐藏属性 ,但前者只是个约定而已,该访问用户还是能直接访问的;而后者根本就是个掩耳盗铃的方式,我完全可以通过__class__和属性名称拼接出那个属性值的字符串进而拿到那个私有属性对象,如下:

class Test:
    def __init__(self):
        self._v1 = 10
        self.__v2 = {10:10}

test = Test()
print(dir(test))
className = str(test.__class__)
index = className.index('.')
attrName = '_' + className[index + 1:len(className) - 2] + '__v2'
print(getattr(test,attrName))

# 输出:
# ['_Test__v2', '__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__', '_v1']
# {10:10}

所以这两种方式都不好。
因为我们代码是加密的,所以只要想办法让用户无法看到属性即可,一般用户查看属性是通过dir()的方式来的,而dir时解释器会先去用属性中去查看是否有__dict__属性值,有的话就会直接返回它对应的值并拼接到一个数组中,如果能给用户扔一个空的数组,用户就啥都看不到了;所以最后就变成了这样:

class Test:
    def __init__(self):
        self._v1 = 10
        self.__v2 = {10:10}
    def __getattribute__(self, item):
        exclude = {"__class__","__dict__"} # __class__只是拿来凑数的,重点是__dict__
        if item in exclude:
            raise AttributeError("cannot find this attriute")
        return super().__getattribute__(item)

重载了__getattribute__方法,因为解释器去调用一个对象的属性或者方法的时候,都会先去这个方法中找,找到就直接返回,找不到就抛出一个异常再去__getattr__中去找,再找不着就彻底异常了;因此我在__getattribute__中进行了一层过滤,一旦发现用户想查看__dict__,直接就告诉他找不着,此时dir对象的时候就只能返回一个空数组给用户了。

test = Test()
print(dir(test))
# 输出 []

这样基本的功能不会受影响,而实现的细节用户也不能知道了。
当然这个方法是前提的,那就是我这个接口本身已经被加密了,用户可以直接调用,但无法看到源码;如果用户可以接触到源码,那这个方法也就没什么意义了。
另外就是有那么非常小的一点儿几率,用户可能能猜到你用的这个属性名称是什么,他可以直接拼接出来,不过几率很小,而且开发人员如果真不想用户猜到的话,完全可以混淆这个变量名,甚至写个很长的变量名,反正正常写代码时是在编辑器里边,多打几个字母又不影响开发,哈哈。