О Python на Хабре было много хороших статей. Как об особенностях реализации, так и о прикладных фичах, отсутствующих в других мейнстримных языках. Однако я с удивлением обнаружил (поправьте, если не прав), что есть одна важная тема, не раскрытая ни на Хабре, ни в русскоязычном интернете вообще. Эта статья будет посвящена такой штуке, как stack frame. Скорее всего она не скажет ничего, ну или может с учетом последнего пункта почти ничего нового опытным python-разработчикам, однако будет полезна новичкам (а может и вредна, но все примеры ниже).
Я постарался написать статью так, чтобы её было удобно читать, открыв параллельно repl и бездумно копировать туда код эксперементируя. Поэтому по возможности большая часть примеров имеет вид «однострочники в интерпретаторе».
Начнем мы немного издалека, с того что заметим, что Traceback это тоже объект, а потом найдем где там стековый кадр и уже перейдем к делу.
>>> import requests
>>> requests.get(42)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3/dist-packages/requests/api.py", line 55, in get
return request('get', url, **kwargs)
File "/usr/lib/python3/dist-packages/requests/api.py", line 44, in request
return session.request(method=method, url=url, **kwargs)
File "/usr/lib/python3/dist-packages/requests/sessions.py", line 421, in request
prep = self.prepare_request(req)
File "/usr/lib/python3/dist-packages/requests/sessions.py", line 359, in prepare_request
hooks=merge_hooks(request.hooks, self.hooks),
File "/usr/lib/python3/dist-packages/requests/models.py", line 287, in prepare
self.prepare_url(url, params)
File "/usr/lib/python3/dist-packages/requests/models.py", line 338, in prepare_url
"Perhaps you meant http://{0}?".format(url))
requests.exceptions.MissingSchema: Invalid URL '42': No schema supplied. Perhaps you meant http://42?
>>> import sys
>>> sys.last_traceback
<traceback object at 0x7f8c37378608>
>>> dir(sys.last_traceback)
['tb_frame', 'tb_lasti', 'tb_lineno', 'tb_next']
>>> sys._getframe()
<frame object at 0x7fda9bffa388>
>>> sys._getframe().f_back
>>> sys._getframe(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: call stack is not deep enough
>>> (lambda: sys._getframe().f_back == sys._getframe(1))()
True
И даже (хотя это было ожидаемо)
>>> (lambda: sys._getframe().f_back is sys._getframe(1))()
True
>>> import threading
>>> sys._current_frames()
{140576946280256: <frame object at 0x7fda9bffa6c8>}
>>> threading.Thread(target=lambda: print(sys._current_frames())).start()
{140576883275520: <frame object at 0x7fda9b337048>, 140576946280256: <frame object at 0x228fbc8>}
CPython implementation detail: This function should be used for internal and specialized purposes only. It is not guaranteed to exist in all implementations of Python.
>>> threading.Thread(target=lambda: print(sys._getframe(1).f_code.co_name)).run()
run
>>> threading.Thread(target=lambda: print(inspect.stack()[1][3])).run()
run
>>> threading.Thread(target=lambda: print('called from', sys._getframe(1).f_globals['__name__'])).run()
called from threading
import sys
class Example(object):
def __init__(self):
pass
def check_noosphere_connection():
print('OK')
def proof(example):
a = 2
b = 2
example.check_noosphere_connection()
print('2 + 2 = {}'.format(str(a + b)))
def broken_evil_force():
def corrupted_noosphere(pseudo_self):
print('OK')
frame = sys._getframe()
frame.f_back.f_locals['a'] = 3
FakeExample = type('FakeExample', (), {'check_noosphere_connection': corrupted_noosphere})
proof(FakeExample())
% python habr_example.py
OK
2 + 2 = 4
import sys
import ctypes
class Example(object):
def __init__(self):
pass
def check_noosphere_connection():
print('OK')
def proof(example):
a = 2
b = 2
example.check_noosphere_connection()
print('2 + 2 = {}'.format(str(a + b)))
def broken_evil_force():
def corrupted_noosphere(pseudo_self):
print('OK')
frame = sys._getframe()
frame.f_back.f_locals['a'] = 3
FakeExample = type('FakeExample', (), {'check_noosphere_connection': corrupted_noosphere})
proof(FakeExample())
def evil_force():
def corrupted_noosphere(pseudo_self):
print('OK')
frame = sys._getframe()
frame.f_back.f_locals['a'] = 3
ctypes.pythonapi.PyFrame_LocalsToFast(ctypes.py_object(frame.f_back), ctypes.c_int(0))
FakeExample = type('FakeExample', (), {'check_noosphere_connection': corrupted_noosphere})
proof(FakeExample())
if __name__ == '__main__':
broken_evil_force()
evil_force()
% python habr_example.py
OK
2 + 2 = 4
OK
2 + 2 = 5
К сожалению, не доступен сервер mySQL