Python 在类中有“私有”变量吗?

2024-11-28 08:38:00
admin
原创
180
摘要:问题描述:我来自 Java 世界,正在阅读 Bruce Eckels 的《Python 3 模式、食谱和习语》。在阅读有关类的内容时,它继续说在 Python 中不需要声明实例变量。你只需在构造函数中使用它们,然后它们就出现了。例如:class Simple: def __init__(self, s...

问题描述:

我来自 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 中不需要它。

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

作为一名 Python 和 Java 程序员,我发现privateprotected是非常非常重要的设计概念。但实际上,在数万行 Java 和 Python 代码中,我从未真正使用过privateprotected

为什么不呢?

这是我的问题“保护免受谁的侵害?”

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

其他团队的程序员?他们为同一家公司工作。他们只需打个电话就能获得源代码。

客户?一般来说,这是雇佣编程。客户一般拥有代码。

那么,我究竟在保护它免受谁的侵害?

解决方案 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

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

解决方案 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

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2545  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1551  
  建筑工程全生命周期涉及从项目规划、设计、施工到运营维护等多个复杂阶段,每个阶段都产生和依赖大量信息。PLM(产品生命周期管理)系统作为一种整合数据、流程和人员的数字化解决方案,正逐渐成为建筑行业实现高效协同与可持续发展的关键支撑。通过数字化转型,PLM系统能够优化各阶段的工作流程,提升项目整体质量和效率,为建筑工程的全...
plm是什么软件   1  
  产品生命周期管理(PLM)系统在企业资源成本率优化方面发挥着至关重要的作用。通过构建有效的数据模型,PLM系统能够整合企业各个环节的数据,为资源成本的精准分析和优化提供有力支持。这不仅有助于企业降低成本,还能提升产品质量和市场竞争力。PLM系统概述PLM系统是一种用于管理产品从概念设计到退役全生命周期过程中所有信息和流...
PLM项目管理软件   1  
  产品生命周期管理(PLM)系统在现代企业的产品研发、生产与管理过程中扮演着至关重要的角色。它整合了从产品概念设计到产品退役的全生命周期数据与流程,助力企业提升效率、降低成本并增强创新能力。随着技术的不断发展,到 2025 年,PLM 系统将具备一系列核心功能模块,这些模块将深度影响企业的运营与发展。产品数据管理模块产品...
plm是什么意思   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用