Когда НЕ использовать самосоглашение в Python?

Я совсем недавно обдумал соглашение self в Python и начал писать более сложный код. Однако один опытный программист и мой друг сказал мне, что использовать self для каждой переменной в методе класса расточительно.

Я понимаю, что self приведет к тому, что переменная будет отнесена к этому классу. Так будет ли правдой, что, если в этом нет необходимости, рекомендуется избегать использования self?

Ниже приведен код, который извлекает информацию о League of Legends из API и сохраняет каждую переменную в self.var_name, чтобы проиллюстрировать, как я (возможно, без необходимости) использую self.

async def getChampInfo(self, *args):
    """ Return play, ban, and win rate for a champ """
    self.uri = "http://api.champion.gg/v2/champions/{}?api_key={}"
    self.champ = " ".join(args)
    self.champID = lu.getChampID(self.champ)
    self.res = requests.get(self.uri.format(
        self.champID, League.champion_gg_api_key)).json()
    self.role = self.res[0]["role"]
    self.role_rate = self.res[0]["percentRolePlayed"]
    self.play_rate = self.res[0]["playRate"]
    self.win_rate = self.res[0]["winRate"]
    self.ban_rate = self.res[0]["banRate"]

person James    schedule 22.06.2017    source источник
comment
Какие из них вы когда-либо использовали вне этого метода? Полагаю, что первые четыре должны быть локальными переменными (шаблон uri может быть атрибутом класса), последние пять атрибутов.   -  person jonrsharpe    schedule 23.06.2017
comment
Думаю, я пропустил self.train, потому что действительно не ссылаюсь на эти переменные вне метода. Я должен быть в состоянии сократить код, чтобы теперь он имел больше смысла.   -  person James    schedule 23.06.2017


Ответы (2)


Есть случаи, когда использование self не требуется.

С верхней части моей головы:

  • когда переменная используется только в 1 функции или создается внутри функции/метода и используется только в этой функции/методе
  • когда переменная не должна быть разделена между методами
  • когда переменная не должна подвергаться воздействию других классов/областей/контекстов

Другой частичный ответ заключается в том, что при создании метакласса/фабрик/композиции что-то подобное может иметь больше смысла, чтобы отойти от соглашения об использовании self, например:

class Factory(object):
    def __init__(cls, *args, **kwargs):
        thing = cls(args, kwargs)

Я мог бы пропустить некоторые вещи здесь, но это то, о чем я могу думать в данный момент.

связанные с:

person jmunsch    schedule 22.06.2017
comment
Отлично, что помогает! Итак, в приведенном выше примере, если мне не нужен доступ к этим переменным вне экземпляра или метода, то это в основном расточительно. Я повозлюсь с удалением себя и посмотрю, как это пойдет оттуда. Ваше здоровье! - person James; 23.06.2017
comment
@James добавил еще одну ссылку в более подробное обсуждение использования self по соответствующим ссылкам. :) нп. - person jmunsch; 23.06.2017

self приведет к тому, что переменная будет отнесена к экземпляру класса, а не к самому классу. Я не знаю, имели ли вы это в виду или нет, но об этом, безусловно, стоит подумать.

Переменные в области видимости класса можно разделить на две категории: переменные класса и экземпляра. Переменные класса определяются в начале определения класса, вне любого метода. Если переменная постоянна для всех экземпляров или используется только в методах класса/статических, она должна быть переменной класса. Часто такие переменные являются настоящими константами, хотя во многих случаях это не так. Переменные экземпляра обычно определяются в __init__, но во многих случаях их следует определять в другом месте. При этом, если у вас нет веской причины не делать этого, определите переменные экземпляра в __init__, так как это сохранит ваш код (и класс) организованным. Вполне допустимо давать им значения-заполнители (например, None), если вы знаете, что переменная важна для состояния экземпляра, но ее значение не определено до тех пор, пока не будет вызван определенный метод.

Вот хороший пример:

