Как создать частный конструктор, который должен вызываться только статической функцией класса, а не из else где?
Частный конструктор в Python
Ответ 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()