переопределение встроенных неизменяемых типов работает хорошо
переопределение str
;http URL strings
Вот пример переопределения str
.Для этого не требуется модуль typing
, но он по-прежнему работает с подсказками типов.
Этот производный класс str
утверждает, что инициализированная строка выглядит как строка URL-адреса http.
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)
Thisв результате получается класс, который допускает только некоторые строки.В противном случае он ведет себя как неизменный экземпляр 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')
(обновление: позже я обнаружил библиотеку Python purl )
Другой пример:
overriding 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)
Это гарантирует, что ошибочные входы обрабатываются раньше, чем позже.В противном случае он ведет себя так же, как 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)
Техника для наследования неизменяемых типов происходит из этого ответа SO .