Одна из причин, по которой Duck Typing поощряется в Python, заключается в том, что кто-то может обернуть один из ваших объектов, и тогда он будет выглядеть как неправильный тип, но все равно будет работать.
Вот пример класса, который оборачивает объект. LoggedObject
действует во всех отношениях как объект, который он оборачивает, но когда вы вызываете LoggedObject
, он записывает вызов перед выполнением вызова.
from somewhere import log
from myclass import A
class LoggedObject(object):
def __init__(self, obj, name=None):
if name is None:
self.name = str(id(obj))
else:
self.name = name
self.obj = obj
def __call__(self, *args, **kwargs):
log("%s: called with %d args" % (self.name, len(args)))
return self.obj(*args, **kwargs)
a = LoggedObject(A(), name="a")
a(1, 2, 3) # calls: log("a: called with 3 args")
Если вы явно протестируете для isinstance(a, A)
, это не удастся, потому что a
является экземпляром LoggedObject
. Если вы просто позволите утке набирать текст, это сработает.
Если кто-то пропустит неправильный тип объекта по ошибке, возникнет какое-то исключение, например AttributeError
. Исключение может быть более понятным, если вы явно проверяете типы, но я думаю, что в целом этот случай является выигрышным для утки.
Бывают случаи, когда вам действительно нужно проверить тип. Недавно я узнал следующее: когда вы пишете код, который работает с последовательностями, иногда вам действительно нужно знать, есть ли у вас строка или какая-либо другая последовательность. Учтите это:
def llen(arg):
try:
return max(len(arg), max(llen(x) for x in arg))
except TypeError: # catch error when len() fails
return 0 # not a sequence so length is 0
Предполагается, что он возвращает наибольшую длину последовательности или любой последовательности, вложенной в нее. Работает:
lst = [0, 1, [0, 1, 2], [0, 1, 2, 3, 4, 5, 6]]
llen(lst) # returns 7
Но если вы позвоните llen("foo")
, , он будет повторяться вечно до тех пор, пока стек не переполнится.
Проблема состоит в том, что строки имеют специальное свойство, заключающееся в том, что они всегда действуют как последовательность, даже если вы берете наименьший элемент из строки; односимвольная строка все еще является последовательностью. Поэтому мы не можем написать llen () без явного теста на строку.
def llen(arg):
if isinstance(arg, basestring): # Python 2.x; for 3.x use isinstance(arg, str)
return len(arg)
try:
return max(len(arg), max(llen(x) for x in arg))
except TypeError: # catch error when len() fails
return 0 # not a sequence so length is 0