Как стать автором
Обновить

Комментарии 96

Я теперь понял почему при подаче заявки на ипотеку через домклик возвращается null
P.S. пояснение: сбер при неудовлетворении заявки просто сообщает что клиенту отказано, однако многие другие банки подробно поясняют причину, предлагают хотя бы меньшую сумму. Не знаю, может это не массово, но слышал от нескольких человек и лично с этим тоже сталкивался. / сори за оффтоп
int(null) = 0, всё логично.
Как выстрелить себе в ногу на Питоне.
Тем не менее интересно иногда заглянуть, как оно внутри работает
Простреленная нога-то? Да, интересно.
Это заставляет лучше понять логику работы языка программирования. На мой взгляд без этого тяжело. Ну и в целом это интересно. Покопаться как оно под капотом устроено
Как выстрелить себе в ногу на Питоне.


Без использования указателей!
С указателями был бы Undefined Behavior =) Тут к бабке не ходи =)
В java так-же, если что
В js такое же поведение при использовании return в try finally

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

спасибо. Поправил

А в C# это будет ошибка компиляции:
Control cannot leave the body of a finally clause

Да это вообще какое-то странное поведение, на мой взгляд. return должен выходить из функции, а не идти в блок finally с переписыванием возвращаемого результата.

Вполне ожидаемое поведение - finally на то и finally, чтобы выполняться независимо от результата try/except блоков, поэтому ответ был почти очевиден. Другое дело, что так писать действительно не стоит, да поможет нам линтер (не уверен, отслеживают ли они такие конструкции).

Звучит как отличный feature request

На мой взгляд ожидаемое поведение, это когда после return функция заканчивается

И чем же return такой особенный, что после него блок finally не должен выполнятся, когда вся его суть в том и состоит, что он выполняется всегда, независимо от того, что произошло внутри блока try.

Всем?
В x86 Assembly (С/С++) это две инструкции leave, ret строго друг за другом.
При возврате результата — всегда push, leave, ret. Во всяком случае так код генерируется GCC, FPC и VisualStudio.
Это ооочень упрощённое представление. А вызовы деструкторов для локальных объектов? А восстановление регистров и SEH-кадра? А проверка канарейки на стеке? А инлайнинг?
Инлайн-функции — это вообще даже не функции как таковые, это включение кода тела функции в точку её вызова.
Восстановление регистров — это в первую очередь задача компилятора до вызова leave и зависит от используемого соглашения о вызовах.
leave вообще должна восстановить указатель на стек, целостность стэка при этом в большинстве компиляторов не проверяется, что иногда используется для разных хаков, в том числе на inline assembly.
Деструкторы, насколько я помню внутрянку, и различные реализации на чистом C это коллбэк вызываемый до выхода из функции, через атрибут компилятора. Это не обязательный блок.
Суть в том что return всегда должен осуществить безусловный выход, без выполнения иных блоков кода.
в C++ такого рода штуки чаще всего решаются через Undefined Behavior. Так как стандарт чаще всего избегает их регулирования и уводит все на реализацию конкретных компиляторов. Если я правильно помню — те же соглашения о вызове функции — тоже специфичны для каждого компилятора.
Один из моих любимых вопросов в C++ — что будет если через const_cast снять константность с this в const-методе и изменить поле =)
В GCC — поле изменится, т.к. мы все равно оперируем ссылкой на экземпляр объекта в оперативной памяти.
Экземпляр объекта — это структура в памяти со ссылкой на класс объекта и набором свойств. Ссылка на класс объекта нужна чтобы найти точку входа в функцию (метод класса). То есть сегмент кода общий на все экземпляры. Ограничения константности всегда можно обойти, в том числе через intptr.
Либо изменится, либо SIGSEGV, если константный объект размещён в read-only секции.
В первом случае — либо дальнейшие обращения к этому полю будут читать новое значение из памяти, либо старое, если компилятор его где-то закешировал.
Масса возможностей!
Спасибо, похоже зависит от архитектуры процессора, компилятора, ОС, и фазы луны.
На QNX ни разу SIGSEGV не прилетал.
Хабр — торт!
Это не обязательный блок.

Так они все необязательные: и leave необязательный (FPO), и ret необязательный (TCO).
Плюсовый return может скомпилироваться в любое число инструкций, от нуля до бесконечности.

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

Кто такое сказал? В стандарте такого нет.
Ладно, примем как факт что я не знаю С++ достаточно хорошо.
Более-менее я знаю только ANSI C 89 и ANSI C 98, плюс когда то в студенчестве писал свою ОС на ассемблере под x86 32 битный реальный режим с поддержкой гибридной адресации памяти (до 4х ГБ адресного пространства).

