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