【问题标题】:Truly Private Variables in Python 3Python 3 中真正的私有变量
【发布时间】:2019-05-15 07:05:23
【问题描述】:

所以我知道如何在 python 中像这样使变量“私有”:

class Foo:
    def __init__(self):
        self.__private = 'bar'

这“有效”而无效,如下所示:

foo = Foo()
'__private' in vars(foo) #False
'_Foo__private' in vars(foo) #True

现在,我知道这是在 python 中创建私有变量的方法,我喜欢这种方法。它允许您修改名称,这样子类就不会意外地覆盖它(因为它以类的名称开头),并且没有人会意外地使用它。它还使您能够更改私有变量如果您知道自己在做什么。此外,这是最好的方法,因为真正的私有变量是不可能的。

我是这么想的。

最近在看PEP 8,看到了这样一行:

我们在这里不使用术语“私有”,因为在 Python 中没有一个属性是真正私有的(通常没有不必要的工作量)。

此报价可在Designing for Inheritance section of PEP 8 中找到。

请注意“没有通常不必要的工作量”这句话。我现在确信必须有一种方法可以在 python 中获取真正的私有变量。我该怎么做?

我尝试过覆盖__getattribute__,但问题是无法判断呼叫是否来自班级内部(我知道)。

此外,__dict__ 属性在尝试执行此操作时很烦人,因为它包含对所有实例变量的引用。

我也想到了元类,但那些似乎和__getattribute__有同样的问题。

想法?


注意: 我知道在 python 中创建真正私有变量的任何方法都应该永远在生产代码中完成。我只是想知道它是如何完成的。

【问题讨论】:

  • 如果你真的想要隐私,你考虑过编译你的代码吗?根据您将其编译成的内容,确定的用户可能(或可能不会!)仍然能够对源代码进行去混淆处理,但它非常接近
  • @CharlesLandau 问题是关于私有(如在公共、私有、在 C++/Java 世界中受保护)属性,而不是源代码隐私。
  • @CharlesLandau 我明白了,但问题是扩展基类,我看不到如何从编译的可执行文件扩展类。无论如何,通过dir(classname) 调用仍然可以轻松绕过它。
  • 私有属性来自于编写一个 C 扩展,它可以让您以一种无法从外部代码访问的方式存储数据,除非通过手动内存操作的大锤方法。 C 扩展需要大量工作。

标签: python scope private


【解决方案1】:

我尝试过覆盖 getattribute,但问题是无法判断调用是否来自类内部(我知道)。

您可以使用inspect 模块查找调用函数的名称和模块,您可以将其与白名单进行比较。

但是inspect也有getattr_static,可以绕过任何__getattribute__


在 Python 中没有什么是真正私有的。有办法让访问变得困难,但总有办法绕过这些办法。

唯一的解决方案是在当前的 Python 解释器之外。您可以将外部函数接口用于其他更安全的语言或远程过程调用(例如 xmlrpc)到相同或在子进程中运行的另一个 Python 解释器,甚至可以作为具有不同权限的不同用户运行的解释器。私有变量和所有允许访问它的函数都将存在于当前解释器之外。那么就没有办法检查了。

这种类型的privilege separation 甚至是 Pyro RPC 库的stated use cases 之一。

【讨论】:

  • 你可以看看里面有没有。看看我的新答案。
  • 哦,等等,getattr_static。我明白了,但它可以绕过不公开的__dict__吗?
  • 另外,subprocess 是个好主意,但它似乎遥远。
【解决方案2】:

看了this answer 关于inspect 模块之后,我(有点)做到了!

class Foo:
    def __init__(self, private):
        self.private = private

    def __getattribute__(self, attr):
        import inspect
        frame = inspect.currentframe()
        try:
            back_self = frame.f_back.__self__
            if not back_self == self: #is it inside the class?
                ban = ('private', '__dict__') #all private vars, ban __dict__ for no loopholes
                if attr in ban:
                    msg = 'Foo object has no attribute {!r}'
                    raise AttributeError(msg.format(attr))
        finally:
            del frame
        return super().__getattribute__(attr)

    def print_private(self):
        print(self.private) #access in the class!


foo = Foo('hi')
foo.print_private() #output: hi
foo.private #makes an error

嗯,差不多。 inspect 也可用于查找值。不过,这非常接近。它允许在类内部使用object.attr,但如果从外部调用会产生错误。这可能是最接近的。

【讨论】:

  • inspect.getattr_static(foo, 'private')?
  • 我用你的代码试过了,它应该禁止__dict__,并且仍然能够从外部访问它。试试看。
  • 另外,object.__getattribute__(foo, '__dict__') 可以获取实例字典。
  • 如果你的第一个参数不是self怎么办?就此而言,如果您有一个名为 self 的参数不是第一个参数怎么办? self 这个名字在 Python 中并不特殊,它只是一个广泛使用的约定。
  • @MadPhysicist 我知道,但这是一个不错的选择。可以编辑代码以更改任何名称,我只是不想遍历所有名称以查看它们是否为自己。
【解决方案3】:

