Python中的私有构造函数

Python中的私有构造函数,python,static,constructor,private,Python,Static,Constructor,Private,如何创建一个只能由类的静态函数调用而不能从其他地方调用的私有构造函数?首先,术语“构造函数”不适用于Python,因为尽管\uuuu init\uuu()方法扮演一个角色,它只是一个方法,当对象已经创建并且需要初始化时调用它 Python中类的每个方法都是公共的。程序员通常在方法名称中用\uu或\u标记“私有”方法,例如: # inheriting from object is relevant for Python 2.x only class MyClass(object): #

如何创建一个只能由类的静态函数调用而不能从其他地方调用的私有构造函数?

首先,术语“构造函数”不适用于Python,因为尽管
\uuuu init\uuu()
方法扮演一个角色,它只是一个方法,当对象已经创建并且需要初始化时调用它

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

# 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
引述:

此外,下列特殊形式使用前导或尾随 下划线可以识别(通常可以与任何情况结合使用) 公约):

  • \u单个\u前导\u下划线
    :弱“内部使用”指示器。例如,M中的“
    ”
    导入*
    “不导入名称以下划线开头的对象

  • 单尾随下划线:按惯例使用,以避免与
    Python关键字,例如。
    
    Tkinter.Toplevel(master,class='ClassName')

  • \uuuuuuuuuuuuuuuuuuu前导下划线
    :命名类属性时,调用name 损坏(在类FooBar中,
    \uuboo
    变为
    \ufoobar\uboo
    ;见下文)

  • \uuuuu双\u前导\u和尾随\u下划线\uuuuuuu
    :“魔法”对象或 位于用户控制的名称空间中的属性。例如,
    \uuuuu init\uuuu
    \uuuu导入\uuuu
    \uuuu文件\uuuu
    。永远不要捏造这样的名字;只使用它们 如文件所述

如何创建私有构造函数

本质上,这是不可能的,因为python没有像您来自其他OOP语言时所认为的那样使用构造函数,也因为python没有强制隐私,它只是有一个特定的语法来建议将给定的方法/属性视为私有。让我详细说明一下

第一:python中最接近构造函数的是,但很少使用(通常使用
\uuuuu init\uuuu
,它修改刚刚创建的对象(实际上它已经将
self
作为第一个参数)

无论如何,python基于这样一个假设:每个人都是同意的成年人,因此private/public并不像其他语言那样被强制执行

正如其他一些响应者所提到的,应该是“私有”的方法通常用一个或两个下划线作为前缀:
\u private
\u private
。两者之间的区别在于后者会扰乱方法的名称,因此您将无法从对象实例化外部调用它,而前者不会

例如,如果类
A
同时定义了
\u private(self)
\u private(self)

您通常希望使用单下划线,因为-特别是在单元测试中-使用双下划线会使事情变得非常棘手


HTH!

因为还没有人提到过这一点——您可以在很大程度上控制哪些名称在哪些作用域中可见,并且有很多作用域可用。下面是另外两种将类的构造限制为工厂方法的方法:

#Define the class within the factory method
def factory():
  class Foo:
    pass
  return Foo()

(注意:这仍然允许从外部引用类(例如,对于
isinstance
检查),但很明显,您不应该直接实例化它。)

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

class MongoConn(object):
    @classmethod
    def instance(cls):
        if not hasattr(cls, '_instance'):
            cls._instance = cls()
        return cls._instance

    def __init__(self):
        assert not hasattr(self.__class__, '_instance'), 'Do not call constructor directly!'

如果您想要一个实例。

前缀
\uu
\u
不能提供将对象实例化限制为特定“工厂”的解决方案,但是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,创建失败。\uu 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'
>>>onlycreateable(onlycreateable.\uu createKey,“我是一个测试”)
回溯(最近一次呼叫最后一次):
文件“”,第1行,在
AttributeError:类型对象“OnlyCreatable”没有属性“\uu createKey”

在类方法之外构造
OnlyCreatable
的唯一方法是知道
OnlyCreatable.\uuuu create\u key
的值。由于该类属性的值是在运行时生成的,并且其名称的前缀是.\uuuuuu。将其标记为不可访问,因此实际上无法获取该值和/或构造对象

您可以通过抽象类实现这种效果。任何需要在“私有构造函数”中定义的实例属性都可以是抽象属性。然后,您的工厂类方法通过填充这些抽象属性以及执行任何其他初始化工作(如数据验证)来构建自己的具体类

from 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
的继承作为文档,或者为安全起见定义一个虚拟属性

可能的调整

  • 需要可变实例属性吗?添加setter
  • 不关心类和实例属性之间的差异吗?使用

    class _Foo(cls):
        _a = a
    
    return _Foo()
    
尽管严格保密
>>> 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
>>> 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'
from 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
class _Foo(cls):
    _a = a

return _Foo()
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