【问题标题】:When to NOT use the self convention in Python?什么时候不要在 Python 中使用 self 约定?
【发布时间】:2017-06-23 08:51:48
【问题描述】:

我最近刚刚开始了解 Python 中的 self 约定,并开始编写更复杂的代码。然而,一位经验丰富的程序员和我的朋友告诉我,对类方法中的每个变量都使用self 是一种浪费。

我了解self 将导致变量归属于该类。那么,除非有需要,否则避免使用self是一种好习惯吗?

下面是一些从 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"]

【问题讨论】:

  • 那么,您曾经该方法之外使用过哪些方法?我猜我会说前四个应该是局部变量(uri 模板可能是一个类属性),最后五个属性。
  • 我想我错过了self.train,因为我真的在方法之外引用这些变量。我现在应该能够修剪代码以使其更有意义。

标签: python memory-management styles self convention


【解决方案1】:

有些情况下不需要使用self

在我的头顶:

  • 当变量仅在 1 个函数中使用,或在函数/方法中创建且仅在该函数/方法中使用时
  • 当变量不需要在方法之间共享时
  • 当变量不需要暴露给其他类/作用域/上下文时

另一个部分答案是,在创建元类/工厂/组合时,摆脱使用self 的惯例可能更有意义,例如:

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

我可能在这里遗漏了一些东西,但这些是我目前能想到的。

相关:

【讨论】:

  • 非常有帮助!所以在上面的例子中,如果我不需要在实例或方法之外访问那些变量,那基本上就是浪费了。我会修补删除 self ,看看它是如何从那里开始的。干杯!
  • @James 在相关链接下添加了另一个链接,以更深入地讨论self 的用法。 :) np.
【解决方案2】:

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__ 开始游戏,因此当它被调用时,instance 的 状态已从类默认值“未启动”更改为“已启动”。

还要注意,除了__call__ 之外的其他方法会定期更改实例变量的值,但它们最初并未在所述方法中定义,因为没有令人信服的理由。

【讨论】:

  • 我确实是想引用实例,所以你是对的。我的措辞有点笨拙,因为此时我还没有完全掌握 OOP(或任何与此相关的编程)。就您而言,如果我有一个保持不变的 URI,那么将它放在任何函数之外是否有意义,因为它将是一个常数?
  • 如果类和/或实例需要访问它,那么可以。如果只有一个方法需要访问它,那么只需在该方法中声明它即可。
  • 我会给你一个很好的例子。我在 Cracker Barrel 的 Triangle Peg Game 的实现中使用它来表示游戏本身(与游戏使用的图形和非必要方法相对)。
  • 好的,我在答案的末尾添加了它。随意问任何问题。第一次学习类和 OOP 对我来说比循环和函数等更基础的科目要困难得多,但是一旦我掌握了窍门,我就再也没有回头。例如,我的游戏不包含未在类中定义的函数,尽管如果我愿意,我可以在外部定义它们。不过,函数式编程肯定有它的用途。有一些语言,比如 Java,本质上会强制你使用 OOP,所以这是学习它的另一个很好的理由,并且学得好。
  • 嘿,这真的很酷!我真的很欣赏这是多么深入。你几乎已经涵盖了我对这里的约定的大多数问题。自从我最初发布以来,我已经设法真正清理了我的代码!干杯
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-11-07
  • 2012-06-04
  • 1970-01-01
  • 2013-08-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多