Pull to refresh

Comments 82

Мне кажется, абсолютно необходимо упомянуть, что аннотации типов это не проверка типов, а подсказка компилятору.
>>> def f(a: str):
...     print(a)
...     return
... 
>>> f('a')
a
>>> f(1.0)
1.0
>>> import sys
>>> sys.version
'3.5.2 (default, Nov 12 2018, 13:43:14) \n[GCC 5.4.0 20160609]'
Не только компилятору, но и всяким IDE
Моя интуиция почему-то протестует против внесения в синтаксис языка фичей предназначеных для IDE. Слишком сильно связаны получаеются IDE и язык.
а как комплиятор сейчас это использует? Насколько я читал, CPython пока никак не использует type annotation, а вот IDE уже вовсю
Сам компилятор в рантайме не использует, но можно подключить «runtime code evaluator» и он будет проверять данные в рантайме.
Ссылка: the state of type hints in Python
Использование аннотаций, помимо прочего, достаточно сильно повышает читаемость кода. Так что можно считать это не только фичей для IDE.
Есть еще анализатор mypy, который умеет проверять соответствие типов

Для произвольного кода?


Какова вообще выразительная сила тайпхинтов как системы типов?

Там есть Any, Union (sum type), дженерики и пр. Т.е. можно описать любой тип, даже сложную вложенную структуру или функцию. Можно создать свой кастомный тип.
В общем Python списывает хорошее у Haskell ещё начиная со списковых выражений.
подробнее здесь: docs.python.org/3/library/typing.html
дженерики

А там накладывать требования на типы можно? Ну, например, чтобы у меня дженерик был не поверх любого типа T, а поверх только U[T] для фиксированного (или нет) U?


В общем Python списывает хорошее у Haskell ещё начиная со списковых выражений.

Тайпклассы, экзистенциальные типы, кусочки зависимых типов, kind polymorphism?

чтобы у меня дженерик был не поверх любого типа T, а поверх только U[T] для фиксированного (или нет) U?

Кажется, да.

Много можно чего ещё скопировать из Haskell (и раздуться похлеще монстра C++), но задачи такой, как я понимаю, не стоит. Я просто имел в виду, что разработчики Python в сторону Haskell смотрят внимательно и заимствуются фичи оттуда давно, потому что list comprehensions в Python с 2000 года.
Много можно чего ещё скопировать из Haskell (и раздуться похлеще монстра C++)

Ну это вы зря так, сам язык (если на библиотеку не смотреть) вообще мелкий, меньше сотни страниц ЕМНИП.

Можно указать типы при создании TypeVar и будет он co-, contra- или in- вариантным. К сожалению, пока этот механизм в том же mypy и pycharm работает с проблемами

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

Аннотации это для чего угодно, но не для исполнения кода: для читаемости, для подсказок в IDE, для статического анализа (mypy тот же самый), etc.
Ну здравствуйте, вроде как всю жизнь код питон компилируется в промежуточный байт-код и этот байт код затем исполняется.
Ну объектные файлы же не продуцируются. Слинковать с другими объектными файлами ничего нельзя.

Байт-код все равно исполняется виртуальной машиной.
С точки зрения типов, компилятор мог бы проверять типы на этапе компиляции байт кода. Таким образом, можно проверять код до его запуска. У чистого интерпретатора, без этапа компиляции такой возможности нет, в случае ошибки типа он все-равно должен закончить выполнение.
Т.е. у чистого интерпретатора мало смысла в проверке типов.
Я то сам, дне думаю, что для динамического языка не стоит внедрять обязательную проверку типов. Ну если только в качестве опции в виде билбиотеки. Не в ядре.
Я за то, чтобы сохранять ядро как можно более компактным.
UFO landed and left these words here
С такой логикой любой язык можно назвать интерпретируемым, ведь в конечном итоге всё интерпретируется процессором.
При всём уважении к Вашему юзернэйму, это так не называется.
Не называется, но если следовать логике lorc (при которой если язык компилируется в нечто, что далее интерпретируется, то сам язык считается интерпретируемым), все языки можно назвать интерпретируемыми.
Нет, это не моя логика. Пожалуйста, не надо приписывать мне то, чего я не говорил.
А это чьи слова:
Байт-код все равно исполняется виртуальной машиной.

