Подтвердить что ты не робот

Частный конструктор в Python

Как создать частный конструктор, который должен вызываться только статической функцией класса, а не из else где?

4b9b3361

Ответ 1

Как создать частный конструктор?

В сущности, невозможно, потому что python не использует конструкторы так, как вы думаете, если это происходит из других языков ООП, и потому что python не обеспечивает конфиденциальность, он просто имеет определенный синтаксис предположить, что данный метод/свойство следует считать закрытым. Позвольте мне уточнить...

Во-первых: ближайший к конструктору, который вы можете найти в python, это метод __new__, но это очень редко используется (обычно вы используете __init__, которые изменяют только что созданный объект (на самом деле он уже имеет self в качестве первого параметра).

Независимо от того, что python основан на предположении, что все являются соглашающимися взрослыми, таким образом, частный/общественный не применяется, как это делает какой-либо другой язык.

Как упоминалось некоторыми другими респондентами, методы, предназначенные для "private", обычно добавляются одним или двумя символами подчеркивания: _private или __private. Разница между ними заключается в том, что последний будет скремблировать имя метода, поэтому вам не удастся вызвать его извне объекта-объекта, а первое не будет.

Так, например, если ваш класс A определяет как _private(self), так и __private(self):

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

Обычно вы хотите использовать одно подчеркивание, поскольку - особенно для модульного тестирования - наличие двойных подчеркиваний может сделать вещи очень сложными....

НТН!

Ответ 2

Префиксы _ и __ не предлагают решения, ограничивающего создание экземпляров объекта определенной "фабрикой", однако 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

Построение объекта с помощью метода класса 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. Так как это значение атрибута класса генерируется во время выполнения и к его имени добавляется префикс __, помечающий его как недоступный, фактически "невозможно" получить это значение и/или построить объект.

Ответ 3

Как никто еще не упомянул об этом, вы можете иметь значительный контроль над тем, какие имена видны в каких областях - и доступно много доступных областей. Вот два три других способа ограничить построение класса методом factory:

#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

Это делает невозможным (по крайней мере, не используя хотя бы одно свойство magic (double underscore)) для доступа к классу Foo, но все же позволяет использовать несколько функций (путем определения их перед удалением глобального имени Foo.

Ответ 4

Цитирование Руководство по стилю Python (PEP 8):

Кроме того, следующие специальные формы с использованием ведущих или конечных     признаются знаки подчеркивания (их обычно можно комбинировать с любым случаем     конвенции):

  • _single_leading_underscore: слабый индикатор "внутреннего использования". Например. "from M import *" не импортирует объекты, имя которых начинается с подчеркивания.

  • single_trailing_underscore_: используется конвенцией, чтобы избежать конфликтов с   Ключевое слово Python, например.    Tkinter.Toplevel(master, class_='ClassName')

  • __double_leading_underscore: при присвоении имени атрибуту класса вызывается имя   mangling (внутри класса FooBar, __boo становится _FooBar__boo, см. ниже).

  • __double_leading_and_trailing_underscore__: "магические" объекты или   атрибуты, которые находятся в управляемых пользователем пространствах имен. Например. __init__  __import__ или __file__. Никогда не изобретайте такие имена; использовать их только   как задокументировано.

Ответ 5

Кулак всего, термин "конструктор" не относится к Python, потому что, хотя метод __init__() играет роль одного, это просто метод, который вызывается, когда объект уже создан и требует инициализации.

Каждый метод класса в Python является общедоступным. Обычно программисты отмечают методы "private" с помощью _ или __ в имени метода, например:

# 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

Ответ 6

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

Вы можете достичь чего-то в этом направлении с помощью абстрактного класса. Любые атрибуты экземпляра, которые необходимо определить в "приватном конструкторе", могут быть абстрактными свойствами. Затем ваш метод класса фабрики создает собственный конкретный класс, заполняя эти абстрактные атрибуты, а также выполняя любые другие действия по инициализации, такие как проверка данных.

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