【问题标题】:Python static immutable propertiesPython 静态不可变属性
【发布时间】:2020-03-24 03:11:41
【问题描述】:

在 Python 中实现静态不可变属性的正确方法是什么?

小例子:

程序模块“家庭”有一个类Parent,定义如下:

class Parent():
    def __init__(self, type):
        self.type = type

父级可以是以下两种字符串类型之一:'mother''father'。该类的外部用户应该能够在构造时设置类型,然后查询parent.type 以获取这两个值中的任何一个。此外,Family 模块的其他部分使用Parent 类并依赖于返回的这两个值中的任何一个。因此,对类型的要求如下:

  • 可供用户外部和家庭模块内部使用
  • 始终是“母亲”或“父亲”,因此基于不可变变量

一种天真的方法可以通过传递字符串来鼓励设置类型:

parent = Parent('mother')

但这允许意外拼写错误(例如Parent('omther'))。为了防止这种情况,我采用了以下方法:

class Parent():
    TYPE_MOTHER = 'mother'
    TYPE_FATHER = 'father'
    TYPES = [TYPE_MOTHER, TYPE_FATHER]

    def __init__(self, type):
        assert type in self.TYPES, ('Invalid type "%s"' % type)
        self.type = type

parent = Parent(Parent.TYPE_MOTHER)

但是,没有什么能阻止用户随意更改这些静态变量,例如:

parent = Parent('mother')
Parent.TYPE_MOTHER = 'burrito'
print(parent.type == Parent.TYPE_MOTHER)
#> False

为了解决这个问题,我考虑使用@staticmethod@property 注释:

class Parent():
    @property
    @staticmethod
    def TYPE_MOTHER():
        return 'mother'

    @property
    @staticmethod
    def TYPE_FATHER():
        return 'father'

这不会阻止用户将字符串传递给构造函数(例如Parent('mother')),但至少可以防止他们通过更改父类型来搞砸 Family 模块。


我在使用这种方法时遇到的问题:

  • 很丑,4行代码而不是1行
  • 感觉很老套(可能是由于我在其他语言中使用受语言支持的私有静态变量的经验)
  • 我的 IDE 的 linter 不喜欢它,因为没有“self”参数(即使提供了)

您的问题:

  • 这个方法是 Pythonic 的吗?
  • 有更好的方法吗?
  • 您能否建议一种模式来实现这种不变性,同时强制用户仅使用我希望他们使用的变量,防止他们将原始字符串传递给构造函数?

【问题讨论】:

标签: python design-patterns interface immutability static-variables


【解决方案1】:

这不就是课程的用途吗?只需要继承 MotherFather 类即可:

class Parent():
    def __init__(self, type):
        self.type = type

class Mother(Parent):
    def __init__(self):
        super().__init__('mother')

class Father(Parent):
    def __init__(self):
        super().__init__('father')

parent = Mother()

【讨论】:

  • 一个有趣的方法。不过,如果有 20 种类型,你会说这仍然可行吗?
  • 或者如果类的此类类型属性有多种变体(诚然不包括在原始问题中)?例如,parent.type 是“母亲”或“父亲”,但 Parent 也可以有 parent.character 是“好”、“中性”或“坏”,parent.vehicle 是“汽车”、“摩托车”、“没有'。等等。然后你不需要排列这些类型中的每一个来覆盖每个变体吗?
  • 我认为您需要的是某种 Builder 模式,例如:parent = parentBuilder().makeMother().withCharacterGood().withVehicleMotorBike().get()
  • 是的,可以。与我提出的解决方案相比,您会怎么说?
  • 使用我提出的解决方案,那么您的用户只会看到我的示例中的方法链接。您仍然可以按照您的建议使用字符串和枚举,但是方法链接可以隐藏这一点,让您随时更改确切的细节。 (想象一下,如果您将 TYPE_BIKE 重命名为 TYPE_MOTORBIKE 等,您的用户会经历怎样的恐惧)
【解决方案2】:

总体而言,方法很好,只是有点长。如果您不想实现继承,最简单的方法是让TYPES 列表成为受保护的类变量:

class Parent():
    def __init__(self, type):
        self._TYPES = ['mother', 'father']
        assert type in self._TYPES, ('Invalid type "%s"' % type)
        self.type = type

【讨论】:

  • 谢谢,是的,鉴于 Parent._TYPES 仍然可以(尽管不应该)被修改,这将在用户和滥用之间建立约定。另一方面,它在构造类时促进了不需要的字符串传递,因为在这种情况下,'mother' 和 'father' 按照惯例在外部是不可用的
  • 真的!然后可能使 TYPES 列表成为只有一个 getter 的属性,这至少可以解决重复问题。
【解决方案3】:

我为我的问题找到了一个整洁的答案 - Enum class (doc)

from enum import Enum
class Parent():
    class TYPES(Enum):
        MOTHER = 'mother'
        FATHER = 'father'

    def __init__(self, type:TYPES):
        assert type in self.TYPES, ('Invalid type "%s"' % type)
        self.type = type

parent = Parent(Parent.TYPES.MOTHER)

在这种情况下,用户仍然可以覆盖Parent.TYPES。除了使用__setattr__ 并捕获恶意写入之外,我想不出另一种方法来防止它。如果你能想到这里分享你的想法。

这种模式提供以下好处:

  • 由于 Enum 的政策,无法覆盖单个属性 MOTHER 和 FATHER
  • 由于__setattr__,无法覆盖类型
  • 无法使用原始字符串代替 Parent.TYPES.MOTHER(或 .FATHER)
  • 兼容assert type in self.TYPESassert isinstance(type, self.TYPES)

到目前为止,我可以看到两个用法上的不同:

  • 如果我想获得实际值 - 即。 “母亲”或“父亲”,那么我需要使用枚举的.value 属性。所以我需要使用parent.type.value 而不是parent.type(它将返回"TYPES.MOTHER"),它正确返回'mother'
  • 要打印TYPES的内容我需要使用list(self.TYPES)

我认为使用 Enum 我也可以放弃 ALL_CAPS 标准,因为我不需要表示 types 是静态的。

【讨论】:

  • 我不会太担心用户的“恶意”行为。不小心传递了错误的值,好吧,但是为什么用户会破坏自己的代码呢?
  • 我承认“恶意”是夸大其词; “错误”可能更合适。我宁愿没有用户去'嘿,我知道我可以在哪里存储本地types 变量...parent.TYPES = ['tall', 'short']',默默地打破类。这不是防止这种行为的不变性的整个想法 - const,final 等吗?我认为花大量时间使用 JavaScript 让我对此类用户非常谨慎,即。我已经多次看到这种情况发生。
猜你喜欢
  • 1970-01-01
  • 2020-05-23
  • 1970-01-01
  • 2014-12-07
  • 1970-01-01
  • 2012-02-25
  • 1970-01-01
  • 2017-01-22
  • 1970-01-01
相关资源
最近更新 更多