【问题标题】:Python type-hint friendly type that constrains possible values约束可能值的 Python 类型提示友好类型
【发布时间】:2019-07-03 20:26:07
【问题描述】:

我想要一种 python 类型提示友好的方式来创建具有受限值范围的类型。

例如,基于str 类型的URL 类型只接受看起来像“http”URL 的字符串。

# this code is made up and will not compile
class URL(typing.NewType('_URL', str)):
    def __init__(self, value: str, *args, **kwargs):
        if not (value.startswith('http://') or value.startswith('https://')):
            raise ValueError('string is not an acceptable URL')

【问题讨论】:

    标签: python python-3.x python-typing


    【解决方案1】:

    覆盖内置的不可变类型效果很好

    覆盖str; http URL 字符串

    这是一个覆盖str 的示例。这不需要typing 模块,但仍适用于类型提示。

    这个str派生类断言初始化的字符串看起来像一个http URL字符串。

    class URL(str):
        def __new__(cls, *value):
            if value:
                v0 = value[0]
                if not type(v0) is str:
                    raise TypeError('Unexpected type for URL: "%s"' % type(v0))
                if not (v0.startswith('http://') or v0.startswith('https://')):
                    raise ValueError('Passed string value "%s" is not an'
                                     ' "http*://" URL' % (v0,))
            # else allow None to be passed. This allows an "empty" URL instance, e.g. `URL()`
            # `URL()` evaluates False
    
            return str.__new__(cls, *value)
    

    这会导致类只允许某些字符串。否则,它的行为就像一个不可变的 str 实例。

    # these are okay
    URL()
    URL('http://example.com')
    URL('https://example.com')
    URL('https://')
    
    # these raise ValueError
    URL('example')  # ValueError: Passed string value "example" is not an "http*://" URL
    URL('')  # ValueError: Passed string value "" is not an "http*://" URL
    
    # these evaluate as you would expect
    for url in (URL(),  # 'False'
                URL('https://'),  # 'True'
                URL('https://example.com'),  # 'True'
               ):
        print('True') if url else print('False')
    

    (更新:后来我找到了purl Python 库)

    另一个例子,

    覆盖int;受限整数范围Number

    这个int 派生类只允许19 的值。

    这也有一个特殊的功能。如果实例初始化为空(Number()),则该值等于0(此行为派生自int 类)。在这种情况下,__str__ 应该是'.'(程序要求)。

    class Number(int):
        """integer type with constraints; part of a Sudoku game"""
    
        MIN = 1  # minimum
        MAX = 9  # maximum
    
        def __new__(cls, *value):
            if value:
                v0 = int(value[0])
                if not (cls.MIN <= v0 <= cls.MAX):
                    raise ValueError('Bad value "%s" is not acceptable in'
                                     ' Sudoku' % (v0,))
            # else:
            #    allow None to be passed. This allows an "empty" Number instance that
            #    evaluates False, e.g. `Number()`
    
            return int.__new__(cls, *value)
    
        def __str__(self):
            """print the Number accounting for an "empty" value"""
            if self == 0:
                return '.'
            return int.__str__(self)
    

    这可确保尽早处理错误的输入。否则,它的行为就像int

    # these are okay
    Number(1)
    Number(9)
    Number('9')
    
    # this will evaluate True, just like an int
    Number(9) == int(9)
    Number('9') == int(9)
    Number('9') == float(9)
    
    # this is okay, it will evaluate False
    Number()
    print('True') if Number() else print('False')  # 'False'
    
    # these raise ValueError
    Number(0)  # ValueError: Bad value "0" is not acceptable in Sudoku
    Number(11)  # ValueError: Bad value "11" is not acceptable in Sudoku
    Number('11')  # ValueError: Bad value "11" is not acceptable in Sudoku
    

    还有特殊的“功能”

    print(Number(1)) # '1' (expected)
    print(Number())  # '.' (special feature)
    




    inheriting immutable types is derived from this SO answer 的技术。

    【讨论】:

    • 断言可以关闭。 assert 不应用于控制流。
    • @TigerhawkT3 已修复。改用TypeError。谢谢。
    【解决方案2】:

    子类化内置类型可能会导致一些奇怪的情况(考虑精确检查 type(...) is str 的代码)

    这是一种类型安全且完全保留字符串类型的纯类型方法:

    from typing import NewType
    
    _Url = NewType('_Url', str)
    
    def URL(s: str) -> _Url:
        if not s.startswith('https://'):
            raise AssertionError(s)
        return _Url(s)
    
    print(type(URL('https://example.com')) is str)  # prints `True`
    

    这里的方法将运行时检查“隐藏”在一个从 api 角度看起来像构造函数的函数后面,但实际上只是一个 tiny type(我找不到对“微小类型”的规范引用)只是成为我能找到的最好的资源)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-08-13
      • 1970-01-01
      • 2017-01-21
      • 1970-01-01
      • 2022-10-06
      • 2012-09-06
      • 2022-08-23
      相关资源
      最近更新 更多