Comments 97
Как выстрелить себе в ногу на Питоне.
Без использования указателей!
Ничего функция не вернет, будет синтаксическая ошибка. Вы забыли двоеточие поставить после закрывающей скобки.
А в C# это будет ошибка компиляции:
Control cannot leave the body of a finally clause
Да это вообще какое-то странное поведение, на мой взгляд. return должен выходить из функции, а не идти в блок finally с переписыванием возвращаемого результата.
Вполне ожидаемое поведение - finally на то и finally, чтобы выполняться независимо от результата try/except блоков, поэтому ответ был почти очевиден. Другое дело, что так писать действительно не стоит, да поможет нам линтер (не уверен, отслеживают ли они такие конструкции).
Звучит как отличный feature request
На мой взгляд ожидаемое поведение, это когда после return функция заканчивается
И чем же return
такой особенный, что после него блок finally
не должен выполнятся, когда вся его суть в том и состоит, что он выполняется всегда, независимо от того, что произошло внутри блока try
.
Один из моих любимых вопросов в C++ — что будет если через const_cast снять константность с this в const-методе и изменить поле =)
Это не обязательный блок.
Так они все необязательные: и
leave
необязательный (FPO), и ret
необязательный (TCO).Плюсовый
return
может скомпилироваться в любое число инструкций, от нуля до бесконечности.Суть в том что return всегда должен осуществить безусловный выход, без выполнения иных блоков кода.
Кто такое сказал? В стандарте такого нет.
finally не выполняется, если вызвать System.exit(0) :)
В Java, finally не выполняется, если в try вызвать exit() :)
Поэтому и хочется просвещать людей =)
try
{
return ...;
}
finally
{
Dispose();
}
В нем синтаксически запрещен возврат значений из метода из блока finally, что и правильно. Т.к. у finally другое предназначение, именно то которое вы указали, освободить ресурсы.
Ну здрасьте. Весь смысл finally в том, что он выполняется всегда. А если там освобождение ресурсов?
Что касается переписывания результата, то это тоже ожидаемо, поскольку во многих языках возврат значения это сохранение результата в специальную область памяти, а затем собственно передача управления вызывающему коду.
1)
Если обратиться к исходному коду CPython, то можно увидеть следующие строчкитак мы же смотрели на байт код до этого, получается python код -> байт код -> CPython? Не проще ли сразу рассматривать что получается в CPython?
2)
Как видите, всё очень просто и понятно: мы сохраняем в переменной retval значение с вершины стека и переходим к выходу из текущего блока.
да не очень то и просто, почему
goto fast_block_end;возвращает в нашу функцию?
Cpython в данном случае выступает как интерпретатор этого байт-кода. То есть это цикл, который читает команды на байт коде и их выполняет. В частности в вырезке показан код обработки оператора return.
Этот goto завершает обработку текущей команды в интерпретаторе и интерпретатор переходите считыванию следующей команды.
Совсем не очевидное поведение
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 }
Ну вообще комментатор выше неправ. Такое поведение будет работать только если возврат именованный.
Обычный: https://play.golang.org/p/VwbT6W1FxRf
Именованный: https://play.golang.org/p/7nOVAje_FNH
Копипаст конечно неплохо, но ссылкой все же лучше.
По моему 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:
Явное лучше, чем неявное.
Здесь мы видим неявное поведение. И это нехорошо.
Смотреть код языка Питон на Си, чтобы понять, что и почему возвращяет код на Питон - интересное решение.
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.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
>>>
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.
Как видите, даже несмотря кажущуюся неочевидность, 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, то только он и выполнится.
Вот для того, чтобы обнаружить, что документация
Специально потратил 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. Есть только конструкция try-catch и деструкторы.
Роль finally-блоков выполняют деструкторы, и вернуть значение из них нельзя.
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
+1 микро-фишка питона в копилочку
Плагины, которые такое проверяют:
— github.com/wemake-services/wemake-python-styleguide
— github.com/PyCQA/flake8-bugbear `B012`
Что вернёт эта функция в Python?