Python中的私有构造函数

use*_*793 63 python static constructor private

如何创建一个私有构造函数,只能通过类的静态函数调用而不是从其他位置调用?

mac*_*mac 30

如何创建私有构造函数?

从本质上讲,这是不可能的,因为如果你来自其他OOP语言并且因为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
Run Code Online (Sandbox Code Playgroud)

您通常希望使用单个下划线,因为 - 特别是对于单元测试 - 具有双下划线可能会使事情变得非常棘手....

HTH!

  • 问题是关于私有构造函数,而不是私有成员函数.你刚才解释了如何拥有私人功能. (5认同)
  • @mac如果是这样,那么你的回答应该是"这是不可能的.",而不是你写的,因为问题显然是关于构造函数,而不是函数.或者,如果你能想出一种获得预期效果的方法,那就是答案. (4认同)
  • 整个“同意成人”的模因需要消亡。私有修饰符存在的原因是因为对象旨在成为一种状态机。在 OOP 中,方法和成员是紧密耦合的。之所以创建私有成员,是因为在公共接口之外更改它可能会产生不明显的副作用,从而导致难以追踪错误。 (3认同)
  • +1这是正确的答案!:)实际上你可以从课外调用私有方法,但你不应该这样做,所以你的答案是可以的.谢谢. (2认同)

小智 25

___前缀不提供解决方案限制的对象到特定的"工厂"的实例化,然而Python是一个功能强大的工具箱,并且可以在多于一种方式来实现所期望的行为(如@Jesse W上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
Run Code Online (Sandbox Code Playgroud)

使用create类方法构造对象:

>>> OnlyCreatable.create("I'm a test") 
<__main__.OnlyCreatable object at 0x1023a6f60>
Run Code Online (Sandbox Code Playgroud)

在尝试构造对象而不使用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
Run Code Online (Sandbox Code Playgroud)

如果尝试通过模仿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'
Run Code Online (Sandbox Code Playgroud)

OnlyCreatable类方法之外构造的唯一方法是知道它的值OnlyCreatable.__create_key.由于此类属性的值是在运行时生成的,并且其名称前缀为__标记为无法访问,因此实际上"不可能"获取此值和/或构造对象.

  • 并非不可能,它是`OnlyCreatable._OnlyCreatable__create_key`。或者,您可以使用`-O`或`PYTHONOPTIMIZE` env var禁用断言来运行Python。 (2认同)
  • 这与Python拥有“强大的工具箱”无关。任何语言都可以实现这一点。在任何真正的 OOP 中,它被认为是一种可怕的做法的原因是,在实际运行代码之前,无法识别实例化类是否抛出异常。因此,在这个用例中,最小惊讶原则被严重打破了。 (2认同)

Jes*_*t Z 10

正如没人提到的那样 - 你可以对在什么范围内可见的名称有相当大的控制权- 并且有很多可用的范围.以下是另外 三种将类的构造限制为工厂方法的方法:

#Define the class within the factory method
def factory():
  class Foo:
    pass
  return Foo()
Run Code Online (Sandbox Code Playgroud)

要么

#Assign the class as an attribute of the factory method
def factory():
  return factory.Foo()
class Foo:
  pass
factory.Foo = Foo
del Foo
Run Code Online (Sandbox Code Playgroud)

(注意:这仍然允许从外部引用类(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
Run Code Online (Sandbox Code Playgroud)

这使得不可能(至少不使用至少一个魔术(双下划线)属性)来访问Foo该类,但仍允许多个函数使用它(通过在删除全局Foo名称之前定义它们).

  • 第二个选项允许isinstance检查,通过将类称为factory.Foo (2认同)

gim*_*mel 6

引用Python样式指南(PEP 8):

此外,还会识别使用前导或尾部下划线的以下特殊形式(这些形式通常可与任何案例约定结合使用):

  • _single_leading_underscore:弱"内部使用"指标.例如," from M import *"不会导入名称以下划线开头的对象.

  • single_trailing_underscore_:用于避免与Python关键字冲突的约定,例如 Tkinter.Toplevel(master, class_='ClassName')

  • __double_leading_underscore:当命名一个类属性时,调用名称修改(在类FooBar中,__boo变为_FooBar__boo;见下文).

  • __double_leading_and_trailing_underscore__:生成在用户控制的命名空间中的"魔术"对象或属性.例如__init__, __import____file__.不要发明这样的名字; 只记录使用它们.

  • 我不知道这是如何回答这个问题的.它没有说构造函数. (7认同)
  • 但构造函数具有固定名称,因此您不能仅根据约定附加下划线. (7认同)

Noé*_*ein 6

尽管 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
Run Code Online (Sandbox Code Playgroud)

下面是一个使用示例:

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
Run Code Online (Sandbox Code Playgroud)


Zau*_*bov 2

首先,术语“构造函数”不适用于Python,因为虽然__init__()方法扮演了一个角色,但它只是一个在对象已经创建并需要初始化时调用的方法。

Python 中类的每个方法都是公共的。通常,程序员用方法名称___方法名称来标记“私有”方法,例如:

# 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
Run Code Online (Sandbox Code Playgroud)

  • @Tadeck,这不是真的,BasicWolf 是正确的。以双下划线为前缀的方法不是私有的 - 它们是名称损坏的,但仍然可以从类外部访问它们。 (4认同)