Доступ к protected атрибутам наследуемого класса

У меня есть абстрактный класс SCConnection который наследуется в двух других классах. Приведу два простых примера

class SCConnection(ABC):

    __slots__ = (
        '_ip',
        '_port',
        '_timeout',
        '__connection',
    )

    def __init__(self, ip: str, port: int, timeout: int):
        self._ip = ip
        self._port = port
        self._timeout = timeout
        self.__connection = None

    @property
    def attributes(self) -> Tuple:
        return self._ip, self._port, self._timeout

    @abstractmethod
    def open_connection(self):
        raise NotImplementedError

    def set_connection(self, connection_object):
        if self.__connection is None:
           self.__connection = connection_object

Собственно у классов разница только в типе подключения. В одном случае это Telnet, в другом SSL. Это происходит в методе open_connection

class FirstSystem(SCConnection):

      def __init__(ip: str, port: int, timeout: int):
          super(FirstSystem, self).__init__(
            ip=ip,
            port=port,
            timeout=timeout 
      )

      def open_connection(self):
          ip, port, timeout = self.attributes
          telnet_client = Telnet(ip, port, timeout)
          self.set_connection(telnet_client)
class SecondSystem(SCConnection):

    def __init__(self, ip: str, timeout: int, port: int = 31114):
        super(SecondSystem, self).__init__(
            ip=ip, port=port, timeout=timeout
        )

    def open_connection(self):
        ip, port, timeout = self.attributes
        ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
        ssl_context.load_verify_locations(cafile=certifi.where())
        _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)

        _connection = ssl_context.wrap_socket(_socket)
        _connection.connect((ip, port))
        _connection.settimeout(timeout)
        self.set_connection(_connection)

В связи с этим вопрос, корректно ли так делать или лучше как-то по другому?


Ответы (1 шт):

Автор решения: insolor

Я считаю, что решение со свойством, которое возвращает кортеж из атрибутов, не очень удачное, вот почему:

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

Варианты решения:

  1. Сделать отдельные property для каждого параметра. Минус - придется вручную прописывать эти property.
  2. Сделать контейнер для набора параметров, например объект или namedtuple, из него получать значения по именам, пример:
from abc import ABC, abstractmethod
from collections import namedtuple


ConnectionParameters = namedtuple("ConnectionParameters", "ip,port,timeout")


class SCConnection(ABC):
    __slots__ = (
        '__connection_parameters'
        '__connection',
    )

    def __init__(self, ip: str, port: int, timeout: int):
        self.__connection_parameters = ConnectionParameters(ip, port, timeout)
        self.__connection = None

    @property
    def connection_parameters(self) -> ConnectionParameters:
        return self.__connection_parameters

    @abstractmethod
    def open_connection(self):
        raise NotImplementedError

    @property
    def connection(self):
        return self.__connection
    
    @connection.setter
    def connection(self, connection_object):
        if self.__connection is None:  # Тут лучше бы бросать исключение, а не молча игнорировать попытку перезаписи
           self.__connection = connection_object


class FirstSystem(SCConnection):
    def __init__(ip: str, port: int, timeout: int):
        super().__init__(
            ip=ip,
            port=port,
            timeout=timeout 
        )

    def open_connection(self):
        params = self.connection_parameters
        telnet_client = Telnet(params.ip, params.port, params.timeout)
        self.connection = telnet_client


class SecondSystem(SCConnection):
    def __init__(self, ip: str, timeout: int, port: int = 31114):
        super().__init__(
            ip=ip, port=port, timeout=timeout
        )

    def open_connection(self):
        params = self.connection_parameters
        ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
        ssl_context.load_verify_locations(cafile=certifi.where())
        _socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)

        connection = ssl_context.wrap_socket(_socket)
        connection.connect((params.ip, params.port))
        connection.settimeout(params.timeout)
        self.connection = connection

Для namedtuple, конечно, не декларируются типы значений полей, так что, если это критично, то нужно переделать на объект с немодифицируемыми полями, но все остальное останется без изменений.

→ Ссылка