И это в ответ на утверждение, что питон компилируется. Из контекста я понял, что вы считаете его интерпретируемым. Или я ошибаюсь?
Да, я продолжаю считать питон интерпретируемым. И буду считать его таким, пока не смогу получить из программы на питоне объектный файл, скормить его ld и слинковать его с моей программой на С.
Из программы на питоне можно получить .pyc файл. И при этом ни строчки из этой программы не исполнить.
Скажите, .pyc-файл исполняется непосредственно на процессоре, без вспомогательных прокладок?

Вот когда начнёт исполняться — тогда и будет компилируемым.
Но байткод-то в результате чего получается? В результате компиляции питонокода.

Машкод тоже может не исполнятся непосредственно на процессоре. В современных процах он собственно и не исполняется, а транслируется в микрокоды, которые затем исполняются.
Компилируемый язык программирования — язык программирования, исходный код которого преобразуется компилятором в машинный код.

В машинный.
p.s. Тут какой-то спор о терминах, которые уже давно устоялись.

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


Можно и поспорить ;)
Нет. Компилятор производит объектные файлы, которые потом линкуются в исполняемый файл. Который потом может быть загружен в память и исполнен процессором. Это вроде бы обычный способ определять компилируемый язык.

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

если вы запакуете виртуальную машину и байт-код в один файл, то это все равно не будет скомпилированной программой

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

Я бы называл питон компилируемым в байт-код. Но да, сам байткод при этом интерпретируется (как и машинный код).
>>Я бы называл питон компилируемым в байт-код. Но да, сам байткод при этом интерпретируется (как и машинный код).

После последней фразы могу запостить только эту ссылку. Финиш, конечно:
www.youtube.com/watch?v=LLk9_EH6Pfo
Эм в этом примере наоборот же получается, чистый питон игнорирует аннотации.
Ну да, я это и хотел показать. Что чистый питон не проверяет типы. Если бы проверял, он что-то бы сделал, например выбросил бы исключение.
Ну скажем так, это не настоящая проверка типов, но кое-где они проверяются. Например dataclass не создает параметр для __init__ у которого не проставлен тип данных или добавленная перегрузка overload, из того же пакета typing, вообще не работает. Очень надеюсь в будущих выпусках разработчики усилят это направление.
Для меня большой вопрос, нужно ли тащить статическую проверку типов в рантайм питона. Возможно просто я не сталкиваюсь с необходимостью статической проверки, но лично меня вполне устраивает дак тайпинг в питоне. Если нужна строгая типизация можно просто взять другой язык. Ну хотя бы тот же Cython. Или Си или C++.
Если все тащить в одну кучу получится еще один C++, точнее получим все недостатки C++, без его достоинств.
В моей практике ошибки с типами так редки, что ИМХО не стоит городить огород.

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

В виде подключаемых библиотек я готов мириться. Пусть будет, для любителей. Но только не в ядре языка.
По моему это просто мода. Лично я накушался строгой типизации в C++. А так сначала можно затащить строгую типизацию в питон, а потом без дизайн паттернс ни шагу ступить нельзя будет.
Без аннотации типов командой больше 5 человек разрабатывать очень трудно. Совершенно непонятно что тебе пробросили через 3 функции. Или могу я вызвать чужой метод и отправить в него set вместо list. это нужно еще и чужой код просмотреть прежде чем использовать.

Ну и уж совсем мечты это нормально предсказание типов для LLVM и для компилятора в WebAssemly, а то питон сейчас жив одним ML
строгая статическая типизация
статическую проверку типов в рантайм

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

Если нужна строгая типизация можно просто взять другой язык

Типизация в питоне, кстати, строже чем в большинстве скриптовых. Хоть функции и принимают любые объекты, но вольностей, типа складывания строки с числом он не позволяет, и неявных приведений типов (как в js — лишь бы что-то выполнить) он не делает.
Хоть функции и принимают любые объекты, но вольностей, типа складывания строки с числом он не позволяет, и неявных приведений типов (как в js — лишь бы что-то выполнить) он не делает.

>>> 3 == "10"
False

Сравнивать значения разных типов он, однако, вполне себе позволяет.

Потому что это осмысленная операция, и, в отличие от всяких JS и PHP, питон не будет пытаться привести их к одному типу, т.е. для разных типов всегда будет False.
$ php -r 'var_dump(3 === "10");'
bool(false)
Ваше === — это костыль для замены == с его глупым поведением.
Невежа. Тьфу на вас.
Просвятите, какая необходимость иметь в вашем языке оператор ==?
Мой язык — русский, в нём нет этого оператора. Кто касается ПХП, то достаточно вспомнить историю языка — простая работа с вебом, данные из форм приходят нетипизированно.

