【问题标题】:__setattr__ versus __slots__ for constraining attribute creation in Python__setattr__ 与 __slots__ 用于在 Python 中约束属性创建
【发布时间】:2013-12-22 18:27:39
【问题描述】:

我一直在阅读有关如何使 Python 类不那么动态的文章,特别是不允许用户动态创建新属性。我读过overloadding __setattr__ is a good way to do this__slots__ is not the way to go。一位post on this last thread 实际上建议__slots__ 可以破坏酸洗。 (谁能证实这一点?)

但是,我只是在阅读 Python 2.2 的最新消息,the attribute access section 实际上建议使用__slots__ 来限制属性创建,而不仅仅是像其他人建议的那样进行优化。就Python历史来说,有谁知道__slots__的初衷是什么?约束变量创建是要被滥用的功能还是错误?人们如何看待__slots__ 在实践中的使用?有很多人看到__setattr__ 重载以限制属性创建吗?哪个最好?如果您更熟悉一种方法或另一种方法,请随时发布您所知道的方法的优缺点。另外,如果您有其他解决问题的方法,请分享! (请尽量不要重复__slots__ 的缺点,这些缺点已在other threads 中表达。)

编辑:我希望避免讨论“为什么?”,但第一个答案表明这会出现,所以我会在这里说明。在有问题的项目中,我们使用这些类来存储“配置信息”,允许用户使用他们的(用户的)参数设置对象的属性,然后将对象传递给程序的另一部分。对象不仅仅是存储参数,所以字典不起作用。我们已经让用户不小心输入了错误的属性名称,最终创建了一个新属性,而不是设置程序期望的属性之一。这未被检测到,因此用户认为他们正在设置参数但没有看到预期的结果。这让用户感到困惑,而且很难发现。通过约束属性创建,将抛出异常而不是静默通过。

编辑2,重新酸洗:这些对象将是我们将来想要存储的东西,酸洗似乎是一种很好的方法。如果__slots__ 显然是最好的解决方案,我们可能会找到另一种存储它们的方法,但酸洗肯定是有价值的,应该考虑在内。

编辑 3:我还应该提到,内存保护不是问题。很少会创建这些对象,因此节省的任何内存都可以忽略不计(例如 3-12 GB 机器上的 10 千字节)。

【问题讨论】:

  • 我相信插槽的目的是(并且仍在使用)使类实例的内存占用更小。
  • 只需使用__slots__,它可以满足您的需求并节省内存,这很好
  • @piokuc -- 但它会破坏酸洗和其他任何依赖于具有已定义 __dict__ 的对象的东西,这并不完全不常见。此处的实际建议必须由 OP 打算使用此类来确定。当然,一个更公平的问题是为什么 OP 首先要限制属性创建?我们在这里都是成年人……
  • @mgilson 我同意了解更多关于特定类及其用途的信息会很有用,即并非每个对象都需要腌制
  • @mgilson 我添加了我为什么要约束属性创建的描述。作为一个普遍的问题,为什么这么多人似乎反对这一点?我意识到 Python 是一种动态语言,但很少有时候我想为现有对象添加新属性。许多库,例如 Numpy,不允许您向它们的类添加属性(我试过)。也许我只是有这种感觉,因为我来自 OOP 背景。

标签: python attributes slots setattr


【解决方案1】:

您为什么要限制开发人员这样做?除了“我不希望他们这样做”之外,还有什么技术原因吗?如果没有,请不要这样做。

无论如何,使用__slots__ 可以节省内存(没有__dict__),所以这是更好的解决方案。如果您的某些代码需要对象具有__dict__,您将无法使用它。

如果有充分的理由限制它(同样,你确定有一个吗?)或者你需要节省一点内存,去__slots__


阅读您的解释后,您可能想使用__setattr__,可能与__slots__ 结合使用(无论如何您都需要将属性白名单存储在某处,因此您也可以使用它来节省内存)。这样您就可以显示一些更有用的信息,例如可用的 similar 属性。一个可能的实现可能如下所示:

class Test(object):
    __slots__ = ('foo', 'bar', 'moo', 'meow', 'foobar')

    def __setattr__(self, name, value):
        try:
            object.__setattr__(self, name, value)
        except AttributeError:
            alts = sorted(self.__slots__, key=lambda x: levenshtein(name, x))
            msg = "object has no attribute '{}', did you mean '{}'?"
            raise AttributeError(msg.format(name, alts[0]))

我测试它的levenshtein() 是来自this site 的实现#4。对于不太聪明的用户,您可能希望使错误更加详细,并包括所有接近的匹配项,而不仅仅是第一个匹配项。

您可以通过创建仅包含 __setattr__ 方法的 mixin 类来进一步改进代码。这样您就可以将代码保留在您的真实类之外,并在必要时使用自定义 __setattr__(只需在 mixin 中使用 super(...).__setattr__(...) 而不是 object.__setattr__

【讨论】:

  • +1 反复要求 OP 考虑是否真的需要限制属性创建:)
  • 我在帖子中添加了问题描述。总而言之:是的,我确实有充分的理由这样做。我还应该指出,我们的许多用户不是开发人员,而是具有数学和科学背景但编程经验有限的人。
  • 我喜欢提供紧密名称匹配的想法;这对于拼写错误或添加“s”等非常有用。我可能最终不会使用插槽来存储属性列表,只是因为它会破坏 Pickling,但你是对的,它确实需要存储在某个地方。
  • @hunse 这是从 3 年开始的,我想强调的是,您可以使用 __setstate____getstate__ 轻松修复损坏的泡菜。我创建了一个gist 来演示。
【解决方案2】:

我对您的用例的建议是使用__setattr__ 并在使用Python 的warnings 模块无法识别属性名称时发出警告

【讨论】:

  • 我的建议是在创建类之前验证并将__setattr__ 排除在外。 warning 不过是个好电话。
  • 警告是一个好主意,特别是因为它们可以被视为错误或警告,甚至可以根据用户偏好被抑制。我对warnings 模块了解不多,因为我们过去曾使用记录器来发出警告,但我现在肯定会研究它。
猜你喜欢
  • 2018-02-16
  • 2018-08-18
  • 2017-06-11
  • 2023-03-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-19
  • 1970-01-01
相关资源
最近更新 更多