我来自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对私有标识符的支持有限,通过一个自动在任何以两个下划线开头的标识符前加上类名的特性。在大多数情况下,这对程序员来说是透明的,但最终结果是任何以这种方式命名的变量都可以用作私有变量。

更多信息请参见这里。

一般来说,与其他语言相比,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')

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

关于源代码(更改访问权限,从而绕过Java或c++等语言封装):

您并不总是拥有源代码,即使您拥有源代码,这些源代码也由一个只允许特定程序员访问源代码的系统管理(在专业上下文中)。通常,每个程序员都负责某些类,因此知道自己能做什么,不能做什么。源代码管理器还锁定正在修改的源代码,当然,还管理程序员的访问权限。

所以根据经验,我更相信软件而不是人。所以约定很好,但多重保护更好,比如访问管理(真正的私有变量)+源代码管理。

Python对私有标识符的支持有限,通过一个自动在任何以两个下划线开头的标识符前加上类名的特性。在大多数情况下,这对程序员来说是透明的,但最终结果是任何以这种方式命名的变量都可以用作私有变量。

更多信息请参见这里。

一般来说,与其他语言相比,Python的面向对象实现有点原始。但实际上我很享受这个过程。这是一个概念上非常简单的实现,非常适合该语言的动态风格。

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

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

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

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

下面是我处理Python 3类字段的方法:

class MyClass:
    def __init__(self, public_read_variable, private_variable):
        self.public_read_variable_ = public_read_variable
        self.__private_variable = private_variable

我只在MyClass方法中使用两个下划线访问__private_variable。

我用一个下划线对public_read_variable_进行读访问 在类之外,但从不修改变量:

my_class = MyClass("public", "private")
print(my_class.public_read_variable_) # OK
my_class.public_read_variable_ = 'another value' # NOT OK, don't do that.