Private Constructor in Python

前端 未结 8 1973
栀梦
栀梦 2020-12-13 23:19

How do I create a private constructor which should be called only by the static function of the class and not from else where?

相关标签:
8条回答
  • 2020-12-13 23:23
    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!'
    

    If you want a single instance.

    0 讨论(0)
  • 2020-12-13 23:29

    Quoting the Python style guide (PEP 8):

    In addition, the following special forms using leading or trailing underscores are recognized (these can generally be combined with any case convention):

    • _single_leading_underscore: weak "internal use" indicator. E.g. "from M import *" does not import objects whose name starts with an underscore.

    • single_trailing_underscore_: used by convention to avoid conflicts with Python keyword, e.g. Tkinter.Toplevel(master, class_='ClassName')

    • __double_leading_underscore: when naming a class attribute, invokes name mangling (inside class FooBar, __boo becomes _FooBar__boo; see below).

    • __double_leading_and_trailing_underscore__: "magic" objects or attributes that live in user-controlled namespaces. E.g. __init__, __import__ or __file__. Never invent such names; only use them as documented.

    0 讨论(0)
  • 2020-12-13 23:35

    As no-one has mentioned this yet -- you can have considerable control over what names are visible in what scopes -- and there are lots of scopes available. Here are two three other ways to limit construction of a class to a factory method:

    #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
    

    (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

    #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
    

    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.

    0 讨论(0)
  • 2020-12-13 23:36

    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:

    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
    

    Constructing an object with the create class-method:

    >>> OnlyCreatable.create("I'm a test") 
    <__main__.OnlyCreatable object at 0x1023a6f60>
    

    When attempting to construct an object without using the create class-method creation fails due to the assertion:

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

    If attempting to create an object by mimicking the create class-method creation fails due to compiler mangling of 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'
    

    The only way to construct OnlyCreatable outside of a class-method is to know the value of OnlyCreatable.__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.

    0 讨论(0)
  • 2020-12-13 23:36

    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.:

    # 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
    
    0 讨论(0)
  • 2020-12-13 23:41

    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.

    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
    

    If you find you need no abstract attributes on Foo, you won't get the TypeError when calling Foo(). In that case you can either rely on the inheritance from ABC as documentation, or define a dummy attribute for safety.

    Possible tweaks

    • Need mutable instance attributes? Add setters.
    • Don't care about the difference between class and instance attributes? Simplify with

      class _Foo(cls):
          _a = a
      
      return _Foo()
      
    0 讨论(0)
提交回复
热议问题