finally не выполняется, если вызвать System.exit(0) :)

В Java, finally не выполняется, если в try вызвать exit() :)

Если следовать этой логике, блок finally должен отрабатываться при вызове любой функции внутри блоков try и catch.

Finally/defer буквально придумали против этого.
Чаще всего используется для освобождения ресурсов.

Такие вопросы, на мой взгляд, хотя и логичны — но часто вызывают кучу проблем, когда первый раз с ними сталкиваешься.
Поэтому и хочется просвещать людей =)
Лол, а если в finally освобождение системного ресурса?
Значит ССЗБ, если вышли раньше, чем освободили ресурс.
В шарпе такая конструкция будет отлично работать:
try
{
    return ...;
}
finally
{
    Dispose();
}


В нем синтаксически запрещен возврат значений из метода из блока finally, что и правильно. Т.к. у finally другое предназначение, именно то которое вы указали, освободить ресурсы.
В Delphi/ObjectPascal ровно тоже самое — ошибка компиляции, что логично

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

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

По моему автор не раскрыл тему полностью, я на питоне мало писал, но у меня по ходу чтения статьи возникает много логических вопросов:
1)
Если обратиться к исходному коду CPython, то можно увидеть следующие строчки
так мы же смотрели на байт код до этого, получается python код -> байт код -> CPython? Не проще ли сразу рассматривать что получается в CPython?

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

да не очень то и просто, почему
goto fast_block_end;
возвращает в нашу функцию?
Байт-код — это, по сути, низкоуровневый код Вашей программы (грубо говоря такой ассемблер)
Cpython в данном случае выступает как интерпретатор этого байт-кода. То есть это цикл, который читает команды на байт коде и их выполняет. В частности в вырезке показан код обработки оператора return.
Этот goto завершает обработку текущей команды в интерпретаторе и интерпретатор переходите считыванию следующей команды.
golang:
func foo() error{
err, obj := makeObj()
defer func(){
    err = obj.Close()
    if err!= nil {log}        
}()
....
return err
}
Будет возвращать ту err которая в defer, не смотря на то что управление функции дошло до return

У питона про это хотя бы какая-то дока есть. Есть ли про это поведение дока в golang?

Deferred functions may read and assign to the returning function's named return values.

In this example, a deferred function increments the return value i after the surrounding function returns. Thus, this function returns 2:
func c() (i int) {
    defer func() { i++ }()
    return 1
}

Так это не named return value

Копипаст конечно неплохо, но ссылкой все же лучше.

По моему defer в go немного другое значение имеет чем finally в других языках.

В go эти функции ставятся в очередь и выполняются друг за другом после выполнении всей функции, try finally выполняется в самой функции. Хотя предназначение одно

В документации описано данное поведение функции:
The return value of a function is determined by the last return statement executed. Since the finally clause always executes, a return statement executed in the finally clause will always be the last one executed


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

Один из постулатов философии Python:
Явное лучше, чем неявное.


Здесь мы видим неявное поведение. И это нехорошо.

Ну на самом деле, не более неявное, чем если написать "a=1", а где-нибудь дальше в коде "a=2". То есть "неявным" это поведение делает не логика работы return и finally, а решение повторно использовать return.

А поймать то, что вернется в первом return я так понимаю невозможно в принципе?
насколько мне известно — нет

Смотреть код языка Питон на Си, чтобы понять, что и почему возвращяет код на Питон - интересное решение.

Это единственный источник правды. Документация обманет, комментарии соврут, а код — нет.
Странно, а у меня вывод несколько иной, константа 1 вообще не грузится:
Python 3.9.1 (tags/v3.9.1:1e5d33e, Dec  7 2020, 17:08:21) [MSC v.1927 64 bit (AMD64)] on win32
def foo():
    try:
        return 1
    finally:
        return 2
    
import dis
dis.dis(foo)
  2           0 SETUP_FINALLY            6 (to 8)
  3           2 POP_BLOCK
  5           4 LOAD_CONST               1 (2)
              6 RETURN_VALUE
        >>    8 POP_TOP
             10 POP_TOP
             12 POP_TOP
             14 POP_EXCEPT
             16 LOAD_CONST               1 (2)
             18 RETURN_VALUE
             20 RERAISE
             22 LOAD_CONST               0 (None)
             24 RETURN_VALUE
Проверил на python 3.8 и python 3.9. Такое происходит только в 3.9.
Может быть какая-то оптимизация такой результат даёт?

Примерно то же самое наблюдается с таким кодом

