Python 中的私有成员
- 2025-03-12 08:55:00
- admin 原创
- 15
问题描述:
如何在 Python 中将方法和数据成员设为私有?或者 Python 不支持私有成员?
解决方案 1:
9.6. 私有变量
Python 中不存在“私有”实例变量,即除了对象内部之外无法访问的变量。但是,大多数 Python 代码都遵循一个惯例:带有下划线前缀的名称(例如 _spam)应被视为 API 的非公共部分(无论它是函数、方法还是数据成员)。它应被视为实现细节,如有更改,恕不另行通知。
由于类私有成员有一个有效的用例(即避免名称与子类定义的名称发生名称冲突),因此对这种称为名称改编的机制的支持有限。任何形式为 __spam 的标识符(至少两个前导下划线,最多一个尾随下划线)在文本上均被替换为
_classname__spam
,其中 classname 是当前类名,前导下划线已被删除。只要标识符出现在类的定义中,这种改编就与标识符的语法位置无关。
例如,
class Test:
def __private_symbol(self):
pass
def normal_symbol(self):
pass
print dir(Test)
将输出:
['_Test__private_symbol',
'__doc__',
'__module__',
'normal_symbol']
__private_symbol
应被视为一种私有方法,但仍可通过 访问_Test__private_symbol
。
解决方案 2:
其他答案提供了技术细节。我想强调 Python 与 C++/Java 等语言(根据您的问题,我假设您熟悉这些语言)之间的哲学差异。
Python(以及 Perl)中的一般态度是,属性的“隐私”是程序员的请求,而不是编译器/解释器的铁丝网。这封邮件很好地概括了这个想法,并经常被称为“我们都是自愿的成年人”,因为它“假设”程序员有足够的责任心不会干涉内部。前导下划线是一种礼貌的信息,表示该属性是内部的。
另一方面,如果您确实想访问某些应用程序的内部结构(一个显著的例子是文档生成器,如 pydoc),您可以自由地这样做。作为程序员,您有责任了解自己在做什么并正确地完成它,而不是语言强迫您按照它的方式去做。
解决方案 3:
Python 中没有private
任何其他访问保护机制。Python样式指南中记录了一项惯例,用于指示类的用户他们不应访问某些属性。
_single_leading_underscore:弱“内部使用”指示符。例如,
from M import *
不导入名称以下划线开头的对象。single_trailing_underscore_:按惯例使用,以避免与 Python 关键字冲突,例如
Tkinter.Toplevel(master, class_='ClassName')
__double_leading_underscore:命名类属性时,调用名称修改(在 FooBar 类中,__boo 变成 _FooBar__boo;见下文)。
解决方案 4:
Python 不直接支持隐私。程序员需要知道何时可以安全地从外部修改属性,但无论如何,使用 Python,您可以通过一些小技巧实现类似私有的功能。现在让我们看看一个人是否可以将任何内容私有化。
class Person(object):
def __priva(self):
print "I am Private"
def publ(self):
print " I am public"
def callpriva(self):
self.__priva()
现在我们将执行:
>>> p = Person()
>>> p. publ()
我是公开的
>>> p.__priva()
回溯(最近一次调用最后一次):
文件“”,第 1 行,位于
p.__priva()
AttributeError:'Person' 对象没有属性 '__priva'
#解释:您可以在这里看到我们无法直接获取该私有方法。
>>> p.callpriva()
我是私人的
#解释:在这里我们可以访问类内部的私有方法
那么如何访问该变量呢?
你可以这样做:
>>> p._Person__priva
我是私人的
哇,实际上如果 python 获取任何以双下划线开头的变量,都会通过在开头添加单下划线和类名来“翻译”:
注意:如果您不想更改此名称,但仍想向其他对象发送信号让其远离,则可以使用单个初始下划线名称,初始下划线不会通过带星号的导入导入(从模块导入 *)
示例:
#test.py
def hello():
print "hello"
def _hello():
print "Hello private"
#----------------------
#test2.py
from test import *
print hello()
print _hello()
输出-->
你好
回溯(最近一次调用最后一次):
文件“”,第 1 行,位于
NameError:名称“_hello”未定义
现在如果我们手动调用_hello。
#test2.py
from test import _hello , hello
print hello()
print _hello()
输出-->
你好
你好私人
最后:Python 实际上没有同等的隐私支持,尽管单下划线和双下划线在某种程度上给你提供了两个级别的隐私
解决方案 5:
如果 Python 函数、类方法或属性的名称以两个下划线开头(但不以两个下划线结尾),则为私有;其他所有方法均为公共。Python 没有受保护的类方法的概念(只能在自己的类和子类中访问)。类方法要么是私有的(只能在自己的类中访问),要么是公共的(可以从任何地方访问)。
深入 Python
解决方案 6:
这可能会有用:
import sys, functools
def private(member):
@functools.wraps(member)
def wrapper(*function_args):
myself = member.__name__
caller = sys._getframe(1).f_code.co_name
if (not caller in dir(function_args[0]) and not caller is myself):
raise Exception("%s called by %s is private"%(myself,caller))
return member(*function_args)
return wrapper
class test:
def public_method(self):
print('public method called')
@private
def private_method(self):
print('private method called')
t = test()
t.public_method()
t.private_method()
解决方案 7:
这个问题有点长,但我认为它触及了真正的问题根源——可见性范围。请大家坚持下去,我会努力解决这些问题!
简单地导入模块不一定能让应用程序开发人员访问其所有类或方法;如果我无法实际查看模块源代码,我怎么知道有哪些可用?必须有人(或某物)告诉我我能做什么,并解释如何使用那些允许我使用的功能,否则整个事情对我来说毫无用处。
那些通过导入的模块基于基本类和方法开发高级抽象的人会得到一份规范文档——而不是实际的源代码。
模块规范描述了所有旨在向客户端开发人员展示的功能。在处理大型项目和软件项目团队时,模块的实际实现应该始终对使用它的人保持隐藏——它是一个带有外部世界接口的黑匣子。对于 OOD 纯粹主义者,我相信技术术语是“解耦”和“一致性”。模块用户只需知道接口方法,而不必担心实现细节。
在没有首先更改其底层规范文档的情况下,绝对不要更改模块,这可能需要在某些组织中在更改代码之前进行审查/批准。
作为一名业余程序员(现已退休),我开始了一个新模块,其 spec 文档实际上是以模块顶部的一大注释块的形式写出来的,这将是用户在 spec 库中实际看到的部分。由于只有我一个人,我还没有设置一个库,但这应该很容易做到。
然后我开始编写各种类和方法,但不编写函数体——只有像“print()”这样的空打印语句——足以让模块编译时没有语法错误。完成此步骤后,我会编译完成的空模块——这是我的规范。如果我在项目团队中工作,我会在继续充实主体之前提交此规范/接口以供审查和评论。
我一次充实每个方法的主体并进行相应的编译,确保语法错误能够立即得到修复。这也是开始在底部编写临时“主”执行部分以在编码时测试每个方法的好时机。编码/测试完成后,所有测试代码都将被注释掉,直到您再次需要更新时才需要它。
在现实的开发团队中,规范注释块也会出现在文档控制库中,但那是另外一回事。重点是:作为模块客户端,您只能看到此规范,而看不到源代码。
附言:早在时间开始之前,我就在国防航天界工作,我们做了一些很酷的东西,但专有算法和敏感系统控制逻辑等东西都被严密地保管和加密在超级安全的软件库中。我们可以访问模块/包接口,但不能访问黑盒实现体。有一个文档管理工具可以处理所有系统级设计、软件规格、源代码和测试记录——所有这些都是同步在一起的。政府有严格的软件质量保证标准要求。有人记得一种叫“Ada”的语言吗?我才多大!
解决方案 8:
PEP 8
“方法名称和实例变量”部分https://peps.python.org/pep-0008/#method-names-and-instance-variables
仅对非公共方法和实例变量使用一个前导下划线。
为了避免与子类发生名称冲突,请使用两个前导下划线来调用 Python 的名称修改规则。
Python 将这些名称与类名混杂在一起:如果类 Foo 有一个名为 __a 的属性,则它不能被 Foo.__a 访问。(坚持的用户仍然可以通过调用 Foo._Foo__a 获得访问权限。)通常,双前导下划线仅应用于避免与设计为子类的类中的属性发生名称冲突。
注意:关于__names的使用存在一些争议(见下文)。
“为继承而设计”部分https://peps.python.org/pep-0008/#designing-for-inheritance
始终确定类的方法和实例变量(统称为“属性”)应为公共还是非公共。如果有疑问,请选择非公共;稍后将其设为公共比将公共属性设为非公共更容易。
公共属性是您希望与类无关的客户端使用的属性,并且您承诺避免向后不兼容的更改。非公共属性是不打算由第三方使用的属性;您无法保证非公共属性不会更改甚至被删除。
我们在这里不使用术语“私有”,因为在 Python 中没有属性是真正私有的(通常不需要不必要的工作)。
另一类属性是“子类 API”的一部分(在其他语言中通常称为“受保护”)。有些类被设计为可继承,以扩展或修改类的行为。在设计这样的类时,请谨慎决定哪些属性是公共的,哪些属性是子类 API 的一部分,哪些属性真正只能由基类使用。
考虑到这一点,这里是 Pythonic 指南:
公共属性不应该有前导下划线。
如果公共属性名称与保留关键字冲突,请在属性名称后附加一个尾随下划线。这比缩写或错误拼写要好。(但是,尽管有此规则,对于已知是类的任何变量或参数,尤其是类方法的第一个参数,首选拼写是“cls”。)
注 1:请参阅上面的类方法的参数名称建议。
对于简单的公共数据属性,最好只公开属性名称,而不要使用复杂的访问器/变量器方法。请记住,如果您发现简单的数据属性需要增加功能行为,Python 会提供一条通往未来增强的简单途径。在这种情况下,使用属性将功能实现隐藏在简单的数据属性访问语法后面。
注 1:尽量保持功能行为没有副作用,尽管缓存等副作用通常都可以。
注 2:避免将属性用于计算成本高昂的操作;属性符号会让调用者认为访问(相对)便宜。
如果您的类打算被子类化,并且您有不想让子类使用的属性,请考虑使用双前下划线命名它们,而不使用尾部下划线。这会调用 Python 的名称改编算法,其中类的名称被改编为属性名称。这有助于避免在子类无意中包含同名属性时发生属性名称冲突。
注 1:请注意,在混乱的名称中仅使用简单类名,因此如果子类选择相同的类名和属性名,仍然会发生名称冲突。
注 2:名称修改可能会使某些用途(例如调试和getattr ())变得不那么方便。但是,名称修改算法有据可查,并且易于手动执行。
注 3:并非所有人都喜欢名称混淆。尝试在避免意外名称冲突的需求与高级调用者的潜在使用之间取得平衡。
解决方案 9:
import inspect
class Number:
def __init__(self, value):
self.my_private = value
def set_private(self, value):
self.my_private = value
def __setattr__(self, my_private, value):
f = inspect.stack()[1][3]
if f not in ['__init__', 'set_private']:
raise Exception("can't access private member-my_private")
# the default behavior
self.__dict__[my_private] = value
def main():
n = Number(2)
print(n.my_private)
n.set_private(3)
print(n.my_private)
if __name__ == '__main__':
main()
解决方案 10:
我使用 Python 2.7 和 3.5。我写了以下代码:
class MyOBject(object):
def __init__(self):
self.__private_field = 10
my_object = MyOBject()
print(my_object.__private_field)
运行并得到:
AttributeError:'MyOBject' 对象没有属性 '__private_field'
请参阅:
https://www.tutorialsteacher.com/python/private-and-protected-access-modifiers-in-python