我喜欢做的事情,虽然它不是 100% 私有的,但是在方法中使用闭包来 R/W 通常不可访问的属性作为 member_descriptor 对象:

def privateNS():

    class MyObject(object):
        __slots__ = ['private'] # name doesn't matter

        def __new__(cls, value): # only sets inst.private on new instance creation
            inst = object.__new__(cls)

            setprivate(inst, value)

            return inst

        # __init__ is not needed, and can't be used here to set inst.private

        def showprivate(inst):
            return getprivate(inst)

    dsc = MyObject.private # get descriptor
    getprivate = dsc.__get__
    setprivate = dsc.__set__
    del MyObject.private # revoke normal access

    return MyObject

MyObject = privateNS()
del privateNS

inst = MyObject( 20 )
print( inst.showprivate() ) # 20

请注意,inst.private 名称不存在,如果被引用,将引发 AttributeError。
但成员描述符本身确实存在,并且绑定到类。

但就像我说的,它不是 100% 私人的......
您可以通过闭包访问提供给类方法的描述符方法:

>>> inst.showprivate.__closure__[0].cell_contents
<method-wrapper '__get__' of member_descriptor object at 0x00E588A0>

这是第一个后门,如果该方法在其闭包中包含__set__
但如果没有,第二个后门只是稍微复杂一点:

>>> inst.showprivate.__closure__[0].cell_contents.__self__.__set__( inst, 30 )
>>> inst.showprivate()
30

虽然在使用多个闭包时会有帮助,但闭包单元的顺序取决于当前运行(如字典键)。

但遗憾的是,我似乎想不出比这更安全的东西...

问题如之前的回答所述:
属性无法判断它们在哪里被访问,并且通过 python 代码提供该级别的功能总是让它们保持打开状态,因为它们总是可以被访问和更改。

如果我错了,请评论:)

【讨论】:

  • 在我的回答中,我找到了一种查看呼叫来源的方法。问题是inspect 似乎能够看到任何东西。 :(
  • 哦等等,我看错了你的答案,所以虽然我的完全隐藏了属性,但你的实际上限制了访问!干得好!
  • 谢谢!可悲的是,inspect 可以看穿它......但它与我认为你能得到的一样接近。如果你能找到阻止inspect 的方法,不过... ...那就太好了!
  • 实际上,我的方法似乎已经这样做了,至少通过getattr_static,因为它似乎检查了类或实例__dict__ 的属性(我正在使用__slots__ 所以只有类具有__dict__,并且私有属性与del 未链接)。但我的问题是我将属性的描述符注入到方法闭包中。
  • 另外,您的问题是 getattr_static 使用 type 机制(基本功能)闯入您的课程并绕过您的 __getattribute__ 方法(如果您查看 inspect.py ,你可以看到发生了什么,因为大多数标准库都是用低质量的 python 代码编写的),这就是为什么禁止 __dict__ 对你不起作用。 ;)
【解决方案4】:

通过使用闭包而不是属性,您可以在没有花哨的检查的情况下获得几乎相同的效果。

class Foo:
    def __init__(self):
        private = 'bar'
        def print_private():
            print(private)
        self.print_private = print_private

foo = Foo()
foo.print_private()  # works
foo.private  # kaboom

当然,inspect 也可以看到闭包。

【讨论】:

  • 虽然这在您需要管理可以更改的对象时并不好,但只需从闭包中访问私有对象并随意更改...您也可以更改闭包单元格中的内容:stackoverflow.com/a/37666086/2131849
【解决方案5】:

Python之所以没有私有属性,是因为我们无法判断它是在类内部还是外部。它们共享相同的属性访问过程。 self.private 正是 obj.private。所以,如果我们阻止obj.privateself.private 也会被阻止。区分它们的唯一方法是给它们起不同的名称,并通过@propertydata descriptor 使self._private 成为self._private 的代理,并相信使用它的人都是成年人。

无论如何,我想分享data descriptor 的概念,它可以通过添加一个属性代理层来NEARLY 私有属性(正如我所说,这将阻止从'内部访问' 类):

class Private:
    def __init__(self, attribute):
        self.attribute = attribute

    def __get__(self, obj, type=None):
        raise AttributeError("'{}' object has no attribute '{}'".format(obj, self.attribute))

    def __set__(self, obj, value):
        obj.__dict__[self.attribute] = value

class YourClass:
    private = Private('private')

    def __init__(self):
        self.private = 10
        print(self.private)  # Raise AttributeError

使用双下划线或更改__getattribute__都是不好的做法,尤其是后者,可能会导致灾难。

【讨论】:

  • 嗯,我希望有办法,但我想至少我知道这是不可能的。谢谢!
  • 是的,这就是 Python 的“真面目”。
  • __getattribute__ 在元编程中也可以,但我同意它不应该在日常使用中使用。
  • inspect 可以看到它叫什么。
  • 这真的很黑,很有趣。
猜你喜欢
  • 2014-08-19
  • 2011-11-19
  • 2015-01-03
  • 1970-01-01
  • 2015-02-11
  • 2021-11-22
  • 2011-11-16
  • 2011-10-21
相关资源
最近更新 更多