Pull to refresh

Comments 97

UFO just landed and posted this here
int(null) = 0, всё логично.
Тем не менее интересно иногда заглянуть, как оно внутри работает
Простреленная нога-то? Да, интересно.
Это заставляет лучше понять логику работы языка программирования. На мой взгляд без этого тяжело. Ну и в целом это интересно. Покопаться как оно под капотом устроено
Как выстрелить себе в ногу на Питоне.


Без использования указателей!
С указателями был бы Undefined Behavior =) Тут к бабке не ходи =)
UFO just landed and posted this here
В 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.

UFO just landed and posted this here
Это ооочень упрощённое представление. А вызовы деструкторов для локальных объектов? А восстановление регистров и SEH-кадра? А проверка канарейки на стеке? А инлайнинг?
UFO just landed and posted this here
в C++ такого рода штуки чаще всего решаются через Undefined Behavior. Так как стандарт чаще всего избегает их регулирования и уводит все на реализацию конкретных компиляторов. Если я правильно помню — те же соглашения о вызове функции — тоже специфичны для каждого компилятора.
Один из моих любимых вопросов в C++ — что будет если через const_cast снять константность с this в const-методе и изменить поле =)
UFO just landed and posted this here
Либо изменится, либо SIGSEGV, если константный объект размещён в read-only секции.
В первом случае — либо дальнейшие обращения к этому полю будут читать новое значение из памяти, либо старое, если компилятор его где-то закешировал.
Масса возможностей!
UFO just landed and posted this here
Это не обязательный блок.

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

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

Кто такое сказал? В стандарте такого нет.
UFO just landed and posted this here

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

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

Если следовать этой логике, блок finally должен отрабатываться при вызове любой функции внутри блоков try и catch.
UFO just landed and posted this here
Такие вопросы, на мой взгляд, хотя и логичны — но часто вызывают кучу проблем, когда первый раз с ними сталкиваешься.
Поэтому и хочется просвещать людей =)
Лол, а если в 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
}

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

По моему 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. Надо будем посмотреть.
Python 3.7.7 (D:/Python37-32/python.exe)
>>> def foo():
    try:
        return 1
    finally:
        return 2
    
>>> foo()
2
>>> import dis
>>> dis.dis(foo)
  2           0 SETUP_FINALLY            4 (to 6)

  3           2 LOAD_CONST               1 (1)
              4 RETURN_VALUE

  5     >>    6 LOAD_CONST               2 (2)
              8 RETURN_VALUE

Немного смущает, что в каждой версии языка байт код увеличивается. (?)

На условном «собеседовании» кандидату после этой задачи обязательно нужно предложить решить следующий вариант:
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)

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

UFO just landed and posted this here
И этот топик наглядно иллюстрирует преимущества RAII перед finally :)

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

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

JS также вернет return из блока finally.
JS также вернет return из блока finally.
итого 14 микро-фишек в копилке…
Пять старушек — рубль!
Sign up to leave a comment.