Подтвердить что ты не робот

Python 3 и статическая типизация

Я не уделял столько внимания Python 3, как мне бы хотелось, и только что заметил некоторые интересные новые синтаксические изменения. В частности, из this SO answer аннотации параметров функции:

def digits(x:'nonnegative number') -> "yields number digits":
    # ...

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

После некоторого поиска, похоже, было много дискуссий относительно (полностью необязательного) статического ввода в Python, например, упомянутого в PEP 3107 и "Добавление необязательной статической типизации на Python" часть 2)

.. но я не знаю, насколько это продвинулось. Существуют ли какие-либо реализации статической типизации, используя аннотацию параметра? Любая из идей с параметризованным типом превратила его в Python 3?

4b9b3361

Ответ 1

Спасибо за чтение моего кода!

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

'''Very simple enforcer of type annotations.

This toy super-decorator can decorate all functions in a given module that have 
annotations so that the type of input and output is enforced; an AssertionError is
raised on mismatch.

This module also has a test function func() which should fail and logging facility 
log which defaults to print. 

Since this is a test module, I cut corners by only checking *keyword* arguments.

'''

import sys

log = print


def func(x:'int' = 0) -> 'str':
    '''An example function that fails type checking.'''
    return x


# For simplicity, I only do keyword args.
def check_type(*args):
    param, value, assert_type = args
    log('Checking {0} = {1} of {2}.'.format(*args))
    if not isinstance(value, assert_type):
        raise AssertionError(
            'Check failed - parameter {0} = {1} not {2}.'
            .format(*args))
    return value

def decorate_func(func):    
    def newf(*args, **kwargs):
        for k, v in kwargs.items():
            check_type(k, v, ann[k])
        return check_type('<return_value>', func(*args, **kwargs), ann['return'])

    ann = {k: eval(v) for k, v in func.__annotations__.items()}
    newf.__doc__ = func.__doc__
    newf.__type_checked = True
    return newf

def decorate_module(module = '__main__'):
    '''Enforces type from annotation for all functions in module.'''
    d = sys.modules[module].__dict__
    for k, f in d.items():
        if getattr(f, '__annotations__', {}) and not getattr(f, '__type_checked', False):
            log('Decorated {0!r}.'.format(f.__name__))
            d[k] = decorate_func(f)


if __name__ == '__main__':
    decorate_module()

    # This will raise AssertionError.
    func(x = 5)

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

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

height = 1.75 # Bob height in meters.
length = len(sys.modules) # Number of modules imported by program.
area = height * length # What that supposed to mean???

Любой человек должен сразу увидеть ошибку в приведенной выше строке, если он знает "человеческий тип" переменных height и length, хотя он выглядит как компьютер как совершенно законное умножение int и float.

Более того, можно сказать о возможных решениях этой проблемы, но применение "типов компьютеров", по-видимому, является полурешением, поэтому, по крайней мере, по моему мнению, это хуже, чем никакого решения. Это та же самая причина, почему Systems Hungarian - ужасная идея, а Apps Hungarian - отличная версия. Там больше на очень информативном

Ответ 2

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

Что касается сторонних реализаций, есть некоторые фрагменты (такие как http://code.activestate.com/recipes/572161/), которые, похоже, выполняют работу довольно хорошо.

EDIT:

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

Ответ 3

"Статическая типизация" в Python может быть реализована только так, что проверка типов выполняется во время выполнения, что означает, что это замедляет работу приложения. Поэтому вы не хотите, чтобы это было общностью. Вместо этого вы хотите, чтобы некоторые из ваших методов проверяли его входы. Это можно легко сделать с помощью простых утверждений или с декораторами, если вы (ошибочно) думаете, что вам это нужно много.

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

assert isinstance(theobject, myclass)

сделайте следующее:

theobject = IMyClass(theobject)

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

Это сочетало динамизм Python с желанием определенного типа определенным образом.

Ответ 4

Это не ответ на вопрос напрямую, но я обнаружил вилку Python, которая добавляет статическую типизацию: mypy-lang.org, конечно один не может полагаться на него, так как он все еще мал, но интересен.

Ответ 5

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

Затем я предпочитаю использовать beartype, объясненный в этом сообщении *. Он поставляется с git репо, тестами и объяснением, что он может и что он не может сделать... и мне нравится имя;)

* Пожалуйста, не обращайте внимания на вопрос Сесила о том, почему Python не поставляется с батареями, включенными в этот случай.