【问题标题】:Type safe Python 3.2 dictionary键入安全的 Python 3.2 字典
【发布时间】:2013-01-07 11:29:49
【问题描述】:

在 Python (3.2) 中实现类型安全字典的好方法是什么 - 一个只允许将特定类型的对象添加到自身的字典?

我自己有一个简单的解决方案:使用“addItem”方法围绕字典构建一个包装类,该方法在添加对象之前执行类型检查断言。看看有人有没有更好的东西。

【问题讨论】:

  • 你为什么认为你需要这样的东西?
  • 那是相当非 Pythonic
  • 我对 Python 纯粹主义者的批评不感兴趣。我正在寻找问题的答案。
  • @Mikey,我问的原因是因为答案会因您实际尝试做的事情而异。大多数情况下,答案将是做其他事情,但也许不会。
  • 一个更好的解决方案是让你的类型检查器只检查对象是否有你想要的接口,这与实际的类型比较不同,而且更多的是 Pythonic。您多年的经验给您的印象是,这是做事的唯一方法。

标签: python types dictionary


【解决方案1】:

这里的 Pythonic 方法是只使用普通字典并只向其中添加特定类型的对象 - 不要试图强制执行限制,它不应该是必需的。


编辑:为了扩展我的论点,让我解释一下——你似乎认为编写好的代码需要类型安全。第一个问题是为什么?当然,类型安全会在编译时捕获一些错误,但根据我的经验,这些错误很少见,即使是最琐碎的测试也很容易捕获,而且通常很容易修复。

相比之下,最烦人、最难修复和最难测试的错误是逻辑错误,计算机根本无法发现。最好通过制作易于理解的可读代码来防止这些错误,从而使错误更加突出。动态类型通过减少代码的冗长性极大地帮助了这一点。您可能会争辩说,键入会使代码更容易阅读(因为可以在使用它们时看到变量的类型),但是在动态语言中,这种事情是通过仔细命名来给出的——如果我命名一个变量seq,人们会认为它是一个序列并且可以这样使用。根据我的经验,描述性命名和良好文档的结合使动态代码变得更好。

归根结底,语言中的类型安全是一个偏好问题,但是,Python 是一种围绕鸭子类型的思想设计的动态语言。该语言中的所有内容都是围绕它设计的,尝试以另一种方式使用它会适得其反。如果你想写Java,就写Java。

【讨论】:

  • 对于复杂系统中结构良好的代码和良好的设计,这是必要的。阅读任何有关设计的书......所以你是在告诉我,Python 不能用于实现编写良好、具有可读源代码的灵活系统,而其他开发人员可以在几周内不费力地工作。
  • @Mikey 这根本不是真的 - 查看任何编码良好的 Python 程序,您会发现情况并非如此。类型安全只是确保您的程序遵循其设计的一种方法。
  • 明确地说,这不是反类型安全——我完全理解为什么有些人喜欢它,有些情况下需要它,这只是你想要类型安全的一个例子, Python 不适合这项工作。
  • 没有语言特性来支持它——Python 只是提供了做你想做的事情的灵活性,这可以被黑客入侵做这样的事情。我还可以制作一个字典,在每次插入值时都会抛出异常——这并不意味着这是一个好主意。 Python 有exec,将它用于 99% 的东西并不是一个好主意。如果你不喜欢动态类型,那么使用一种基于它构建的语言就是愚蠢的。如果你想要 Java、C# 或 C++,那就去使用它们,而不是写“Python”,这会在未来让其他人头疼。
  • @Mikey 不,尽管我认为 Python 的整洁和简洁的代码从中受益匪浅。我所知道的与您所描述的最接近的是 Boo,它本质上是 Python 与 C#(静态类型,.net)的交叉。我仍然会说以 Python 风格进行开发并不排除正确的语义,但如果你不能超越一种方法,那么祝你找到你想要的东西。
【解决方案2】:

通过对dict 进行子类化并将保护添加到__setitem__.update().setdefault();添加一个从默认值获取类型的.fromkeys() 类方法是一个不错的额外功能:

from itertools import tee