Для разных типов должен кидаться экзепшон, потому что никакие два значения разных типов никогда не могут быть равны. Если я сравниваю значения разных типов, то у меня ошибка в логике. John Major equality — плохая штука.


Иными словами, False в 3 == 4 и False в 3 == "4" имеют принципиально разную семантику.

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

А зачем параметры различного типа сравнивать? Они же различного типа.

С динамической типизацией тип параметров заранее может быть не известен же.

Со статической в каком-то смысле тоже не обязан. Обычное гомогенное равенство вполне себе полиморфно.

>> 3 == "10"
False
Правильнее так:
>>> print( 10 == "10")
False

Если числа разные — непонятно, что есть False
Без print не работает
>>>  3 == "10"
  File "<stdin>", line 1
    3 == "10"
    ^
IndentationError: unexpected indent

> Без print не работает

Конечно, если пробел впереди воткнуть — работать не будет.

>>> 3 == "10"
False
>>>  3 == "10"
  File "<stdin>", line 1
    3 == "10"
    ^
IndentationError: unexpected indent


только print тут ни при чём.
Clojure — интерпретируемый язык с динамической типизацией, но там есть библиотека spec, которая позволяет описать «схему» данных примерно как в Python, но в рантайме это всё проверяется на корректность. И также есть аннотации типов, которые позволяют указать тип Java и это влияет на производительность, потому что можно написать алгоритм, который будет работать только со списком Long, не задействуя рефлексию.
не компилятору, а статическому анализатору
Наверное лучше вообще убрать и компилятор и статический анализатор и просто написать; «это просто подсказки».

Также, type hints можно использовать, когда нам нужна явная привязка названия переменной к её типу. Например, есть библиотека pydantic, которая использует type hint'ы в датаклассах для маршаллинга и валидации схемы данных. Мы перешли на неё с marshmallow, очень удобно.

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

Python молодцы. Многие языки еще долго будут догонять.

Еще бы добавить аннотаций для функционального программирования и будет еще круче
Повторюсь. Я за то, чтобы сохранять ядро языка как можно более компактным. Чтобы его можно бы было всюду использовать с минимальными изменениями.
По идее давно напрашивается модульный язык. Зачастую в энтерпрайзе надо чтобы программисты использовали одни и те же конструкции и не использовали какие-то другие. Надо научить язык отсекать лишние в конкретном проекте конструкции.
В модуле typing вроде есть Callable? Т.е. функции высшего порядка вполне могут работать с этими аннотациями.
аннотаций для функционального программирования

Вы мне лучше скажите, какая аннотация должна быть у функции, принимающей совпадение по регулярке (тип _sre.SRE_Match)?
Какие возможности Python 3 вы добавили бы в приведённый здесь список?
asyncio и холиварный := (3.8+)
что сказать, язык будущего))

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

и очень интересная фича LRU кэш, я чессно не знал и dict юзал
кста вроде одинакого по скорости этот кэш и ручной через dict, кто нибудь протестил нормально скорость??

имхо, в Питон версии 100500 + следует ввести:
  • инспекцию структуры любого обьекта с преттипринтом или лучше в визуальном представлении, когда берёшь неизвестный модуль, где документация плохая а все типы стёрты
  • обьединить нотацию для класса и dicta, inst['field'] эквивалентно inst.field

    интерпретировать сложные comprehention декларативно с оптимизаций и кешем где надо


    встроить без геморa частичные функции, с поддержкой интерпретатора заместо partial:

    def fn ( a,b,c)

    k = fn(1,2) #-> обьявление частичной функции k(c) c изсестными a,b,
    потом
    k[0] = 3 # безгеморная замена параметра на определённом месте




    сложные lambda обьявления с функционалом как обычный метод

    fib = lambda @lru_cache(maxsize=512), a: int ,b : int -> int : if number == 0: return 0
    if number == 1: return 1

    return fib_memoization(number-1) + fib_memoization(number-2)





Все же не так красиво, да и по скорости f-string в разы* быстрее:
Type            Trial 1                 Trial 2                 Trial 3
%:              0.273233943000          0.268914790000          0.273714235000
str.format():   0.7942503730000681      0.793637686999773       0.7926878570001463
str.Template(): 3.3321329630002765      3.3256752329998562      3.315622544999769
f-string:       0.1914799450000828      0.18900782099990465     0.19004946999984895
Тестировалось на python 3.7, оригинал.
ну ясно дело оптимизировали и сделали изящнее.
оно и раньше так же примерно работало

Нет, не так же. Можно было только подставлять значения, а делать вычисления прямо внутри было нельзя (теперь в f-string можно). Так что теперь нужно быть осторожнее с такими строками. <sarkazm>Это почти как маленький php внутри python.</sarkazm>
Ко всему + то что сказал lega производительность в разы больше.


Примерные тесты
~ $ python3.7 -m timeit 'a = 1' 'b = "string"' '"Test strings formatting: {a} {b}".format(**locals())'
500000 loops, best of 5: 620 nsec per loop
~ $ python3.7 -m timeit 'a = 1' 'b = "string"' 'f"Test strings formatting: {a} {b}"'
2000000 loops, best of 5: 157 nsec per loop
~ $ python3.6 -m timeit 'a = 1' 'b = "string"' '"Test strings formatting: {a} {b}".format(**locals())'
1000000 loops, best of 3: 0.599 usec per loop
~ $ python3.6 -m timeit 'a = 1' 'b = "string"' 'f"Test strings formatting: {a} {b}"'
10000000 loops, best of 3: 0.142 usec per loop
~ $ python2.7 -m timeit 'a = 1' 'b = "string"' '"Test strings formatting: {a} {b}".format(**locals())'
1000000 loops, best of 3: 0.435 usec per loop

Изменения крутые и полезные.
Очень хотелось бы что бы исправили концептуальные проблемы языка, даже если это означает python 4 и сломанную обратную совместимость.


Например те же импорты все ещё очень сложны для понимания новичками (особенно по сравнению с другими языками). При этом циркулярные импорты падают в рантайме с неадекватной ошибкой, а orm фреймворки вроде той же алхимии предлагают решать проблему просто — указывать название классов текстом *facepalm".

К сожалению, я изучал питон очень давно и не могу 100% вспомнить какие у меня были проблемы. Но, кажется, импорты — это не та вещь, которая мне показалась непонятной.


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


В ОРМ циклические завимости надо решать не написанием названий классов текстом, а правильной расстановкой relation, мне кажется это в 95% случаев возможно (если я не прав — прошу привести пример).

циклическая зависимость легко может разрешаться интерпретатором без жалобы на якобы ошибки, зря что так не встраивают. Странное дело её не разруливает даже maven в своих проэктах…

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

циклическая зависимость легко может разрешаться интерпретатором без жалобы на якобы ошибки
Как такая цикличекая зависимость может решиться интерпретатором?
# a.py
import b
value = b.value + 5

# b.py
import a
value = a.value + 7

PS: В питоне нормально решаются циклические зависимости, проблемы не в питоне, а «в руках».
её не разруливает даже maven в своих проэктах

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

LRU-кэш полезная штука, но надо пользоваться с умом, т.к. он подходит только для идемпотентных функций(методов)
Если говорить о Python 3 в целом, то в версии 3.8 у нас будут Assignment Expressions (PEP 572):
if x := True:
     print(f'{x}')
# Выведет: True

x = 2
print([y := x**x, y**2, y**3])
# Выведет: [4, 16, 64]


И много разговоров про None-aware operators (PEP 505), значительно сокращающие код:
# Old
data = data if data is not None else []
# New
data = data ?? []

# Old
if lst:
    lst.append('string')
# New
lst?.append('string')

# Old
if callable(foo):
    foo()
# New
foo?()

# Old
if x:
   x + 1
# New
x? + 1

# Old
result = a
try:
    result = result.b
except AttributeError:
    pass
else:
    result = result.c
    try:
        result = result.d
    except AttributeError:
        pass
    else:
        result = result.e
# New
a?.b.c?.d.e

Однако PEP 505 сейчас в состоянии Deferred.

Ну и конечно следовало в статье упомянуть asyncio — это важная часть, по которой сделано много хорошей и качественно работы.
а ничего что по идее должно выбрасываться exception и исполнение прекращается, а тут проглатываются эксепшены и выдаётся None которое может стать ложноположительным?

Используйте a.b.c.d.e и будет exception

Only those users with full accounts are able to leave comments. Log in, please.