Python 中的私有构造函数
如何创建一个私有构造函数,该构造函数只能由类的静态函数调用,而不是从其他地方调用?
How do I create a private constructor which should be called only by the static function of the class and not from else where?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(15)
如果您想要单个实例。
If you want a single instance.
_
和__
前缀不提供将对象实例化限制到特定“工厂”的解决方案,但是 Python 是一个强大的工具箱,所需的行为可以是通过不止一种方式实现(正如 Z 的 @Jesse W 所证明的那样)。这是一个可能的解决方案,可以使类保持公开可见(允许 isinstance 等),但确保只能通过类方法进行构造:
使用
create
类构造对象 -方法:尝试在不使用
create
类方法的情况下构造对象时,由于断言,创建失败:如果尝试通过模仿
create
类方法来创建对象由于
OnlyCreatable.__createKey
的编译器损坏,创建失败。在类方法之外构造
OnlyCreatable
的唯一方法是了解OnlyCreatable.__create_key
的值。由于该类属性的值是在运行时生成的,并且其名称以 __ 为前缀,将其标记为不可访问,因此实际上“不可能”获取该值和/或构造对象。The
_
and__
prefixes don't offer a solution to restricting instantiation of an object to a specific 'factory', however Python is a powerful toolbox and the desired behaviour can be achieved in more than one way (as @Jesse W at Z has demonstrated).Here is a possible solution that keeps the class publicly visible (allowing
isinstance
etc.) but ensures construction is only possible by class-methods:Constructing an object with the
create
class-method:When attempting to construct an object without using the
create
class-method creation fails due to the assertion:If attempting to create an object by mimicking the
create
class-methodcreation fails due to compiler mangling of
OnlyCreatable.__createKey
.The only way to construct
OnlyCreatable
outside of a class-method is to know the value ofOnlyCreatable.__create_key
. Since this class-attribute's value is generated at runtime and it's name is prefixed with __ marking it as inaccessible it is effectively 'impossible' to obtain this value and/or construct the object.从本质上讲,这是不可能,因为 python 并不像您来自其他 OOP 语言时所想象的那样使用构造函数,而且因为 python 不强制隐私,它只是有一个特定的语法来建议给定的方法/属性应该被视为私有的。让我详细说明一下...
首先:您可以在 python 中找到的最接近构造函数的是
__new__
方法 但这很少使用(通常使用__init__
,它修改刚刚创建的对象(实际上它已经有self
作为第一个参数)。无论如何,Python 是基于每个人都是一个假设的。 同意成人,因此私有/公共并不像其他一些
响应者提到的那样强制执行,意味着“私有”的方法通常前面加上一个或两个下划线:<。 code>_private 或
__private
两者的区别在于后者会打乱方法的名称,因此您将无法从对象实例化外部调用它,而前者没有。例如,如果您的类
A
定义了_private(self)
和__private(self)
:您通常希望使用单个下划线,如下所示- 特别是对于单元测试 - 有双下划线会让事情变得非常棘手......
HTH!
In essence, it's impossible both because python does not use constructors the way you may think it does if you come from other OOP languages and because python does not enforce privacy, it just has a specific syntax to suggest that a given method/property should be considered as private. Let me elaborate...
First: the closest to a constructor that you can find in python is the
__new__
method but this is very very seldom used (you normally use__init__
, which modify the just created object (in fact it already hasself
as first parameter).Regardless, python is based on the assumption everybody is a consenting adult, thus private/public is not enforced as some other language do.
As mentioned by some other responder, methods that are meant to be "private" are normally prepended by either one or two underscores:
_private
or__private
. The difference between the two is that the latter will scramble the name of the method, so you will be unable to call it from outside the object instantiation, while the former doesn't.So for example if your class
A
defines both_private(self)
and__private(self)
:You normally want to use the single underscore, as - especially for unit testing - having double underscores can make things very tricky....
HTH!
尽管 Python 中不存在严格的私有属性,但您可以使用元类来防止使用
MyClass()
语法来创建MyClass
对象。以下是改编自 Trio 项目的示例:
这是一个使用示例:
Though strictly private attributes do not exist in Python, you can use a metaclass to prevent the use of the
MyClass()
syntax to create aMyClass
object.Here is an example adapted from the Trio project:
Here is an example of use:
您可以对哪些名称在哪些范围内可见有相当大的控制权——并且有很多可用的范围。这里有
两种三种其他方法来将类的构造限制为工厂方法:OR
(注意:这仍然允许从外部引用该类(对于
isinstance
检查,例如),但很明显您不应该直接实例化它。)或者
这使得它不可能(至少,不使用至少一个神奇的(双下划线)属性)来访问
Foo
类,但仍然允许多个函数使用它(通过在删除全局 Foo 名称之前定义它们)。You can have considerable control over what names are visible in what scopes -- and there are lots of scopes available. Here are
twothree other ways to limit construction of a class to a factory method:OR
(Note: This still allows the class to be referred to from outside (for
isinstance
checks, for example), but it makes it pretty obvious that you aren't supposed to instantiate it directly.)OR
This makes it impossible (at least, without using at least one magic (double underscore) property) to access the
Foo
class, but still allows multiple functions to use it (by defining them before deleting the global Foo name.如果使用模块级函数而不是静态方法就可以...
将整个类设为私有,将 API 作为抽象类公开,并使用函数来实例化您的私有类
注意
_SpecificFoo()
和specific_foo()
可以有不同的签名specific_foo()
返回一个Foo
。 API 没有提及_SpecificFoo
这与 Jesse 的第一个选项非常相似,但每次调用
specific_foo
时不会重新定义该类,并且类接口是静态类型的。If a module-level function is OK instead of a static method ...
Make the whole class private, expose the API as an abstract class, and use a function to instantiate your private class
Note
_SpecificFoo()
andspecific_foo()
can have different signaturesspecific_foo()
returns aFoo
. The API makes no mention of_SpecificFoo
This is very similar to Jesse's first option, but the class isn't redefined on each call to
specific_foo
, and the class interface is statically typed.引用 Python 风格指南 (PEP 8):
Quoting the Python style guide (PEP 8):
首先,“构造函数”这个术语不适用于Python,因为尽管
__init__()
方法起到了一个作用,但它只是一个在创建对象时调用的方法。并且需要初始化。Python 中类的每个方法都是公共的。通常程序员在方法名称中使用
_
或__
标记“私有”方法,例如:Fist of all, the term "constructor" does not apply to Python, because, although
__init__()
method plays a role of one, it is just a method which is called when an object has already been created and requires initialization.Every method of a class in Python is public. Generally programmers mark "private" methods with
_
or__
in the name of a method, e.g.:其他答案提到Python中不存在“私有”构造函数(或一般的访问修饰符)之类的东西。虽然这是事实,但您仍然可以利用
__
前缀行为,通过使用双下划线前缀“隐藏”嵌套类中的实际实现来实现相同的最终效果:Other answers have mentioned that there is no such thing as "private" constructors (or access modifiers in general) in Python. While this is true, you can still take advantage of the
__
prefixing behavior to accomplish the same end effect by "hiding" the actual implementation in a nested class with a double-underscore prefix:如果模块级函数而不是静态方法可以,请参阅我的其他答案
您可以通过以下方式实现此效果一个抽象类。任何需要在“私有构造函数”中定义的实例属性都可以是抽象属性。然后,您的工厂类方法通过填充这些抽象属性以及执行任何其他初始化工作(例如数据验证)来构建自己的具体类。
如果您发现
Foo
不需要抽象属性,则在调用Foo()
时不会出现TypeError
。在这种情况下,您可以依赖ABC
的继承作为文档,或者定义一个虚拟属性以确保安全。可选调整
If a module-level function is OK instead of a static method, see my other answer
You can achieve something to this effect with an abstract class. Any instance attributes that need defining in the "private constructor" can be abstract properties. Your factory class method then builds its own concrete class by populating these abstract attributes, as well as doing any other initialisation work such as data validation.
If you find you need no abstract attributes on
Foo
, you won't get theTypeError
when callingFoo()
. In that case you can either rely on the inheritance fromABC
as documentation, or define a dummy attribute for safety.Optional tweaks
如果您只是想要一种简单的方法来表明不应直接使用构造函数,则可以遵循以下模式以使构造函数的默认用法引发异常:
当然,任何人仍然可以执行
Foo ('abc', __private=True)
但它会发出一个明确的信号,表明不支持这种行为,这遵循 Python 的一般设计。If you just want a simple way to signal that a constructor isn't supposed to be used directly, you can follow the following pattern to make the default usage of the constructor raise an exception:
Of course, anyone can still do
Foo('abc', __private=True)
but it would send a clear signal that such behavior isn't supported which follows the general design of Python.我知道,我迟到了,但我们可以这样做来创建私有构造函数。
I know, I'm late but we can do something like this to create private constructor.
另一种解决方案是让
__init__
请求一种只有工厂方法才能知道的私有秘密。请参阅下面的示例:
Another solution, is to let
__init__
ask for a kind of private secret that only factory methods will know.See example below:
一个清晰且轻量级的解决方案是使构造函数参数成为必需的但“私有”。只有当您也将它们设置为仅限关键字时,这才真正有效,否则很容易错过
这里,
Foo(_x=1)
表明我们滥用了 API。create
中的Foo(_x=x)
是否违反了类自己的契约是有争议的。A clear and lightweight solution is to make the constructor arguments required but "private". This only really works if you also make them keyword-only, else it's too easy to miss
Here,
Foo(_x=1)
indicates we're misusing the API.It's debatable whether
Foo(_x=x)
increate
is breaking the class's own contract.我认为这可能是一种更直接的方法。
现在,如果我调用,
将返回以下 AssertionError
如果我调用,
将返回以下内容:
I think this is maybe a more straightforward approach.
Now if I call
The following AssertionError is returned
If I call
The following is returned: