Типы параметров функции в Python

Если я ошибаюсь, создание функции в Python работает следующим образом:

def my_func(param1, param2):
    # stuff

Однако вы фактически не указываете типы этих параметров. Кроме того, если я помню, Python - строго типизированный язык, как таковой, похоже, что Python не должен пропускать параметр другого типа, чем ожидал создатель функции. Однако, как Python знает, что пользователь функции передает правильные типы? Будет ли программа просто умирать, если она ошибочна, если функция действительно использует параметр? Вам нужно указать тип?

Ответ 1

Python строго типизирован, потому что каждый объект имеет тип, каждый объект знает его тип, невозможно случайно или намеренно использовать объект типа "как бы", это был объект другого типа, и все элементарные операции над объект делегируется его типу.

Это не имеет ничего общего с именами. Имя в Python не имеет "тип": если и когда имя определено, имя относится к объекту, а объект имеет тип (но это на самом деле не приводит к типу имени: a имя - это имя).

Имя в Python может отлично ссылаться на разные объекты в разное время (как на большинстве языков программирования, хотя и не на всех) - и нет ограничения на имя, так что если он когда-то ссылался на объект тип X, то он навсегда ограничивается ссылкой только на другие объекты типа X. Ограничения на имена не являются частью концепции "сильной типизации", хотя некоторые энтузиасты static typing (где имена становятся ограниченными, а в статическом, компиляционном времени AKA, моде тоже) неправильно используют термин таким образом.

Ответ 2

Другие ответы хорошо объясняли типизацию утки и простой ответ от tzot:

Python не имеет переменных, как и другие языки, где переменные имеют тип и значение; у него есть имена, указывающие на объекты, которые знают их тип.

Однако одна интересная вещь изменилась с 2010 года (когда вопрос был задан впервые), а именно реализация PEP 3107 (реализована в Python 3). Теперь вы можете указать тип параметра и тип возвращаемого типа функции, например:

def pick(l: list, index: int) -> int:
    return l[index]

Здесь мы видим, что pick принимает 2 параметра, список l и целочисленный index. Также должно возвращаться целое число.

Таким образом, здесь подразумевается, что l - это список целых чисел, который мы можем увидеть без особых усилий, но для более сложных функций это может немного сбить с толку относительно того, что этот список должен содержать. Мы также хотим, чтобы значение index по умолчанию было равно 0. Чтобы решить эту проблему, вы можете вместо этого написать команду pick следующим образом:

def pick(l: "list of ints", index: int = 0) -> int:
    return l[index]

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

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

def pick(l: list, index: int = 0) -> int:
    if not isinstance(l, list):
        raise TypeError
    return l[index]

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

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


Аннотации типов получили гораздо больше внимания в Python 3.5 с введением PEP 484, который представляет стандартный модуль для подсказок типов.

Эти подсказки о типах пришли из проверки типов Mypy (GitHub), которая теперь соответствует PEP 484.

С модулем набора текста поставляется довольно полный набор подсказок типа, в том числе:

  • List, Tuple, Set, Map - для list, tuple, set и map соответственно.
  • Iterable - полезен для генераторов.
  • Any - когда это может быть что угодно.
  • Union - когда это может быть что-либо в указанном наборе типов, в отличие от Any.
  • Optional - когда это может быть Нет. Сокращение от Union[T, None].
  • TypeVar - используется с дженериками.
  • Callable - используется в основном для функций, но может использоваться и для других вызываемых.

Это наиболее распространенные типовые подсказки. Полный список можно найти в документации для модуля ввода.

Вот старый пример с использованием методов аннотации, введенных в модуле ввода:

from typing import List

def pick(l: List[int], index: int) -> int:
    return l[index]

Одна мощная функция - Callable которая позволяет вам вводить аннотированные методы, которые принимают функцию в качестве аргумента. Например:

from typing import Callable, Any, Iterable

def imap(f: Callable[[Any], Any], l: Iterable[Any]) -> List[Any]:
    """An immediate version of map, don't pass it any infinite iterables!"""
    return list(map(f, l))

Приведенный выше пример может стать более точным с использованием TypeVar вместо Any, но это было оставлено читателю как упражнение, так как я считаю, что я уже наполнил свой ответ слишком большим количеством информации о замечательных новых функциях, доступных благодаря подсказке типов,


