Это - Pythonic для проверки типов аргумента функции?

Я знаю, ввожу аргументы функции проверки, обычно осуждается в Python, но я думаю, что придумал ситуацию, где имеет смысл делать так.

В моем проекте у меня есть Абстрактный базовый класс Coord, с подклассом Vector, который имеет больше функций как вращение, изменяя величину, и т.д. Списки и кортежи чисел также возвратят True для isinstance(x, Coord). У меня также есть много функций и методов, которые принимают эти типы Coord как аргументы. Я настроил декораторов для проверки аргументов этих методов. Вот упрощенная версия:

class accepts(object):
    def __init__(self, *types):
        self.types = types

    def __call__(self, func):
        def wrapper(*args):
            for i in len(args):
                if not isinstance(args[i], self.types[i]):
                    raise TypeError

            return func(*args)

        return wrapper

Эта версия очень проста, она все еще имеет некоторые ошибки. Это должно просто там проиллюстрировать тезис. И это использовалось бы как:

@accepts(numbers.Number, numbers.Number)
def add(x, y):
    return x + y

Примечание: Я только проверяю типы аргумента по Абстрактным базовым классам.

Действительно ли это - хорошая идея? Существует ли лучший способ сделать это, не имея необходимость повторять подобный код в каждом методе?

Править:

Что, если я должен был сделать то же самое, но вместо того, чтобы проверить типы заранее в декоратора, я ловлю исключения в декораторе:

class accepts(object):
    def __init__(self, *types):
        self.types = types

    def __call__(self, func):
        def wrapper(*args):

            try:
                return func(*args)
            except TypeError:
                raise TypeError, message
            except AttributeError:
                raise AttributeError, message

        return wrapper

Это немного лучше?

19
задан Nathan Kleyn 13 April 2013 в 21:02
поделиться

5 ответов

Ваш вкус может варьироваться, но стиль Pythonic(tm) заключается в том, чтобы просто использовать объекты так, как вам нужно. Если они не поддерживают операции, которые вы пытаетесь выполнить, будет поднято исключение. Это известно как утиный тип.

Есть несколько причин для предпочтения этого стиля: во-первых, он позволяет использовать полиморфизм, позволяя вам использовать новые виды объектов с существующим кодом, пока новые объекты поддерживают нужные операции. Во-вторых, он оптимизирует успешный путь, избегая многочисленных проверок.

Конечно, сообщение об ошибке, которое вы получаете при использовании неверных аргументов, будет более понятным при проверке типов, чем при вводе утки, но, как я уже сказал, ваш вкус может варьироваться.

.
29
ответ дан 30 November 2019 в 03:08
поделиться

В дополнение к уже упомянутым идеям вы можете захотеть «принуждать» входные данные к типу, который имеет необходимые вам операции. Например, вы можете преобразовать кортеж координат в массив Numpy, чтобы вы могли выполнять с ним операции линейной алгебры. Код принуждения довольно общий:

input_data_coerced = numpy.array(input_data)  # Works for any input_data that is a sequence (tuple, list, Numpy array…) 
1
ответ дан 30 November 2019 в 03:08
поделиться

Одна из причин, по которой Duck Typing поощряется на Python, заключается в том, что кто-то может обернуть один из ваших объектов, и тогда он будет выглядеть как неправильный тип, но все равно работает.

Вот пример класса, который обертывает объект. A 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
9
ответ дан 30 November 2019 в 03:08
поделиться

Если это исключение из правила, то это нормально. Но если инжиниринг/дизайн вашего проекта вращается вокруг проверки типа каждой функции (или большинство из них) тогда, может быть, вы не хотите использовать Python, как насчет C# вместо него?

По моему мнению, создание декоратора для проверки типа обычно означает, что вы будете использовать его много . Так что в этом случае, хотя факторизация обычного кода в декораторе - питоническая, то, что он для проверки типов - не очень питоническая.

.
2
ответ дан 30 November 2019 в 03:08
поделиться

Об этом говорили, потому что Py3k поддерживает аннотации функций , из которых аннотации типа являются приложением. Также в Python2 была попытка прокатить проверку типов .

Я думаю, что это никогда не занимало много времени, потому что основная проблема, которую вы пытаетесь решить ("найти ошибки типа") либо тривиальна для начала (вы видите TypeError[11577]), либо довольно сложна (небольшое различие в интерфейсах типов). Плюс для того, чтобы получить его правильно , вам понадобятся типовые классы и классифицировать каждый тип на Python. В основном, это много работы. Не говоря уже о том, что вы постоянно будете делать проверки runtime.

Python уже имеет сильную и предсказуемую систему типов. Если мы когда-нибудь увидим что-нибудь более мощное, надеюсь, она пройдет через аннотации типов и умные IDE.

.
1
ответ дан 30 November 2019 в 03:08
поделиться
Другие вопросы по тегам:

Похожие вопросы: