Лучшее место для принуждения / преобразования в правильный тип в Python

Я все еще новичок в Python и пытаюсь привыкнуть к его динамической типизации. Иногда у меня есть функция или класс, который ожидает параметр определенного типа, но может получить значение другого типа, которое к нему может быть приведено. Например, он может ожидать float, но вместо этого получит int или десятичное число. Или он может ожидать строку, но вместо этого получает объект, который определяет специальный метод __str__.

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

Альтернатива 1:

def myfunc(takes_float):
    myval = float(takes_float)

myfunc(5)

Альтернатива 2:

def myfunc(takes_float):
    myval = takes_float

myfunc(float(5))

Альтернатива 3:

def myfunc(takes_float):
    assert isinstance(takes_float, float)
    myval = takes_float

myfunc(float(5))

Я уже прочитал этот ответ и этот, и они говорят, что проверка типов в Python "плохо", но я не хочу тратить время на отслеживание очень простых ошибок, которые могут быть мгновенно обнаружены компилятором на статически типизированном языке.


person EMP    schedule 16.12.2009    source источник
comment
Не используйте assert так. Утверждения предназначены для документирования условий, которые не могут произойти во время выполнения, а не для проверки ввода.   -  person Roberto Bonvallet    schedule 16.12.2009
comment
Да, не может произойти, если нет ошибки. В альтернативе 3, когда вызывающий должен преобразовать значение, это будет ошибкой.   -  person EMP    schedule 17.12.2009


Ответы (3)


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

import math
def spc(x):
  math.sin(x) + math.cos(x)

Куда вы должны «заставить» x плавать? Ответ: вообще нигде - sin и cos сделают эту работу за вас, например:

>>> spc(decimal.Decimal('1.9'))
0.62301052082391117

Итак, когда необходимо принуждение (как можно позже)? Например, если вы хотите вызвать строковые методы для аргумента, вы должны убедиться, что это строка - например, пытаясь вызвать. .lower с нестроковыми данными не будет работать, len может работать, но сделает что-то другое, чем вы ожидаете, если аргумент равен, например. список (укажите количество элементов в списке, а не количество символов, которые займет его представление в виде строки) и т. д.

Что касается поиска ошибок - подумайте о модульных тестах - полупрозрачных модульных тестах. будет отлавливать все ошибки статической типизации, а то и некоторые. Но это уже другая тема.

person Alex Martelli    schedule 16.12.2009

Это действительно зависит от обстоятельств. Зачем вам нужен float? Может int нарушить функцию? Если да, то почему?

Если вам нужен параметр для поддержки функции / свойства, которые есть у float, но у int нет, вы должны проверять наличие этой функции / свойства, не, что параметр оказался float. Убедитесь, что объект может делать то, что вам нужно, а не то, что это какой-то определенный тип, с которым вы знакомы.

Кто знает, может быть, кто-то обнаружит серьезную проблему с реализацией float в Python и создаст notbrokenfloat библиотеку. Он может поддерживать все, что делает float, при исправлении какой-нибудь экзотической ошибки, но его объекты не будут типа float. Приведение его к float вручную может лишить его всех преимуществ этого отличного нового класса (или вообще сломать его).

Да, это маловероятный пример, но я думаю, что это правильный образ мышления при работе с динамически типизированным языком.

person Steve Losh    schedule 16.12.2009

Ровно один раз, когда целое число или число с плавающей запятой будет проблемой. Это единственный раз, когда вы обнаружите «простую» ошибку, которая является странной и которую сложно отладить.

Разделение.

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

Если вы используете Python 2.x и случайно бросаете операторы /, не задумываясь, вы можете - при некоторых общих обстоятельствах - совершить неправильный поступок.

У вас есть несколько вариантов.

  1. from __future__ import division предоставит вам семантику Python 3 для разделения.

  2. Всегда запускайте с опцией -Qnew, чтобы получить новую семантику деления.

  3. Используйте float рядом с / операциями.

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

Все остальные проблемы с несовпадением типов завершатся полным отказом за TypeError исключением. Все остальные. Вы не будете тратить время на отладку. Вы сразу поймете, что случилось.


Чтобы быть более конкретным.

Отсутствует отладка типа «ожидала строку, но не получила строку». Это немедленно приведет к сбою при трассировке. Никакой путаницы. Не теряйте времени на размышления. Если функция ожидает строку, то вызывающий должен предоставить строку - это правило.

Альтернатива 2 выше используется РЕДКО для исправления проблемы, когда у вас есть функция, которая ожидает строку, и вы запутались и забыли предоставить строку. Эта ошибка происходит РЕДКО и приводит к немедленному возникновению исключения типа.

person S.Lott    schedule 16.12.2009
comment
Спасибо, хорошо знать варианты для этого, но вещь int / float была всего лишь примером. Есть и другие примеры проблемы. - person EMP; 17.12.2009
comment
@Evgeny: Правда? Вы можете что-нибудь предоставить? Единственное, что сбивает с толку, - это int / float. AFAIK, все остальные умирают за явными исключениями. Приведите пример операции, которая не завершается за исключением случаев. - person S.Lott; 17.12.2009
comment
Другой пример находится в моем исходном сообщении: метод может ожидать строку, но вместо этого получает объект, который определяет специальный метод __str__. Где-то по ходу дела мне нужно позвонить str(), но где? - person EMP; 17.12.2009