Ранее, когда один документированный код Python с, например, Sphinx, некоторые из вышеперечисленных функциональных возможностей можно было получить, написав строки документов в следующем формате:

def pick(l, index):
    """
    :param l: list of integers
    :type l: list
    :param index: index at which to pick an integer from *l*
    :type index: int
    :returns: integer at *index* in *l*
    :rtype: int
    """
    return l[index]

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

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


Пример кода Python, который интенсивно использует подсказки типов, можно найти здесь.

Ответ 3

Вы не указываете тип. Метод будет терпеть неудачу (во время выполнения), если он попытается получить доступ к атрибутам, которые не определены в передаваемых параметрах.

Итак, эта простая функция:

def no_op(param1, param2):
    pass

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

Однако эта функция:

def call_quack(param1, param2):
    param1.quack()
    param2.quack()

... не удастся во время выполнения, если param1 и param2 не имеют атрибуты с атрибутами с именем quack.

Ответ 4

Многие языки имеют переменные, которые имеют определенный тип и имеют значение. Python не имеет переменных; он имеет объекты, и вы используете имена для ссылки на эти объекты.

В других языках, когда вы говорите:

a = 1

то переменная a (обычно целочисленная) меняет ее содержимое на значение 1.

В Python

a = 1

означает "использовать имя a для обозначения объекта 1". Вы можете сделать следующее в интерактивном сеансе Python:

>>> type(1)
<type 'int'>

Функция type вызывается с объектом 1; поскольку каждый объект знает свой тип, легко type найти этот тип и вернуть его.

Аналогично, всякий раз, когда вы определяете функцию

def funcname(param1, param2):

функция получает два объекта и называет их param1 и param2, независимо от их типов. Если вы хотите удостовериться, что полученные объекты относятся к определенному типу, введите свою функцию так, как будто они имеют нужный тип и поймают исключения, которые выбрасываются, если они не являются. Исключенные исключения обычно TypeError (вы использовали недопустимую операцию) и AttributeError (вы пытались получить доступ к несуществующему элементу (методы тоже являются членами)).

Ответ 5

Python не строго типизирован в смысле проверки статического или компилируемого времени.

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

Ответ 6

Как объясняет Алекс Мартелли,

