我来自Java世界,正在阅读Bruce Eckels的《Python 3 Patterns, Recipes and idiom》。

在阅读有关类的内容时,会继续说在Python中不需要声明实例变量。你只需要在构造函数中使用它们,它们就在那里了。

例如:

class Simple:
    def __init__(self, s):
        print("inside the simple constructor")
        self.s = s

    def show(self):
        print(self.s)

    def showMsg(self, msg):
        print(msg + ':', self.show())

如果这是真的,那么任何Simple类的对象都可以改变类外变量s的值。

例如:

if __name__ == "__main__":
    x = Simple("constructor argument")
    x.s = "test15" # this changes the value
    x.show()
    x.showMsg("A message")

在Java中,我们已经学习了公共/私有/受保护变量。这些关键字是有意义的,因为有时您希望类中的变量在类之外没有人可以访问。

为什么Python中不需要这个?


当前回答

自从我开始开发一个想要发布的包以来,我一直在考虑私有类属性和方法(后续阅读中称为成员)。它背后的想法从来都不是不可能覆盖这些成员,而是对那些接触它们的人发出警告。我想到了一些可能有用的解决办法。第一个解决方案在我最喜欢的Python书籍之一《Fluent Python》中使用。


技术1的优点:

它不太可能被意外覆盖。 它很容易理解和实现。 它比实例属性的前双下划线更容易处理。

*在书中使用了哈希符号,但你也可以使用整数转换为字符串。在Python中,禁止使用class .1

class Technique1:

    def __init__(self, name, value):
        setattr(self, f'private#{name}', value)
        setattr(self, f'1{name}', value)

技术1的缺点:

但是,使用这种技术不容易保护方法。这是可能的。 属性查找只能通过getattr实现 仍然没有对用户发出警告


我遇到的另一个解决方案是编写__setattr__。优点:

它很容易实现和理解 它与方法一起工作 查找不受影响 用户得到一个警告或错误

class Demonstration:

    def __init__(self):
        self.a = 1

    def method(self):
        return None

    def __setattr__(self, name, value):
        if not getattr(self, name, None):
            super().__setattr__(name, value)
        else:
            raise ValueError(f'Already reserved name: {name}')

d = Demonstration()
#d.a = 2
d.method = None

缺点:

您仍然可以重写类 为了让变量不仅仅是常量,您需要映射允许的输入。 子类仍然可以覆盖方法


为了防止子类覆盖方法,你可以使用__init_subclass__:

class Demonstration:
    __protected = ['method']

    def method(self):
        return None

    def __init_subclass__(cls):
        protected_methods = Demonstration.__protected
        subclass_methods = dir(cls)
        for i in protected_methods:
            p = getattr(Demonstration,i)
            j = getattr(cls, i)
            if not p is j:
                raise ValueError(f'Protected method "{i}" was touched')

可以看到,有很多方法可以保护类成员,但不能保证用户不会覆盖它们。这应该能给你们一些启发。最后,您还可以使用元类,但这可能会带来新的危险。这里使用的技术也非常简单,你应该看看文档,你可以找到这个技术的有用特性,并根据你的需要定制它们。

其他回答

我是Python的新手但我有c#和JavaScript的背景。就特性而言,Python感觉像是两者的混合体。JavaScript在这方面也有问题,解决方法是创建一个闭包。这可以通过返回不同的对象来防止访问您不想公开的数据。

def print_msg(msg):
    # This is the outer enclosing function

    def printer():
        # This is the nested function
        print(msg)

    return printer  # returns the nested function


# Now let's try calling this function.
# Output: Hello
another = print_msg("Hello")
another()

https://www.programiz.com/python-programming/closure

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures#emulating_private_methods_with_closures

我唯一一次使用私有变量是当我需要在写入或读取变量时做其他事情,因此我需要强制使用setter和/或getter。

正如前面所述,这又涉及到文化。我一直在工作的项目中,读取和写入其他类变量是自由的。当一个实现被弃用时,识别使用该函数的所有代码路径需要更长的时间。当强制使用setter和getter时,可以很容易地编写调试语句来识别已调用的废弃方法和调用它的代码路径。

当你在一个任何人都可以编写扩展的项目中,通知用户那些将在几个版本中消失的废弃方法,因此在升级时将模块破坏降到最低是至关重要的。

所以我的答案是;如果您和您的同事维护一个简单的代码集,那么保护类变量并不总是必要的。如果您正在编写一个可扩展的系统,那么当对核心的更改需要被使用该代码的所有扩展捕获时,它就变得必不可少。

私有变量在Python中或多或少是一种hack:解释器会故意重命名变量。

class A:
    def __init__(self):
        self.__var = 123
    def printVar(self):
        print self.__var

现在,如果你试图在类定义之外访问__var,它会失败:

>>> x = A()
>>> x.__var # this will return error: "A has no attribute __var"

>>> x.printVar() # this gives back 123

但你可以很容易地摆脱这种情况:

>>> x.__dict__ # this will show everything that is contained in object x
               # which in this case is something like {'_A__var' : 123}

>>> x._A__var = 456 # you now know the masked name of private variables
>>> x.printVar() # this gives back 456

你可能知道OOP中的方法是这样调用的:x. printvar () => A.printVar(x)。如果A.printVar()可以访问x中的某个字段,那么这个字段也可以在A.printVar()之外访问…毕竟,函数是为可重用性而创建的,其中的语句并没有任何特殊的功能。

它的文化。在Python中,不写入其他类的实例或类变量。在Java中,如果你真的想这样做,没有什么可以阻止你这样做——毕竟,你总是可以编辑类本身的源代码来达到同样的效果。Python放弃了安全的伪装,并鼓励程序员负责任。在实践中,这工作得非常好。

如果您出于某种原因想要模拟私有变量,您总是可以使用PEP 8中的__前缀。Python会修改像__foo这样的变量名,这样它们就不容易被包含它们的命名空间之外的代码看到(尽管如果你足够坚定,你可以绕过它,就像你可以绕过Java的保护一样)。

根据同样的约定,_前缀意味着_variable应该只在类(或模块)内部使用,即使技术上不阻止从其他地方访问它。你不会摆弄其他类的变量,比如__foo或_bar。

如前所述,可以通过在变量或方法前面加上下划线来表示它是私有的。如果您觉得这还不够,还可以使用属性装饰器。这里有一个例子:

class Foo:

    def __init__(self, bar):
        self._bar = bar

    @property
    def bar(self):
        """Getter for '_bar'."""
        return self._bar

This way, someone or something that references bar is actually referencing the return value of the bar function rather than the variable itself, and therefore it can be accessed but not changed. However, if someone really wanted to, they could simply use _bar and assign a new value to it. There is no surefire way to prevent someone from accessing variables and methods that you wish to hide, as has been said repeatedly. However, using property is the clearest message you can send that a variable is not to be edited. property can also be used for more complex getter/setter/deleter access paths, as explained here: https://docs.python.org/3/library/functions.html#property