Как создать эквивалент частного конструктора в Python?Python

Программы на Python
Ответить
Anonymous
 Как создать эквивалент частного конструктора в Python?

Сообщение Anonymous »

На этом сайте есть несколько вопросов о том, как создать несколько конструкторов в Python — см. здесь, здесь, здесь и здесь — и все согласны с тем, что вам следует либо использовать в конструкторе необязательные аргументы/аргументы ключевых слов, чтобы придать ему разные значения. поведение или использовать методы фабричного класса. Но что, если мы хотим иметь возможность внутренне инициализировать объект данными, специфичными для реализации, не раскрывая его публично? В Java или C++ мы могли бы использовать частный конструктор, но конфиденциальность в Python, похоже, не очень хорошо сочетается с конструкторами. Я могу придумать несколько разных вариантов, но у каждого из них есть некоторые проблемы, о которых я объясню ниже.
Пример
Допустим, мы Мы собираемся создать структуру данных multiset (неупорядоченную коллекцию, допускающую повторяющиеся значения), которую мы реализуем с помощью dict, чтобы отслеживать количество раз появления каждого элемента. Нам нужен конструктор, который будет инициализировать набор списком значений, который может выглядеть примерно так:

Код: Выделить всё

class Multiset():
def __init__(self, iterable):
self._counts = {}
self._size = 0
for item in iterable:
self._counts[item] = self._counts.get(item, 0) + 1
self._size += 1
Пока все хорошо. Но что, если мы хотим иметь метод __add__, чтобы мы могли взять два мультимножества и объединить их? Вероятно, мы хотим сделать что-то вроде этого:

Код: Выделить всё

def __add__(self, other):
combined_counts = {item : self._counts.get(item, 0) + other._counts.get(item, 0)
for item in self._counts.keys() | other._counts.keys()}
combined_size = self._size + other._size
#return a new Multiset whose _counts is combined_counts and _size is combined_size
...но нам нужен способ создать новый объект непосредственно из словаря счетчиков, поскольку наш конструктор принимает только список элементов. Однако мы по-прежнему хотим сохранить словарь _counts как частную деталь реализации. Вот некоторые методы, которые мы могли бы рассмотреть:
1. Открытый конструктор, частный фабричный метод
Мы могли бы оставить конструктор как есть и создать частный фабричный метод, который создает новый объект, а затем напрямую устанавливает атрибуты:

Код: Выделить всё

@classmethod
def _from_counts(cls, counts, size):
result = cls([])
result._counts = counts
result._size = size
return result
Это нормально, но кажется бессмысленным вызывать метод __init__, когда мы все равно собираемся игнорировать все, что он делает. Кроме того, в этом случае у нас была очевидная «пустая» опция для инициализации, но для чего-то более сложного нам, возможно, придется неловко создавать произвольные аргументы.
2. Частный конструктор, общедоступный фабричный метод
Вместо этого мы могли бы изменить метод __init__ так, чтобы он напрямую принимал внутренние данные, и предоставить фабричный метод для внешнего использования.

Код: Выделить всё

# this is only intended to be called from inside our class
def __init__(self, counts, size):
self._counts = counts
self._size = size

# this is part of the public API
@classmethod
def of(cls, iterable):
...
Теперь внешне нам нужно создать объект с помощью Multiset.of(...) вместо Multiset(...), что не идеально . Что еще более важно, обычный шаблон обозначения частных методов с помощью подчеркивания не работает для конструктора - нам приходится либо полагаться исключительно на документацию, чтобы указать, что конструктор не должен быть частью общедоступного API, либо что-то делать. сложнее, как описано в ответах на этот вопрос.
3. Конструктор с ключевыми аргументами
Мы могли бы добавить в метод __init__ функциональность для создания объекта любым способом в зависимости от того, какие аргументы ему переданы:

Код: Выделить всё

def __init__(self, iterable=None, counts=None, size=None):
if counts is not None:
self._counts = counts
self._size = size
else:
#initialize with the items in iterable
Здесь также возникает большая проблема с путаницей между публичным и приватным — теперь у нас есть функция, поведение которой гарантировано для одних входных данных, но не для других.
4. Закрытый фабричный метод, который вызывает __new__ напрямую
Как и вариант 1, но мы можем создать объект без его инициализации, а затем напрямую заполнить его данными:

Код: Выделить всё

@classmethod
def _from_counts(cls, counts, size):
result = cls.__new__(cls)
result._counts = counts
result._size = size
return result
Это позволяет избежать ненужного вызова __init__ и в целом кажется, что это наиболее точно отражает желаемое поведение. Но поскольку он позволяет избежать стандартной процедуры создания объекта, у него могут быть недостатки, о которых я не знаю.
Вопрос
Лучше ли какой-либо из этих методов, чем остальные, или есть другой способ, который я не рассмотрел? Кажется, это достаточно распространенный вариант использования, и другие уже разработали решение, но я не смог найти никакой информации о нем в Интернете.

Подробнее здесь: https://stackoverflow.com/questions/791 ... -in-python
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Python»