Python 3.9.5 (default, May 24 2021, 12:50:35) 
[GCC 11.1.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import dis
>>> def foo():
...     try:
...             raise RuntimeError()
...     except:
...             return 1
...     finally:
...             return 2
... 
>>> dis.dis(foo)
  2           0 SETUP_FINALLY           34 (to 36)
              2 SETUP_FINALLY           10 (to 14)

  3           4 LOAD_GLOBAL              0 (RuntimeError)
              6 CALL_FUNCTION            0
              8 RAISE_VARARGS            1
             10 POP_BLOCK
             12 JUMP_FORWARD            16 (to 30)

  4     >>   14 POP_TOP
             16 POP_TOP
             18 POP_TOP

  5          20 POP_EXCEPT
             22 POP_BLOCK

  7          24 LOAD_CONST               1 (2)
             26 RETURN_VALUE

  5          28 RERAISE
        >>   30 POP_BLOCK

  7          32 LOAD_CONST               1 (2)
             34 RETURN_VALUE
        >>   36 POP_TOP
             38 POP_TOP
             40 POP_TOP
             42 POP_EXCEPT
             44 LOAD_CONST               1 (2)
             46 RETURN_VALUE
             48 RERAISE
             50 LOAD_CONST               0 (None)
             52 RETURN_VALUE
>>> 
интересно, не знал, что такое поведение в 3.9. Надо будем посмотреть.
На условном «собеседовании» кандидату после этой задачи обязательно нужно предложить решить следующий вариант:
def foo():
    try:
        return 1
    finally:
        pass


print(foo())


ответ очевиден после прочтения этой статьи
>>> print(foo())
... 1

А что в этом варианте вас смущает?
pass служит для указания пустого блока. То есть ничего не происходит в finally.
Видимо без статьи ответ был бы скорее всего None
Да, как ответили выше, не зная подробностей, описанных в статье, но зная, как ведёт себя конструкция с return в блоке finally, можно подумать, что будет возвращено значение из последнего выполненного блока, в данном случае — None, который возвращается при отсутствии return. Но в данном случае, блок finally будет выполнен, но функция возвратит значение, которое было задано оператором return.
Как видите, даже несмотря кажущуюся неочевидность, Python, на мой взгляд, ведёт себя максимально понятно и логично: блок finally выполняется после блока try
Изо всех сил пытался найти неочевидность, но не смог. Finally на то и finally.
Даже из комментариев видно, что как минимум не для всех такое поведение очевидно

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


If the try statement reaches a break, continue or return statement, the finally clause will execute just prior to the break, continue or return statement’s execution.

If a finally clause includes a return statement, the returned value will be the one from the finally clause’s return statement, not the value from the try clause’s return statement.

До return из try блока выполнится finaly, а раз там стоит свой return, то только он и выполнится.

В действительности-то наоборот: выполняется сначала return из try, а потом return из finally.
Вот для того, чтобы обнаружить, что документация врётустарела — и приходится заниматься глупостями с дизассемблером :)

Специально потратил 2 минуты на проверку в консоли:


igor@home:~$ cat ./test.py 
def fish():
    try:
        return 1
    finally:
        return 2

print(fish())
igor@home:~$ python3 ./test.py 
2
igor@home:~$ 

и где тут единичка в выводе?

А вы статью читали?

Читал, хотя, пока не попробовал так:


v = 0

def inner(t):
    global v
    v += t

def fish():
    try:
        return inner(1)
    finally:
        return inner(2)

fish()
print(v)

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

Интересно, а в С++ какое поведение будет?

Неопределеное

/s

В C++ нет finally.
И этот топик наглядно иллюстрирует преимущества RAII перед finally :)

Не совсем. Если в деструкторе бросить исключение, то будет очень больно.

А в C++ нет finally. Есть только конструкция try-catch и деструкторы.
Роль finally-блоков выполняют деструкторы, и вернуть значение из них нельзя.

+1 микро-фишка питона в копилочку

JS также вернет return из блока finally.

+1 микро-фишка питона в копилочку

JS также вернет return из блока finally.

+1 микро-фишка питона в копилочку

+1 микро-фишка питона в копилочку

+1 микро-фишка питона в копилочку

+1 микро-фишка питона в копилочку

+1 микро-фишка питона в копилочку

+1 микро-фишка питона в копилочку

+1 микро-фишка питона в копилочку

+1 микро-фишка питона в копилочку

+1 микро-фишка питона в копилочку

+1 микро-фишка питона в копилочку

+1 микро-фишка питона в копилочку

+1 микро-фишка питона в копилочку

итого 14 микро-фишек в копилке…
Пять старушек — рубль!
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.