Python中的私有构造函数

时间:2011-11-21 12:48:08

标签: python static constructor private

如何创建一个私有构造函数,该构造函数只能由类的静态函数调用而不能从else调用?

9 个答案:

答案 0 :(得分:27)

  

如何创建私有构造函数?

本质上,这是不可能的,因为如果你来自其他OOP语言并且python没有强制执行隐私,python不会像你认为的那样使用构造函数,它只是有一个特定的语法来建议给定的方法/属性应该被视为私有。让我详细说明......

首先:最接近你在python中可以找到的构造函数是__new__ method,但这很少使用(你通常使用__init__来修改刚刚创建的对象(事实上它已经self作为第一个参数。)

无论如何,python基于假设每个人都是同意的成人,因此私人/公共不像其他语言那样强制执行。

正如其他一些响应者所提到的那样,意味着“私有”的方法通常由一个或两个下划线加上:_private__private。两者之间的区别在于后者会扰乱方法的名称,因此您将无法从对象实例化之外调用它,而前者则不会。

例如,如果您的班级A同时定义了_private(self)__private(self)

>>> a = A()
>>> a._private()   # will work
>>> a.__private()  # will raise an exception

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

HTH!

答案 1 :(得分:14)

___前缀不提供限制对象实例化到特定工厂的解决方案,但Python是一个功能强大的工具箱,期望的行为可以通过多种方式实现(正如Z的@Jesse W所证明的那样)。 这是一个可以使类公开可见的可能解决方案(允许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的值。由于这个类属性的值是在运行时生成的,并且它的名称前缀为__标记为无法访问,因此它实际上是不可能的&#39;获取此值和/或构造对象。

答案 2 :(得分:9)

由于还没有人提到这一点 - 你可以对在什么范围内可见的名称有相当大的控制权 - 并且有批次的范围可用。以下是两个 三个将类的构造限制为工厂方法的其他方法:

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

OR

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

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

OR

#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名称之前定义它们。

答案 3 :(得分:7)

引用Python style guide (PEP 8)

  

此外,以下特殊形式使用前导或尾随       下划线被认可(这些通常可以与任何情况相结合       约定):

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

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

  •   
  • __double_leading_underscore:在命名类属性时,调用name     mangling(在FooBar类中,__boo变为_FooBar__boo;见下文。)

  •   
  • __double_leading_and_trailing_underscore__:“魔法”物品或     存在于用户控制的命名空间中的属性。例如。 __init__,     __import____file__。不要发明这样的名字;只使用它们     记录在案。

  •   

答案 4 :(得分:3)

首先,术语“构造函数”不适用于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

答案 5 :(得分:3)

尽管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

答案 6 :(得分:1)

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!'

如果你想要一个实例。

答案 7 :(得分:0)

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

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上不需要抽象属性,则在调用TypeError时不会得到Foo()。在这种情况下,您可以依赖于ABC的继承作为文档,或者为安全起见定义一个哑属性。

可能的调整

  • 需要可变的实例属性吗?添加二传手。
  • 不关心类和实例属性之间的区别吗?简化

    class _Foo(cls):
        _a = a
    
    return _Foo()
    

答案 8 :(得分:0)

如果模块级函数可以代替静态方法...

将整个类设为私有,将 API 作为抽象类公开,并使用函数实例化您的私有类

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 时重新定义该类

相关问题