如何创建一个私有构造函数,该构造函数只能被类的静态函数调用,而不能从其他地方调用?
如何创建一个私有构造函数,该构造函数只能被类的静态函数调用,而不能从其他地方调用?
_
和__
前缀并不能提供一种将对象实例化限制在特定“工厂”的解决方案,但是Python是一个强大的工具箱,可以以多种方式实现所需的行为(正如@Jesse W at Z已经展示的那样)。
以下是一种可能的解决方案,它保持类的公开可见性(允许使用isinstance
等),但确保只能通过类方法进行构造:
class OnlyCreatable(object):
__create_key = object()
@classmethod
def create(cls, value):
return OnlyCreatable(cls.__create_key, value)
def __init__(self, create_key, value):
assert(create_key == OnlyCreatable.__create_key), \
"OnlyCreatable objects must be created using OnlyCreatable.create"
self.value = value
使用create
类方法构建对象:
>>> OnlyCreatable.create("I'm a test")
<__main__.OnlyCreatable object at 0x1023a6f60>
尝试构造一个对象而不使用create
类方法时,由于断言的存在,创建会失败。>>> OnlyCreatable(0, "I'm a test")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 11, in __init__
AssertionError: OnlyCreatable objects can only be created using OnlyCreatable.create
如果尝试通过模仿create
类方法创建对象失败,原因是编译器混淆了OnlyCreatable.__createKey
。>>> OnlyCreatable(OnlyCreatable.__createKey, "I'm a test")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'OnlyCreatable' has no attribute '__createKey'
构造OnlyCreatable
类外的对象,唯一的方法是知道OnlyCreatable.__create_key
的值。由于这个类属性的值是在运行时生成的,并且它的名称以双下划线为前缀标记为不可访问,因此实际上几乎“不可能”获取该值和/或构造对象。
OnlyCreatable._OnlyCreatable__create_key
。或者您可以使用-O
标志或PYTHONOPTIMIZE
环境变量禁用Python中的断言来运行代码。 - jonrsharpe__init__
中,将其改为 if 语句而不是断言 -- 断言很容易被禁用,在某些环境中也是如此。此外,您可以使用 is
替换 ==
相等测试,以使程序员的意图清晰明了。 - amka66class Hack: def __eq__(self, other): return True
来进行黑客攻击。如果您想让它适用于恶意对象,请使用is
。 - joel我如何创建私有构造函数?
实际上,这是不可能的,因为 Python 并不像其他面向对象语言那样使用构造函数,并且 Python 并没有强制执行隐私,它只有一种特定的语法来建议一个给定的方法/属性应该被视为私有。让我详细说明一下...
首先:在 Python 中最接近构造函数的是 __new__
方法,但这很少用到(通常使用 __init__
,它修改刚创建的对象(实际上它已经有 self
作为第一个参数了)。
尽管如此,Python 基于每个人都是“成年人”的假设,因此私有/公共并不像其他语言那样被强制执行。
如其他回答者所提到的,一般将需要“私有”的方法前加一个或两个下划线:_private
或 __private
。两者之间的区别在于后者会将方法名混淆,因此您将无法从对象实例化外部调用它,而前者则没有。
因此,例如,如果您的类 A
定义了 _private(self)
和 __private(self)
两个方法:
>>> a = A()
>>> a._private() # will work
>>> a.__private() # will raise an exception
通常情况下,你应该使用单个下划线。双下划线会让事情变得非常棘手,特别是在单元测试中。
希望对你有所帮助!
您可以在不同的作用域中控制哪些名称可见,而且有许多可用的作用域。以下是将类的构造限制为工厂方法的两种三种其他方法:
#Define the class within the factory method
def factory():
class Foo:
pass
return Foo()
或者
#Assign the class as an attribute of the factory method
def factory():
return factory.Foo()
class Foo:
pass
factory.Foo = Foo
del Foo
isinstance
检查),但它明显表明您不应直接实例化它。)#Assign the class to a local variable of an outer function
class Foo:
pass
def factory_maker():
inner_Foo=Foo
def factory():
return inner_Foo()
return factory
factory = factory_maker()
del Foo
del factory_maker
Foo
类变得不可能(至少,不使用至少一个魔术(双下划线)属性),但仍允许多个函数使用它(通过在删除全局Foo名称之前定义它们)。isinstance
检查会不可能,因为这个类是不可访问的(或者至少不应该被访问)。这可以通过让该类继承自一个可见的抽象基类来解决。 - Fred Foo虽然在Python中严格来说不存在私有属性,但是你可以使用元类来防止使用MyClass()
语法创建MyClass
对象。
下面是从Trio项目中改编的示例:
from typing import Type, Any, TypeVar
T = TypeVar("T")
class NoPublicConstructor(type):
"""Metaclass that ensures a private constructor
If a class uses this metaclass like this:
class SomeClass(metaclass=NoPublicConstructor):
pass
If you try to instantiate your class (`SomeClass()`),
a `TypeError` will be thrown.
"""
def __call__(cls, *args, **kwargs):
raise TypeError(
f"{cls.__module__}.{cls.__qualname__} has no public constructor"
)
def _create(cls: Type[T], *args: Any, **kwargs: Any) -> T:
return super().__call__(*args, **kwargs) # type: ignore
以下是一个使用示例:
from math import cos, sin
class Point(metaclass=NoPublicConstructor):
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def from_cartesian(cls, x, y):
return cls._create(x, y)
@classmethod
def from_polar(cls, rho, phi):
return cls._create(rho * cos(phi), rho * sin(phi))
Point(1, 2) # raises a type error
Point.from_cartesian(1, 2) # OK
Point.from_polar(1, 2) # OK
class Foo(ABC):
@abstractmethod
def bar(self) -> int:
...
class _SpecificFoo(Foo):
def __init__(self, bar: int):
self._bar = bar
def bar(self) -> int:
return self._bar
def specific_foo(bar: int) -> Foo:
return _SpecificFoo(bar)
注意
_SpecificFoo.__init__
和 specific_foo
可以有不同的签名。specific_foo
返回一个 Foo
,API 中没有提到 _SpecificFoo
。这与 Jesse 的第一种方法非常相似,但是每次调用 specific_foo
时类并不会被重新定义,并且类接口是静态类型的。
Foo
,但 _SpecificFoo
的受保护状态将强烈鼓励我们使用 specific_foo
实例化新实例。这也比那些实际上会在尝试使用构造函数时抛出错误的解决方案更符合 Python 风格。最好只是使用下划线来阻止它。我很惊讶这没有更多的赞成票。 - croncroncron如果你只是想简单地表示一个构造函数不应该直接使用,你可以按照以下模式来使构造函数默认使用时引发异常:
class Foo:
def __init__(self, arg, __private=False):
assert __private, "Constructor shouldn't be called directly"
self.arg = arg
@classmethod
def from_bar(cls, bar: Bar):
return cls(bar.arg, __private=True)
@classmethod
def from_baz(cls, baz: Baz):
return cls(baz.arg, __private=True)
Foo('abc', __private=True)
,但这会发出明确的信号,表明不支持这种行为,这符合Python的通用设计。PYTHONOPTIMIZE=TRUE
),Python会跳过检查断言。如果您仍希望进行此检查,可以考虑将assert
更改为if not __private: raise RuntimeError("...")
。 - Matthew D. Scholefield__private
可以通过使用 Unicode 字符使其更加晦涩(但可能不太可移植)。 - Dmytro__init__()
方法扮演了一个构造函数的角色,但它只是一个在对象已经被创建并需要初始化时调用的方法。_
或__
标记“私有”方法,例如:# inheriting from object is relevant for Python 2.x only
class MyClass(object):
# kinda "constructor"
def __init__(self):
pass
# here is a "private" method
def _some_method(self):
pass
# ... and a public one
def another_method(self):
pass
MyClass
的类,并在其中添加一个名为__my()
的方法。考虑使用mc_obj = MyClass()
实例化该类。您将能够通过mc_obj._MyClass__my()
访问__my()
方法。 - Zaur Nasiboveval()
的粉丝或者使用赋值语句,例如vars()['a'] = 10
而不是a = 10
。 - Tadeck__
方法。我想指出的是,“_”开头的方法名称与“__”不同,如果你还没有注意到,请参阅@gimel或@mac的答案。 - Tadeckfrom abc import ABC, abstractmethod
class Foo(ABC):
@property
@abstractmethod
def _a(self) -> int:
pass
def bar(self) -> int:
return self._a + 1
@classmethod
def from_values(cls, a: int) -> 'Foo':
class _Foo(cls):
def __init__(self, __a):
self.__a = __a
@property
def _a(self):
return self.__a
return _Foo(a)
Foo() # TypeError: Can't instantiate abstract class ...
Foo.from_values(1).bar() # 1
Foo
上不需要抽象属性,则调用Foo()
时将不会出现TypeError
。在这种情况下,您可以依赖于从ABC
继承的文档,或者为了安全起见定义一个虚拟属性。
class _Foo(cls):
_a = a
return _Foo()
__init__
请求一种只有工厂方法才知道的私密密码。
请参见下面的示例:
from __future__ import annotations
class Foo:
name: str
def __init__(self, check_private_constructor: str):
if check_private_constructor != "from_within":
raise RuntimeError("Please use named constructors!")
@staticmethod
def from_name(name: str) -> Foo:
r = Foo("from_within")
r.name = name
return r
@staticmethod
def from_index(index: int) -> Foo:
r = Foo("from_within")
r.name = str(index)
return r
def __str__(self):
return "Foo.name=" + self.name
# a = Foo("eee") # will raise an exception
a = Foo.from_name("Bob")
print(a)
b = Foo.from_index(3)
print(b)
__
的方法仍然可以被称为obj._ClassName__Method()
。现在,请您解释一下,这是如何实现私有的? - Zaur NasibovClassName
之外调用obj._ClassName__Method()
是正确的行为。 - Tadeck