Нормальное, Pythonic, предпочтительное решение почти всегда "утиная печать": попробуйте использовать аргумент, как если бы он был определенного типа, сделайте это в инструкции try/except, улавливая все исключения, которые могут возникнуть, если аргумент был не на самом деле этого типа (или любого другого типа, красиво утка-подражающего ему;-), а в предложении except попробуйте что-то еще (используя аргумент "как будто" это был какой-то другой тип ").

Прочитайте остальную часть своего сообщения за полезную информацию.

Ответ 7

Python не заботится о том, что вы передаете его функциям. Когда вы вызываете my_func(a,b), переменные param1 и param2 будут удерживать значения a и b. Python не знает, что вы вызываете функцию с соответствующими типами, и ожидает, что программист позаботится об этом. Если ваша функция будет вызываться с различными типами параметров, вы можете привязать код к ним с помощью блоков try/except и оценить параметры любым способом.

Ответ 8

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

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

Ответ 9

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

Когда функция str вызывает метод класса __str__, она тонко выводит ее тип:

>>> class A(object):
...     def __str__(self):
...         return 'a','b'
...
>>> a = A()
>>> print a.__str__()
('a', 'b')
>>> print str(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __str__ returned non-string (type tuple)

Как будто Guido намекает на то, какое исключение должно было бы выполнить программу, если она встречает неожиданный тип.

Ответ 10

В Python все имеет тип. Функция Python будет делать все, что просят сделать, если тип аргументов поддерживает ее.

Пример: foo добавит все, что может быть __add__ ed;), не беспокоясь о его типе. Таким образом, чтобы избежать сбоя, вы должны предоставить только те вещи, которые поддерживают добавление.

def foo(a,b):
    return a + b

class Bar(object):
    pass

class Zoo(object):
    def __add__(self, other):
        return 'zoom'

if __name__=='__main__':
    print foo(1, 2)
    print foo('james', 'bond')
    print foo(Zoo(), Zoo())
    print foo(Bar(), Bar()) # Should fail

Ответ 11

Я не видел этого в других ответах, поэтому добавлю это в банк.

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

Одним из основных инструментов для этого является функция isinstance().

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

def process(data):
    if not isinstance(data, bytes) and not isinstance(data, bytearray):
        raise TypeError('Invalid type: data must be a byte string or bytearray, not %r' % type(data))
    # Do more stuff

Python также предоставляет всевозможные инструменты для поиска объектов. Если вы храбры, вы можете даже использовать importlib для создания собственных объектов произвольных классов, на лету. Я сделал это, чтобы воссоздать объекты из данных JSON. Такое было бы кошмаром на статичном языке, таком как С++.

Ответ 12

Я реализовал оболочку, если кто-то хотел бы указать типы переменных.

import functools

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for i in range(len(args)):
            v = args[i]
            v_name = list(func.__annotations__.keys())[i]
            v_type = list(func.__annotations__.values())[i]
            error_msg = 'Variable '' + str(v_name) + '' should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        v = result
        v_name = 'return'
        v_type = func.__annotations__['return']
        error_msg = 'Variable '' + str(v_name) + '' should be type ('
        error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
        if not isinstance(v, v_type):
                raise TypeError(error_msg)
        return result

    return check

Используйте его как:

@type_check
def test(name : str) -> float:
    return 3.0

@type_check
def test2(name : str) -> str:
    return 3.0

>> test('asd')
>> 3.0

>> test(42)
>> TypeError: Variable 'name' should be type (<class 'str'>) but instead is type (<class 'int'>)

>> test2('asd')
>> TypeError: Variable 'return' should be type (<class 'str'>) but instead is type (<class 'float'>)

EDIT

Приведенный выше код не работает, если какой-либо из аргументов (или возвращаемый тип) не объявлен. Следующее редактирование может помочь, с другой стороны, оно работает только для kwargs и не проверяет args.

def type_check(func):

    @functools.wraps(func)
    def check(*args, **kwargs):
        for name, value in kwargs.items():
            v = value
            v_name = name
            if name not in func.__annotations__:
                continue

            v_type = func.__annotations__[name]

            error_msg = 'Variable '' + str(v_name) + '' should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ') '
            if not isinstance(v, v_type):
                raise TypeError(error_msg)

        result = func(*args, **kwargs)
        if 'return' in func.__annotations__:
            v = result
            v_name = 'return'
            v_type = func.__annotations__['return']
            error_msg = 'Variable '' + str(v_name) + '' should be type ('
            error_msg += str(v_type) + ') but instead is type (' + str(type(v)) + ')'
            if not isinstance(v, v_type):
                    raise TypeError(error_msg)
        return result

    return check

Ответ 13

Чтобы эффективно использовать модуль ввода (новый в Python 3.5), включите все (*).

from typing import *

И вы будете готовы к использованию:

List, Tuple, Set, Map - for list, tuple, set and map respectively.
Iterable - useful for generators.
Any - when it could be anything.
Union - when it could be anything within a specified set of types, as opposed to Any.
Optional - when it might be None. Shorthand for Union[T, None].
TypeVar - used with generics.
Callable - used primarily for functions, but could be used for other callables.

Тем не менее, вы все равно можете использовать имена типов, такие как int, list, dict ,...

Ответ 14

Вы можете вызвать функцию, используя следующие типы формальных аргументов

1. Требуемый аргумент: Обязательные аргументы - это аргументы, переданные функции в правильном позиционном порядке. Здесь количество аргументов в вызове функции должно точно совпадать с определением функции.

2. Аргумент ключевого слова: Аргументы ключевого слова связаны с вызовом функции. Когда вы используете аргументы ключевого слова в вызове функции, вызывающий идентифицирует аргументы по имени параметра. Это позволяет вам пропускать аргументы или помещать их в нерабочее состояние, потому что интерпретатор Python может использовать ключевые слова, предоставленные для соответствия значениям с параметрами.

3. Аргумент по умолчанию: Аргумент по умолчанию - это аргумент, который принимает значение по умолчанию, если в вызове функции для этого аргумента не указано значение.

4. Аргумент переменной длины: Вам может потребоваться обработать функцию для большего количества аргументов, которые вы указали при определении функции. Эти аргументы называются "Variable-Length Arguments" и не называются в определении функции, в отличие от обязательных и стандартных аргументов.