Понятно, что они захватывают объекты, которые эти значения хранят. Но вы ранее утверждали, что захватываютсясохраняются только имена. Поэтому я вам и привел эту ссылку и предлагал посмотреть, что там в замыкании реально захватывается.
Можно конкретную цитату? Строго говоря, конечно, функция – это именованное лямбда-выражение.
Сами уж поищите, вы ловко виляете - понятно, что в контексте нашего разговора мы говорим не просто о функции, а о функции и захваченном контексте.
Простыня на IL для того чтобы вы посмотрели, что контекст захвачен, а Action-ном там не пахнет. Но опять же вас судя по всему интересуют теоретические витания, чем реальность данная нам в ощущениях.
Вы привели ссылки на то, что представляет собой и как организовано лексическое окружение в питоне. Что вы хотели этим доказать?
Ну вообще то там написано, что замыкания захватывают значения свободных переменных. И этим я хочу опровергнуть ваше утверждение "Лексическое замыкание означает просто сохранение лексического контекста имён свободных переменных. Сохраняются имена, а не значения.". Вы могли бы и сами это узнать, если бы прочитали ссылки, которые я привел.
То есть именно то, что я написал: лямбда-выражение – это функция; её можно преобразовать в значение (экземпляр) типа (класса) Action. Что и делает ваш List<Action>.
лямбда-выражение не функция. Вы могли бы с этим ознакомиться, если бы читали более внимательно и прочитали бы, например, последнюю ссылку (довольно объемную, но для вас там есть оглавление).
но она может быть инкапсулирована в объект класса Action или Func. Это в точности именно то, о чём я пишу всё это время, и с чем (как я понял) Вы и ваш товарищ спорите.
Вы неумело жонглируете словами, с этим я, и мой коллега по опасному бизнесу, и спорим.
Ниже IL (intermediate language) кода на C# из примера выше. Как видно ни <>c__DisplayClass0_0 ни <>c__DisplayClass0_1 не расширяют Action, но являются функциональными объектами.
Впрочем, продолжать с вами обсуждение я смысла не вижу - вы либо не читаете ссылки, которые вам дают, либо понимаете их по своему - тут я уже ничего поделать не могу. Просвещайтесь.
Удивительно, что вы не хотите посмотреть как реальность соответствует теории и продолжаете заблуждаться. Не верите отладчику и моим словам - вот вам немного ссылок:
Как вы сказали выше "Охотно верю, но надо просвещать людей." - просвещайтесь пожалуйста.
По поводу вашего вопроса вам уже ответили - там глобальный скоп - там нет свободных переменных и они не захватываются - __code__.co_names и function.__globals__ вам в помощь.
Что касается C#, то, насколько я понимаю, функциональный объект создаётся именно приведением лямбда-выражения к объектному типу Action.
Фишка не в List<Action>, а в том что создается функциональный объект, со своей памятью. Впрочем так же происходит и в случае питона - там также создается функциональный объект со своей памятью. В моем примере можете добавить вывод print(id(l)) чтобы в этом убедиться. Либо сделать dis и также убедиться что вызывается MAKE_FUNCTION.
Если под отладчиком посмотрите содержимое лямбды (l), то там можете увидеть __closure__, который содержит ссылки на свободные (захваченные) переменные (в нашем случае только одну ссылку idx_copy), а в __code__.co_freevars названия захваченных переменных. И если посмотреть на id переменных, которые лежат в __closure__, то можно заметить, что все они соответствуют последней копии idx_copy.
Я немного модифицировал пример - добавил две переменные в замыкание, чтобы было проще смотреть, что попадает в захваченные переменные.
def create():
lambdas = []
idx_copy = 0
for idx in range(10):
idx_copy = idx + 1
print ('-', id(idx), id(idx_copy))
lambdas.append(lambda : print(idx_copy, idx))
return lambdas
import dis
dis.dis(create)
def test():
for l in create():
l()
print (id(l), [(c.cell_contents, id(c.cell_contents)) for c in l.__closure__], l.__code__.co_freevars)
test()
А вот так можно сделать небольшую ошибку и сломать свою лямбду:
def create():
lambdas = []
idx_copy = 0
for idx in range(10):
idx_copy = idx + 1
print ('-', id(idx), id(idx_copy))
lambdas.append(lambda : print(idx_copy, idx))
del idx_copy
return lambdas
Причем здесь указатели на функции я простите не понял. Мы все таки вели разговор про замыкания.
Хотя непредвзятому человеку невозможно понять без поллитры, почему idx_copy должно захватываться, а idx нет.
Там оба захватываются - полно онлайн компиляторов - можно и посмотреть.
lambdas = []
for idx in range(10):
idx_copy = idx
lambdas.append(lambda : print(idx_copy))
for l in lambdas:
l()
using System;
using System.Collections.Generic;
namespace HelloWorld
{
public class Program
{
public static void Main(string[] args)
{
var lambdas = new List<Action>();
for (var idx = 0; idx < 10; idx++)
{
var idx_copy = idx;
lambdas.Add(() => Console.WriteLine($"{idx} -> {idx_copy}"));
}
foreach(var l in lambdas)
l();
}
}
}
Выбрал C# потому что ранее вы говорили про сборку мусора.
Вы наверное что-то мне пытаетесь доказать, но я не понимаю зачем. Хотите поспорить - напишите автору (автор в отличие от нас с вами написал свою первую книгу по питону 10+ лет назад).
Думаю, что автор имеет в виду проблемы с захватом переменных замыканиям в циклах в Python. В С++ можно выбрать разные режимы захвата, в Python приходится выкручиваться. Ну и надо делать ссылку на авторский стиль.
Попробуйте что-то на основе flamegraph, он вроде как должен существенно снижать размер профиля (но у меня все равно получались профили по несколько Гб с примерно таким же результатом как у вас). У того же остина довольно простой формат, котороый можно фильтровать перед анализом "глазками".
Если вы говорите про трейсинг, то возможно вам будет интересно посмотреть на pyroscope, сам его я не использовал (у нас немного другое направление), но выглядит он интересно.
Также большинство указанных в статье инструментов (в разделе Другие инструменты) умеют подцепляться к запущенным приложениям и строить либо FlameGraph, либо выгружать данные в формате Google Trace Event.
А по поводу ванильных примеров - я применял austin на реальном большом десктопном приложении, применял с пользой. С неванильными примерами есть другая проблема - ниже об этом писали - это получающиеся большие логи, которые надо как то предобрабатывать.
Выше уже ответили — различные оптимизации. Мне часто приходится решать задачи улучшения производительности в довольно большой кодовой базе, и там с ходу не понятно куда надо нанести «удар кувалдой».
Все так с .csv - у них хороший парсер для csv - отсюда и результат. Т.е. дело не только в том что они используют mmap, но и в том что остальной код лучше.
Вот у вас есть реальный опыт pandas/polars, судя по вашим комментариям он гораздо интереснее статьи.
Алексей, расскажите, пожалуйста, что вы имеете в виду, когда пишите "Архитектура базируется на NumPy — это ограничивает обработку больших массивов данных, помещающихся в память одного потока."
Rust позволяет эффективно управлять памятью и гарантирует отсутствие состояния гонки. - это просто общие слова, которые не имеют отношения к делу.
Если вы противопостовляете pandas + Dask и polars, надо говорить не про "Polars же написан на Rust — языке программирования, который славится своей безопасностью и высокой производительностью." - а про наличие streaming API из коробки (которое появилось не само по себе).
А вот чтобы было более честное сравнение - "Данные хранятся в колонках, так что чтение и обработка становятся более эффективными." - вы не пробовали те же тесты запустить в pandas если выбрано PyArrow в качестве сравнения? Сюда же и про mmap - оно ведь тоже не на равном месте появляется. Попробуйте так же работать с CSV или XLSX форматами :)
Что касается API, то у Polars свой собственный DataFrame API (иммутабельный, в отличие от Pandas), а еще есть поддержка SQL. У DuckDB — только SQL. А DuckDB то каким образом к pandas относится?
А в абзаце про слияние и join у вас написано про фильтрацию.
Понятно, что они захватывают объекты, которые эти значения хранят. Но вы ранее утверждали, что
захватываютсясохраняются только имена. Поэтому я вам и привел эту ссылку и предлагал посмотреть, что там в замыкании реально захватывается.Сами уж поищите, вы ловко виляете - понятно, что в контексте нашего разговора мы говорим не просто о функции, а о функции и захваченном контексте.
Простыня на IL для того чтобы вы посмотрели, что контекст захвачен, а Action-ном там не пахнет. Но опять же вас судя по всему интересуют теоретические витания, чем реальность данная нам в ощущениях.
Ну вообще то там написано, что замыкания захватывают значения свободных переменных. И этим я хочу опровергнуть ваше утверждение "Лексическое замыкание означает просто сохранение лексического контекста имён свободных переменных. Сохраняются имена, а не значения.". Вы могли бы и сами это узнать, если бы прочитали ссылки, которые я привел.
лямбда-выражение не функция. Вы могли бы с этим ознакомиться, если бы читали более внимательно и прочитали бы, например, последнюю ссылку (довольно объемную, но для вас там есть оглавление).
Вы неумело жонглируете словами, с этим я, и мой коллега по опасному бизнесу, и спорим.
Ниже IL (intermediate language) кода на C# из примера выше. Как видно ни
<>c__DisplayClass0_0
ни<>c__DisplayClass0_1
не расширяютAction
, но являются функциональными объектами.Впрочем, продолжать с вами обсуждение я смысла не вижу - вы либо не читаете ссылки, которые вам дают, либо понимаете их по своему - тут я уже ничего поделать не могу. Просвещайтесь.
Удивительно, что вы не хотите посмотреть как реальность соответствует теории и продолжаете заблуждаться. Не верите отладчику и моим словам - вот вам немного ссылок:
function.__closure__
codeobject.co_freevars
closure-variable
Python Scope & the LEGB Rule: Resolving Names in Your Code – Real Python
Как вы сказали выше "Охотно верю, но надо просвещать людей." - просвещайтесь пожалуйста.
По поводу вашего вопроса вам уже ответили - там глобальный скоп - там нет свободных переменных и они не захватываются -
__code__.co_names
иfunction.__globals__
вам в помощь.Вы неправильно понимаете. Почитать можно тут Lambda expressions - Lambda expressions and anonymous functions - C# reference | Microsoft Learn , тут Built-in reference types - C# reference | Microsoft Learn и тут https://www.codeproject.com/KB/cs/InsideAnonymousMethods.aspx
Фишка не в
List<Action>
, а в том что создается функциональный объект, со своей памятью. Впрочем так же происходит и в случае питона - там также создается функциональный объект со своей памятью. В моем примере можете добавить выводprint(id(l))
чтобы в этом убедиться. Либо сделатьdis
и также убедиться что вызываетсяMAKE_FUNCTION
.Если под отладчиком посмотрите содержимое лямбды (l), то там можете увидеть
__closure__
, который содержит ссылки на свободные (захваченные) переменные (в нашем случае только одну ссылкуidx_copy
), а в__code__.co_freevars
названия захваченных переменных. И если посмотреть наid
переменных, которые лежат в__closure__
, то можно заметить, что все они соответствуют последней копииidx_copy
.Я немного модифицировал пример - добавил две переменные в замыкание, чтобы было проще смотреть, что попадает в захваченные переменные.
А вот так можно сделать небольшую ошибку и сломать свою лямбду:
Причем здесь указатели на функции я простите не понял. Мы все таки вели разговор про замыкания.
Там оба захватываются - полно онлайн компиляторов - можно и посмотреть.
Вот вам два примера:
Выбрал C# потому что ранее вы говорили про сборку мусора.
Ну посчитайте сколько у вас лямбд внутри цикла.
Позднее связывания к этом относится напрямую.
Они работают ожидаемо, если люди понимают, что такое лексическое замыкание и позднее связывание. У нас на собесах 8 из 10 человек про это не знаю, а код такой писать иногда приходится (чуть сложнее, чем передать индекс в лямбду) и рецепты надо помнить. Ваше высказывание про логично/нелогично приводит меня к мысли, что вы с подобным не сталкивались. Люди вон считали это багами - Issue 13652: Creating lambda functions in a loop has unexpected results when resolving variables used as arguments - Python tracker .
В С++ аналогичной проблемы не будет если лямбда связывается по значениям, а не по ссылкам.
Вы наверное что-то мне пытаетесь доказать, но я не понимаю зачем. Хотите поспорить - напишите автору (автор в отличие от нас с вами написал свою первую книгу по питону 10+ лет назад).
python - What do lambda function closures capture? - Stack Overflow
Значит про проблемы вы немного не в курсе.
Думаю, что автор имеет в виду проблемы с захватом переменных замыканиям в циклах в Python. В С++ можно выбрать разные режимы захвата, в Python приходится выкручиваться. Ну и надо делать ссылку на авторский стиль.
Наверное забыли листинг
tp_dealloc
.Я настраивал как здесь описано - Obsidian+Github вместо Notion: синхронизация, бекап и версионность (3-в-1) / Хабр, но в итоге на ios заменил fit на git (писал у себя в постах если интересно). Дополнительные приложения открывать не надо - obsidian и плагин git все делают сами.
Попробуйте что-то на основе flamegraph, он вроде как должен существенно снижать размер профиля (но у меня все равно получались профили по несколько Гб с примерно таким же результатом как у вас). У того же остина довольно простой формат, котороый можно фильтровать перед анализом "глазками".
Если вы говорите про трейсинг, то возможно вам будет интересно посмотреть на pyroscope, сам его я не использовал (у нас немного другое направление), но выглядит он интересно.
Также большинство указанных в статье инструментов (в разделе Другие инструменты) умеют подцепляться к запущенным приложениям и строить либо FlameGraph, либо выгружать данные в формате Google Trace Event.
А по поводу ванильных примеров - я применял austin на реальном большом десктопном приложении, применял с пользой. С неванильными примерами есть другая проблема - ниже об этом писали - это получающиеся большие логи, которые надо как то предобрабатывать.
Для питона есть, в статье есть несколько ссылок, которые точно умеют - это austin, py-spy, pyinstrument.
Выше уже ответили — различные оптимизации. Мне часто приходится решать задачи улучшения производительности в довольно большой кодовой базе, и там с ходу не понятно куда надо нанести «удар кувалдой».
Все так с .csv - у них хороший парсер для csv - отсюда и результат. Т.е. дело не только в том что они используют mmap, но и в том что остальной код лучше.
Вот у вас есть реальный опыт pandas/polars, судя по вашим комментариям он гораздо интереснее статьи.
Алексей, расскажите, пожалуйста, что вы имеете в виду, когда пишите "Архитектура базируется на NumPy — это ограничивает обработку больших массивов данных, помещающихся в память одного потока."
Rust позволяет эффективно управлять памятью и гарантирует отсутствие состояния гонки. - это просто общие слова, которые не имеют отношения к делу.
Если вы противопостовляете pandas + Dask и polars, надо говорить не про "Polars же написан на Rust — языке программирования, который славится своей безопасностью и высокой производительностью." - а про наличие streaming API из коробки (которое появилось не само по себе).
А вот чтобы было более честное сравнение - "Данные хранятся в колонках, так что чтение и обработка становятся более эффективными." - вы не пробовали те же тесты запустить в pandas если выбрано PyArrow в качестве сравнения? Сюда же и про mmap - оно ведь тоже не на равном месте появляется. Попробуйте так же работать с CSV или XLSX форматами :)
Что касается API, то у Polars свой собственный DataFrame API (иммутабельный, в отличие от Pandas), а еще есть поддержка SQL. У DuckDB — только SQL. А DuckDB то каким образом к pandas относится?
А в абзаце про слияние и join у вас написано про фильтрацию.
В целом вы могли бы и лучше, но явно халтурите.
В этом году был доклад на эту тему - B-tree Optimisation for Numerical In-memory Indexes | Talk at С++ Russia 2024 (слайды cpp-russia-03-06.pdf)