Python在类中是否有“私有”变量?

我来自Java世界,正在阅读Bruce Eckels的Python 3模式、配方和习语

在阅读类时,它继续说在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中不需要这个?

626465 次浏览

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

有关更多信息,请参阅这里

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

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

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

按照同样的约定,_前缀意味着_variable应该只在类(或模块)内部使用,即使你在技术上没有被阻止从其他地方访问它。你不会玩弄另一个类的变量,看起来像__foo_bar

“在Java中,我们已经学习了公共/私有/受保护的变量”

“为什么Python中不需要这个?”

出于同样的原因,它不是Java中的选填/必填

您可以自由使用-或不使用privateprotected

作为一个Python和Java程序员,我发现privateprotected是非常非常重要的设计概念。但实际上,在成千上万行Java和Python中,我从来没有实际上使用过privateprotected

为什么不呢?

这是我的问题“保护谁?”

我团队中的其他程序员?他们有源代码。当他们可以更改它时,保护是什么意思?

其他团队的其他程序员?他们为同一家公司工作。他们可以——通过打电话——获得源代码。

客户端?(一般来说)这是雇佣式编程。客户端(通常)拥有代码。

那么,确切地说,我在保护它免受谁的伤害?

私有和受保护的概念非常重要。但Python只是一个用于原型设计和快速开发的工具,可用于开发的资源有限,这就是为什么Python中的一些保护级别没有得到如此严格的遵守。你可以在类成员中使用“__”。它工作正常,但看起来不够好。每次对此类字段的访问都包含这些字符。

此外,您可以注意到Python OOP概念并不完美。Smalltalkruby更接近纯粹的OOP概念。甚至C#或Java也更接近。

Python是一个非常好的工具。但它是一种简化的OOP语言。在语法上和概念上都是简化的。Python存在的主要目标是为开发人员提供以非常快的方式编写具有高抽象级别的易于阅读的代码的可能性。

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

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

当您在一个任何人都可以编写扩展的项目中时,通知用户将在几个版本中消失的已弃用方法对于在升级时将模块中断保持在最低限度至关重要。

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

下划线约定中存在私有变量的变体。

In [5]: class Test(object):
...:     def __private_method(self):
...:         return "Boo"
...:     def public_method(self):
...:         return self.__private_method()
...:


In [6]: x = Test()


In [7]: x.public_method()
Out[7]: 'Boo'


In [8]: x.__private_method()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-8-fa17ce05d8bc> in <module>()
----> 1 x.__private_method()


AttributeError: 'Test' object has no attribute '__private_method'

有一些细微的差异,但为了编程模式的意识形态纯度,它已经足够好了。

有一些例子可以更紧密地实现这个概念,但是因人而异。可以说,人们也可以编写一个使用meta的类定义。

正如上面的许多评论所正确提到的,让我们不要忘记Access修饰符的主要目标:帮助代码用户理解什么应该改变,什么不应该改变。当你看到一个私有字段时,你不会弄乱它。所以它主要是语法糖,在Python中很容易通过_和__实现。

Python中的私有变量或多或少是一种黑客行为:解释器故意重命名变量。

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()…毕竟,函数是为可重用性而创建的,里面的语句没有任何特殊的功能。

当涉及编译器时,游戏就不同了(隐私是一个编译器级别的概念)。它知道带有权限改造修饰符的类定义,因此如果在编译时没有遵循规则,它可能会出错。

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

class Foo:


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


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

这样,引用bar的某人或某物实际上引用了bar函数的返回值,而不是变量本身,因此它可以被访问但不能更改。然而,如果有人真的想这样做,他们可以简单地使用_bar并为其分配一个新值。正如我们反复说的那样,没有什么万无一失的方法可以阻止某人访问你希望隐藏的变量和方法。然而,使用property是你可以发送的最明确的信息,即变量不需要编辑。property也可以用于更复杂的getter/setter/deleter访问路径,如下所述:https://docs.python.org/3/library/functions.html#property

在Python 3中,如果你只是想“封装”类属性,就像Java一样,你可以这样做:

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


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


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

要实例化此操作:

ss = Simple("lol")
ss.show()

注意:print(ss.__s)会抛出一个错误。

在实践中,Python 3会混淆全局属性名称。它像Java中那样将其变成“私有”属性。该属性的名称仍然是全局的,但以一种无法访问的方式,就像其他语言中的私有属性一样。

但是不要害怕它。没关系。它也有工作。;)

Python没有像C++或Java那样的私有变量。如果需要,你也可以随时访问任何成员变量。但是,在Python中不需要私有变量,因为在Python中公开类的成员变量并不坏。如果你需要封装成员变量,稍后可以使用“@属性”来做到这一点,而不会破坏现有的客户端代码。

在Python中,单下划线“_”用于表示方法或变量不被视为类的公共API的一部分,并且API的这部分可能在不同版本之间发生变化。您可以使用这些方法和变量,但如果您使用此类的较新版本,您的代码可能会中断。

双下划线“__”并不意味着“私有变量”。您使用它来定义变量,这些变量是“类局部的”,并且不能被子类轻易覆盖。它修改了变量名称。

例如:

class A(object):
def __init__(self):
self.__foobar = None # Will be automatically mangled to self._A__foobar


class B(A):
def __init__(self):
self.__foobar = 1 # Will be automatically mangled to self._B__foobar

_B__foobar。所以每个子类都可以定义自己的变量__foobar而不覆盖它的父变量。但是没有什么可以阻止你访问以双下划线开头的变量。但是,名称修饰会阻止你 /methods偶然调用这个变量。

我强烈建议您观看Raymond Hettinger在PyCon 2013中的Python的类开发工具包,它提供了一个很好的例子,说明了为什么以及如何使用@属性和“__”实例变量。

如果你已经公开了公共变量并且需要封装它们,那么你可以使用@属性。因此,你可以从最简单的解决方案开始。除非你有具体的理由不这样做,否则你可以将成员变量保留为公共。这是一个例子:

class Distance:
def __init__(self, meter):
self.meter = meter




d = Distance(1.0)
print(d.meter)
# prints 1.0


class Distance:
def __init__(self, meter):
# Customer request: Distances must be stored in millimeters.
# Public available internals must be changed.
# This would break client code in C++.
# This is why you never expose public variables in C++ or Java.
# However, this is Python.
self.millimeter = meter * 1000


# In Python we have @property to the rescue.
@property
def meter(self):
return self.millimeter *0.001


@meter.setter
def meter(self, value):
self.millimeter = value * 1000


d = Distance(1.0)
print(d.meter)
# prints 1.0

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

你并不总是拥有源代码,如果你有,源代码由一个只允许特定程序员访问源代码的系统管理(在专业环境中)。通常,每个程序员都负责某些类,因此知道他能做什么和不能做什么。源管理器还锁定被修改的源,当然,管理程序员的访问权限。

因此,根据经验,我更信任软件而不是人类。所以约定很好,但多个保护更好,例如访问管理(真正的私有变量)+源管理。

所以我是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

以下是我如何处理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.

自从我开始开发一个我想发布的包以来,我一直在思考私有类的属性和方法(在进一步阅读中命名为成员)。背后的想法是永远不要使这些成员无法覆盖,而是对那些接触它们的人发出警告。我想到了一些可能有帮助的解决方案。第一个解决方案用在我最喜欢的Python书籍之一,流利的Python中。


技术优势1:

  • 它不太可能被偶然覆盖。
  • 它很容易理解和实施。
  • 对于实例属性,它比前导双下划线更容易处理。

*在书中使用了哈希符号,但您也可以使用转换为字符串的整数。在Python中禁止使用klass.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')

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