class BaseGame:
    """Base class for all game classes."""

    _ORIGINAL_BOARD = {(0,0): 1, (2,0): 1, (4,0): 1, (6,0): 1, (8,0): 1,
                       (1,2): 1, (3,2): 1, (5,2): 1, (7,2): 1, (2,4): 1,
                       (4,4): 1, (6,4): 1, (3,6): 1, (5,6): 1, (4,8): 0}
    _POSSIBLE_MOVES = {(0,0): ((4,0),(2,4)),
                       (2,0): ((4,0),(2,4)),
                       (4,0): ((-4,0),(4,0),(2,4),(-2,4)),
                       (6,0): ((-4,0),(-2,4)),
                       (8,0): ((-4,0),(-2,4)),
                       (1,2): ((4,0),(2,4)),
                       (3,2): ((4,0),(2,4)),
                       (5,2): ((-4,0),(-2,4)),
                       (7,2): ((-4,0),(-2,4)),
                       (2,4): ((4,0),(2,4),(-2,-4),(2,-4)),
                       (4,4): ((-2,-4,),(2,-4)),
                       (6,4): ((-4,0),(-2,4),(-2,-4),(2,-4)),
                       (3,6): ((-2,-4),(2,-4)),
                       (5,6): ((-2,-4),(2,-4)),
                       (4,8): ((-2,-4),(2,-4))}
    started = False

    def __call__(self):
        """Call self as function."""
        self.started = True
        self.board = __class__._ORIGINAL_BOARD.copy()
        self.peg_count = 14
        self.moves = []

    @staticmethod
    def _endpoint(peg, move):
        """Finds the endpoint of a move vector."""
        endpoint = tuple(map(add, peg, move))
        return endpoint

    @staticmethod
    def _midpoint(peg, move):
        """Finds the midpoint of a move vector."""
        move = tuple(i//2 for i in move)
        midpoint = tuple(map(add, peg, move))
        return midpoint

    def _is_legal(self, peg, move):
        """Determines if a move is legal or not."""
        endpoint = self._endpoint(peg, move)
        midpoint = self._midpoint(peg, move)
        try:
            if not self.board[midpoint] or self.board[endpoint]:
                return False
            else:
                return True
        except KeyError:
            return False

    def find_legal_moves(self):
        """Finds all moves that are currently legal.

        Returns a dictionary whose keys are the locations of holes with
        pegs in them and whose values are movement vectors that the pegs
        can legally move along.
        """
        pegs = [peg for peg in self.board if self.board[peg]]
        legal_moves = {}
        for peg in pegs:
            peg_moves = []
            for move in __class__._POSSIBLE_MOVES[peg]:
                if self._is_legal(peg, move):
                    peg_moves.append(move)
            if len(peg_moves):
                legal_moves[peg] = peg_moves
        return legal_moves

    def move(self, peg, move):
        """Makes a move."""
        self.board[peg] = 0
        self.board[self._midpoint(peg, move)] = 0
        self.board[self._endpoint(peg, move)] = 1
        self.peg_count -= 1
        self.moves.append((peg, move))

    def undo(self):
        """Undoes a move."""
        peg, move = self.moves.pop()
        self.board[peg] = 1
        self.board[self._midpoint(peg, move)] = 1
        self.board[self._endpoint(peg, move)] = 0
        self.peg_count += 1

    def restart(self):
        """Restarts the game."""
        self.board = __class__._ORIGINAL_BOARD.copy()
        self.peg_count = 14
        self.moves.clear()

_ORIGINAL_BOARD и _POSSIBLE_MOVES — настоящие константы. Хотя started не является константой, поскольку ее значение зависит от того, был ли вызван метод __call__ или нет, его значение по умолчанию, False, является постоянным для всех экземпляров, поэтому я объявил его как переменную класса. Обратите внимание, что в __call__ (не беспокойтесь о том, почему я использовал __call__ вместо __init__) я переопределил его как переменную экземпляра, так как __call__ запускает игру, и поэтому при его вызове состояние экземпляра изменился с класса по умолчанию «не запущен» на «запущен».

Также обратите внимание, что другие методы, помимо __call__, регулярно изменяют значение переменных экземпляра, но они изначально не определены в указанных методах, поскольку для этого нет веских причин.

person Isaac Saffold    schedule 23.06.2017
comment
Я определенно хотел сослаться на экземпляр, так что вы правы. Я немного неуклюж в своей формулировке, так как на данный момент у меня нет полного понимания ООП (или любого программирования в этом отношении). По вашему мнению, если у меня есть URI, который останется постоянным, имеет смысл поместить его вне какой-либо функции, поскольку он будет константой? - person James; 23.06.2017
comment
Если класс и/или экземпляры должны получить к нему доступ, то да. Если для доступа к нему требуется только один метод, просто объявите его в этом методе. - person Isaac Saffold; 23.06.2017
comment
Я приведу вам хороший пример. Я использовал его для представления самой игры (в отличие от графики и второстепенных методов, используемых в игре) в реализации игры Triangle Peg Game от Cracker Barrel. - person Isaac Saffold; 23.06.2017
comment
Хорошо, я добавил это в конце своего ответа. Не стесняйтесь задавать любые вопросы. Изучение классов и ООП в первый раз было для меня намного сложнее, чем более простые предметы, такие как циклы и функции, но как только я освоился, я никогда не возвращался назад. Моя игра, например, не содержит функций, которые не определены в классе, хотя я мог бы определить их снаружи, если бы захотел. Однако функциональное программирование определенно имеет свое применение. Есть некоторые языки, такие как Java, которые по существу вынуждают вас использовать ООП, так что это еще одна веская причина изучить его, и изучить его хорошо. - person Isaac Saffold; 23.06.2017
comment
Эй, это действительно круто! Я действительно ценю, насколько это глубоко. Вы в значительной степени ответили на большинство вопросов, которые у меня были о соглашениях здесь. Мне удалось действительно очистить свой код с момента моего первоначального сообщения! Ваше здоровье - person James; 29.06.2017
comment
Рад, что смог быть полезен. - person Isaac Saffold; 30.06.2017