Python 在类中有“私有”变量吗?
- 2024-11-28 08:38:00
- admin 原创
- 181
问题描述:
我来自 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 中,我们学习过 public/private/protected 变量。这些关键字很有意义,因为有时你需要类中的变量,但类外的任何人都无法访问这些变量。
为什么 Python 中不需要这样做?
解决方案 1:
这是文化问题。在 Python 中,您不会写入其他类的实例或类变量。在 Java 中,如果您真的想这样做,没有什么可以阻止您这样做 - 毕竟,您始终可以编辑类本身的源代码来实现相同的效果。Python 放弃了这种安全的伪装,并鼓励程序员承担责任。在实践中,这非常有效。
如果出于某种原因您想要模拟私有变量,则始终可以使用PEP 8__
中的前缀。 Python 会像这样破坏变量的名称,以便它们不容易被包含它们的命名空间之外的代码看到(尽管如果您足够坚定,您可以绕过它,就像如果您努力的话,您可以绕过 Java 的保护一样)。__foo
按照同样的惯例,_
前缀表示_variable
应仅在类(或模块)内部使用,即使技术上不禁止您从其他地方访问它。您不会摆弄看起来像__foo
或 的其他类的变量_bar
。
解决方案 2:
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()
访问...毕竟,函数是为了可重用性而创建的,并且没有赋予其中的语句任何特殊的权力。
解决方案 3:
正如上面许多评论所正确提到的,我们不要忘记访问修饰符的主要目的:帮助代码用户理解什么应该改变,什么不应该改变。当你看到一个私有字段时,你不会乱动它。所以它主要是语法糖,在 Python 中可以通过 和 _ 轻松实现。
解决方案 4:
Python 不像 C++ 或 Java 那样具有任何私有变量。如果需要,您也可以随时访问任何成员变量。但是,在 Python 中您不需要私有变量,因为在 Python 中公开类的成员变量并不是一件坏事。如果您需要封装成员变量,您可以稍后使用“@property”来执行此操作,而不会破坏现有的客户端代码。
在 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
self.__foobar 的名称在 A 类中自动被修改为 self._A__foobar。在 B 类中,它被修改为 self._B__foobar。因此,每个子类都可以定义自己的变量 __foobar,而无需覆盖其父类变量。但没有什么可以阻止您访问以双下划线开头的变量。但是,名称修改会阻止您顺便调用这些变量/方法。
我强烈建议您观看 Raymond Hettinger在PyCon 2013 上制作的Python 类开发工具包,它很好地说明了为什么以及如何使用 @property 和“__”实例变量(相关部分从37 分 16 秒开始)。
如果您已经公开了公共变量,并且需要封装它们,那么您可以使用@property。因此,您可以从最简单的解决方案开始。您可以将成员变量保留为公共变量,除非您有具体的理由不这样做。以下是一个例子:
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
解决方案 5:
下划线约定中的私有变量存在变体。
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'
它们之间存在一些细微的差别,但为了编程模式思想的纯粹性,这已经足够了。
有一些 @private 装饰器的例子可以更紧密地实现这个概念,但您的情况可能会有所不同。可以说,也可以编写一个使用 meta 的类定义。
解决方案 6:
如前所述,您可以通过在变量或方法前加上下划线来表明它是私有的。如果您觉得这还不够,您可以随时使用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
解决方案 7:
Python 对私有标识符的支持有限,通过一项功能,该功能会自动将类名添加到以两个下划线开头的任何标识符的前面。这在很大程度上对程序员来说是透明的,但最终结果是任何以此方式命名的变量都可以用作私有变量。
请参阅此处以了解更多相关信息。
总体而言,与其他语言相比,Python 的面向对象实现有点原始。但实际上,我很喜欢这一点。这是一个概念上非常简单的实现,并且与语言的动态风格非常契合。
解决方案 8:
我唯一一次使用私有变量是当我需要在写入或读取变量时做其他事情时,因此我需要强制使用 setter 和/或 getter。
正如前面所述,这又与文化有关。我曾经从事过一些项目,在这些项目中,读写其他类变量是自由的。当一个实现被弃用时,需要花费更长的时间来识别使用该函数的所有代码路径。当强制使用 setter 和 getter 时,可以轻松编写调试语句来识别已调用的弃用方法以及调用它的代码路径。
当您参与一个任何人都可以编写扩展的项目时,通知用户将在几个版本中消失的弃用方法对于在升级时将模块损坏降至最低至关重要。
所以我的答案是:如果你和你的同事维护一个简单的代码集,那么保护类变量并不总是必要的。如果你正在编写一个可扩展的系统,那么当对核心进行更改时,保护就变得势在必行,所有使用该代码的扩展都需要捕获这些更改。
解决方案 9:
“在 Java 中,我们已经学习过公共/私有/受保护变量”
“为什么这在 Python 中不是必需的?”
出于同样的原因,Java 中不需要它。
您可以自由使用 -- 或者不使用private
和protected
。
作为一名 Python 和 Java 程序员,我发现private
和protected
是非常非常重要的设计概念。但实际上,在数万行 Java 和 Python 代码中,我从未真正使用过private
或protected
。
为什么不呢?
这是我的问题“保护免受谁的侵害?”
我团队里的其他程序员?他们有源代码。当他们可以更改它时,受保护是什么意思?
其他团队的程序员?他们为同一家公司工作。他们只需打个电话就能获得源代码。
客户?一般来说,这是雇佣编程。客户一般拥有代码。
那么,我究竟在保护它免受谁的侵害?
解决方案 10:
在 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 中一样。该属性的名称仍然是全局的,但以一种不可访问的方式,就像其他语言中的私有属性一样。
但不要害怕它。没关系。它也能完成工作。;)
解决方案 11:
私有和受保护的概念非常重要。但是 Python 只是一个原型设计和快速开发的工具,可供开发的资源有限,这就是为什么 Python 中某些保护级别没有得到严格遵守的原因。您可以在类成员中使用“__”。它工作正常,但看起来不够好。对此类字段的每次访问都包含这些字符。
另外,你可以注意到 Python OOP 概念并不完美。Smalltalk或Ruby更接近纯 OOP 概念。甚至 C# 或 Java 也更接近。
Python 是一个非常好的工具。但它是一种简化的 OOP 语言。语法和概念上都简化了。Python 存在的主要目标是让开发人员能够以非常快的方式编写具有高抽象级别的易读代码。
解决方案 12:
以下是我处理 Python 3 类字段的方法:
class MyClass:
def __init__(self, public_read_variable, private_variable):
self.public_read_variable_ = public_read_variable
self.__private_variable = private_variable
__private_variable
我仅在方法内部使用两个下划线访问MyClass
。
我确实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.
解决方案 13:
我是 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
解决方案 14:
关于来源(改变访问权限,从而绕过 Java 或 C++ 等语言封装):
您并不总是拥有源代码,即使您有,源代码也由系统管理,该系统只允许某些程序员访问源代码(在专业环境中)。通常,每个程序员都负责某些类,因此知道自己能做什么和不能做什么。源代码管理器还会锁定正在修改的源代码,当然,还会管理程序员的访问权限。
所以根据经验,我更信任软件而不是人。因此惯例是好的,但多重保护更好,例如访问管理(真正的私有变量)+源管理。
解决方案 15:
自从我开始开发一个想要发布的包以来,我一直在考虑私有类属性和方法(进一步阅读命名成员)。其背后的想法绝不是让覆盖这些成员变得不可能,而是对接触它们的人发出警告。我想出了一些可能有帮助的解决方案。第一个解决方案在我最喜欢的 Python 书籍之一《Fluent 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')
您会看到,有多种方法可以保护您的类成员,但无论如何,这并不能保证用户不会覆盖它们。这只是给您一些想法。最后,您也可以使用元类,但这可能会带来新的危险。这里使用的技术也非常简单,您一定要查看文档,您可以找到此技术的有用功能并根据需要对其进行自定义。
解决方案 16:
尽管 Python 中没有直接的“私有”变量约定,但您当然可以在不同程度上实现自己的约定。除了前置下划线或 getter/setter 方法外,您还可以使用 Cython 实现真正的私有变量。分别创建以下内容的文件 PriVar.pyx 和 setup.py
# PriVar.pyx
cdef class PrivateVariable:
cdef int _value
def __cinit__(self):
self._value = 0
def getvalue(self):
return self._value
def setvalue(self, value):
self._value = value
# setup.py
from setuptools import setup
from Cython.Build import cythonize
setup(ext_modules = cythonize('PriVar.pyx'))
现在使用以下命令构建模块
python3.10 setup.py build_ext --inplace
现在您可以模拟只能通过 getter/setter 函数访问的“私有”变量。
Python 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from PriVar import PrivateVariable
>>> myvar = PrivateVariable()
>>> dir(myvar)
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', 'getvalue', 'setvalue']
# try to get __dict__
>>> myvar.__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'PriVar.PrivateVariable' object has no attribute '__dict__'. Did you mean: '__dir__'?
# can get and set, but not see
>>> myvar.getvalue()
0
>>> myvar.setvalue(42)
>>> myvar.getvalue()
42
>>> myvar.value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'PriVar.PrivateVariable' object has no attribute 'value'
# hmmm maybe its hidden?
>>> myvar._value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'PriVar.PrivateVariable' object has no attribute '_value'
>>> myvar.__value
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'PriVar.PrivateVariable' object has no attribute '__value'. Did you mean: 'getvalue'?
# definitely can not set a non existent attribute
>>> myvar.value = 1
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'PriVar.PrivateVariable' object has no attribute 'value'
# lets try and override it with subclassing
>>> class NewPrivateVariable(PrivateVariable):
... def __init__(self, v):
... self.value = v
...
>>> mynewvar = NewPrivateVariable(100)
>>> mynewvar.value
100
>>> mynewvar.__dict__
{'value': 100}
>>> mynewvar.getvalue()
0
>>> mynewvar.setvalue(5)
>>> mynewvar.getvalue()
5
# still can not access value associated with getter/setter function.. it's private!!
*编辑此方法有效的原因是 Cython cdef int“变量”被创建为 Cython 模块范围的私有变量。您也可以设置公共和只读属性。https ://cython.readthedocs.io/en/latest/src/userguide/extension_types.html#static-attributes
扫码咨询,免费领取项目管理大礼包!