限制可能值的 Python 类型提示友好类型

Jam*_*979 6 python python-3.x python-typing

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

例如,基于类型的URL类型str只接受看起来像“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')
Run Code Online (Sandbox Code Playgroud)

Jam*_*979 7

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

覆盖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)
Run Code Online (Sandbox Code Playgroud)

这导致一个只允许一些字符串的类。否则,它的行为就像一个不可变的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')
Run Code Online (Sandbox Code Playgroud)

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

另一个例子,

覆盖int;受约束的整数范围Number

这个int派生类只允许1通过9包含值。

这也有一个特殊的功能。如果实例没有初始化 ( 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)
Run Code Online (Sandbox Code Playgroud)

这可确保尽快处理错误的输入。否则,它的行为就像一个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
Run Code Online (Sandbox Code Playgroud)

和特殊的“功能”

print(Number(1)) # '1' (expected)
print(Number())  # '.' (special feature)
Run Code Online (Sandbox Code Playgroud)




继承不可变类型的技术源自此 SO answer

  • 可以关闭断言。`assert` 不应该用于控制流。 (3认同)