class MyTypeDict(dict):
    def __init__(self, type_=SomeType, *args, **kw):
        self.type = type_
        super(MyTypeDict, self).__init__(*args, **kw)
        for val in self.itervalues():
            self._checktype(val)

    @classmethod
    def fromkeys(cls, seq, value=SomeType()):
        res = cls(type_=type(value))
        res.update((k, value) for k in seq)
        return res

    def _checktype(self, value):
        if not isinstance(value, self.type):
            raise TypeError('Value type {!r} not allowed'.format(type(value)))

    def __setitem__(self, key, value):
        self._checktype(value)
        super(MyTypeDict, self).__setitem__(key, value)

    def update(self, other):
        # Loop over other, either a dict or an iterable (use a copy with `tee`)
        # for python 3, use `items()` instead.
        items = other.iteritems() if hasattr(other, 'iteritems') else tee(other)
        for key, value in items:
            self._checktype(value)
        super(MyTypeDict, self).update(other)

    def setdefault(self, key, default=None):
        if default is None:
            default = self.type()  # assumes no-args initializer
        else:
            self._checktype(default)
        return super(MyTypeDict, self).setdefault(key, default)

将其用作:

mydict = MyTypeDict(type_=SomeType)

【讨论】:

  • 您的update 方法不太正确,它检查的是键而不是值。 MyTypeDict(str).update({1:'hello'}) 抛出 TypeError.
  • @Duncan:你说的很对;它已经完成了一半(多任务处理不起作用)。
  • 如果键本身是元组,它仍然会出错。这可能无关紧要,但这取决于 OP 实际想要使用它的目的。
  • @Duncan:是的,我已将其切换为在 .iteritems() 上使用鸭子打字。
【解决方案3】:

我认为你可以扩展字典并覆盖__setitem__ 方法

class MyDict(dict):
    def __setitem__(self, key, val):
        #Test for proper interface
        if val.pass_the_test:
            dict.__setitem__(self, key, val)
        else:
            raise SomeKindOfException() 

【讨论】:

  • 这对于我现在想做的事情已经足够了。我的派生类的 NAME 可以表明它是 T 类型的字典,并且 _setItem 将强制执行该规则。不如 List 干净但足够好。
【解决方案4】:

Martijn 已经给了你一个的答案,但正如你所见,要正确处理极端情况是很棘手的。

如果你想要的只是避免在脚下开枪,那么他的回答可能比你实际需要的要多;也许您只需要包装__setitem__,或者您最好让任何类型进入字典,但在完成添加内容或访问它们时进行某种断言。最后一个问题当然是 Python 人的常见回答:如果字典中的对象没有实现正确的接口,就让代码在使用时中断,而不是预先检查。

另一方面,如果您需要防止恶意代码注入流氓值,Martijn 的代码是不够的;你可以通过调用来轻松解决它:

dict.__setitem__(mydict, key, rogue_value)

另外,如果您真的打算将对象限制为单一类型,那么他的答案不是您想要的;您可能有一个对象通过了isinstance 测试,但没有提供正确的鸭子类型行为。

这就是为什么更多的问题背景会很有用。

【讨论】:

  • 我并不像其他开发人员中弹自己的脚那样担心自己中弹。是恶意代码
  • 大型系统中的自定义/自记录代码是我主要关心的问题。当其他开发人员(或六个月后我自己......)查看它的代码时,他们将从所使用的类和类型中清楚地了解模块中发生的事情。 Martijn 的回答是我需要的“矫枉过正”。您还建议的 Dikei 的答案可能就足够了 - 比我的解决方案更清晰、更容易。
  • @Mikey 你这样做可能会让遇到它的任何 Python 开发人员感到困惑 - 它完全适得其反,没有 Python 程序员会假设这是某些事情的工作方式。
【解决方案5】:

根据 DikeiMartijn Pieters 的回答,我想出了这个简单的实现,足以满足我的需求:

class ETypedDictonaryException (Exception):
    pass

class TypedDict(dict):

    def __init__(self, keyType, valueType):
        dict.__init__(self)
        self.keyType = keyType
        self.valueType = valueType

    def __setitem__(self, key, value):
        if ( not isinstance(key, self.keyType) or not isinstance(value, self.valueType) ):
            raise  ETypedDictonaryException("wrong key type:" +str(self.keyType) + " and " +str(self.valueType)+ "  required!")
        dict.__setitem__(self, key, value)

【讨论】:

    猜你喜欢
    • 2018-05-29
    • 1970-01-01
    • 1970-01-01
    • 2011-10-20
    • 1970-01-01
    • 1970-01-01
    • 2013-11-19
    • 1970-01-01
    相关资源
    最近更新 更多