Я имею в виду, что PyPl является первоисточником неотъемлемых частей реальной питоновской программы, выполняя такую же функцию, как системная библиотека C++.
Нет. "Такую же функцию, как системная библиотека C++" в питончике выполняет Python Standard Library (кстати, с кучей платформозависимых оговорок, см. раздел Notes on availability). А PyPl - это пакетный менеджер, его аналогами в C++ являются vcpkg, conan и тому подобное.
А вот и нет. Открываем TI-евский "Optimizing C/C++ Compiler User’s Guide" и читаем:
The compiler supports associative versions for some of the addition and multiply-and-accumulate intrinsics. These associative intrinsics are prefixed with “_a_”. The compiler is able to reorder arithmetic computations involving associative intrinsics, which may produce more efficient code.
[...]
However, this reordering may affect the value of the expression if saturation occurs at different points in the new ordering. [...] A rule of thumb is that if all your data have the same sign, you may safely use associative intrinsics.
То есть с одной стороны код будет лучше оптимизироваться компилятором, а с другой погромист должен сам думать, с какими данными эти ассоциативные интринсики можно применять, а с какими нет.
Ну скажем так на cppreference этот момент формулируется следующим образом:
When signed integer arithmetic operation overflows (the result does not fit in the result type), the behavior is undefined, — the possible manifestations of such an operation include:
it wraps around according to the rules of the representation (typically two's complement),
it traps — on some platforms or due to compiler options (e.g. -ftrapv in GCC and Clang),
it saturates to minimal or maximal value (on many DSPs),
it is completely optimized out by the compiler.
то есть, вообще говоря, знаковое переполнение (даже если оно завершается сатурацией) - это UB, а компилятор, как известно, имеет право выполнять любые оптимизации в расчете на то, что UB не происходит. К слову, у TI-евских DSP есть ассоциативные интринсики (например, _a_sadd - ассоциативное сложение с насыщением), так вот, их тоже нужно применять с осторожностью, т.к. компилятор их будет переупорядочивать при необходимости.
Да посмотрите на самые базовые возможности SDL в конце концов.
Ага, а еще посмотрите как эту SDL перехреначивают каждый мажорный релиз. Вот и в SDL3 по сравнению с SDL2 все перехреначили до неузнаваемости, там и ABI и API весь сломан - все попереименовали, типы поменяли (где раньше стол был яств теперь там гроб стоит был int, теперь bool)... И я уверен что еще будут фиксить всякие баги, которых там все еще миллион, со сломом API/ABI, а юзер будет вынужден лепить свои #if SDL_VERSION_ATLEAST( ... ) чтобы лавировать между всем этим с разной степенью успешности. В std такому точно не место. Идея запихать в std все на свете заведомо мертворожденная, никто этим заниматься не будет.
На любой платформе, использующей для представления целых чисел дополнительный код (а сколько-нибудь современные платформы все такие), самое большое положительное число переходит в самое большое отрицательное, и наоборот
Вполне себе современные DSP смотрят на вас с недоумением. Кстати, арифметика с насыщением весьма уязвима к потенциальному переупорядочиванию операций компилятором. Например, компилятор имеет право изменять порядок операций типа a + b + c, и разные компиляторы действительно выполняют это сложение в разном порядке. Вы, думаю, в курсе, но для остальных напомню, что оба компилятора используют стандартную для x86-64 calling convention, где для передачи параметров типа INTEGER используются регистры rdi, rsi, rdx и rcx по порядку, т.е. gcc производит сложение как ((a + b) + c) + d), а clang - как (a + b) + (c + d). Так вот, предположим в нашем гипотетическом примере с a + b + c, что a == INT_MAX, b == INT_MAX, а c == INT_MIN. Тогда, если сложение будет производиться компилятором в порядке (a + b) + c, то результатом будет -1, а если оно будет производиться в порядке a + (b + c), то результатом будет INT_MAX - 1.
Там много чего пишут - еще про Трампа, курдов, токсичную маскулинность и так далее. Написано явно не совсем здоровым человеком, но увлекательно, спору нет - когда в свое время читал, просто-таки не мог оторваться от совершенно неожиданных поворотов сюжета.
Yeah. It's an ironic thing...
unsigned long total = nr * size;
if (nr > ULONG_MAX / size)
return -EINVAL;
In an ideal world, people who write code like that should receive a
permanent ban from promoting Rust.
Как же он неразрывно связан, если, по вашим собственным наблюдениям, к лямбде никаких обращений нет, а контекст меняется?
А какие должны быть "обращения к лямбде" в C коде, вызов функции что ли? А зачем? А вот обновление её контекста по ходу его изменения, разумеется, производится. Я не просто так акцентировал внимание на том, что генерируемый код - не плюсовый, и понятие "класс" там отсутствует. Производится просто эмуляция "лямбда-объекта" с использованием негустого набора доступных в C средств, вот и все. Забавно, что приходится разжевывать такие элементарные вещи.
Но этот контекст не является частью лямбды, как конструкции языка.
Просто конкретно в C нет средств сделать контекст "частью лямбды" в вашем представлении - с помощью конструкций языка. Но тем не менее даже там у лямбда-объекта есть своя "память" в виде структурки с набором захваченных внешних по отношению к нему объектов. А как это выглядит со стороны питона - вам уже написали ниже, как и куда смотреть. Засим откланиваюсь, удачи в дальнейшем образовании, всего хорошего.
Надеюсь, вы видите, что по ходу выполнения h() нет никаких обращений к лямбде в исходном тексте?
Надеюсь, вы видите, что Cython не генерирует плюсовый код? Ах да, не видите, вы же рассчитываете, что другие вам все расскажут и покажут, а вы будете только задавать глупые вопросы с умным видом. Так вот, Cython генерирует код на C. Там нет классов. Лямбда - это обычная функция, которая принимает параметр __pyx_self, который является грубым аналогом плюсового this (который так же первым параметром неявно принимают в плюсах нестатические методы классов). Через него и можно добраться до контекста/замыкания, как я показал выше, контекст неразрывно связан с конкретным экземпляром "лямбда-объекта", на который собственно и указывает этот __pyx_self.
А то, что у среды исполнения есть свои собственные структуры для хранения данных о состоянии программы - бесспорно.
Как говорится, хоть стрижено, хоть брито - все голо.
Вам уже ниже пытаются объяснить, что и в интерпретаторе "так" не работает. Но вы не хотите посмотреть в отладчик, а продолжаете витать в своих странных схоластических фантазиях.
Поймите простую вещь: лямбда не знает, в каком контексте она будет применяться, и какое там будет лексическое окружение. Поэтому она должна сохранять весь используемый ею лексический контекст.
Если внутри лямбды используются только глобальные переменные, то заниматься сохранением контекста нет смысла - глобальные переменные всегда доступны, они не уничтожатся, если вручную не сделать им del. Здесь действительно достаточно только имен переменных, сами соответствующие им объекты сохранять в контекст не нужно. Локальные объекты внутри какой-то функции - дело другое (см. код ниже).
или, наоборот, так:
Последний раз показываю. В дальнейшем, пожалуйста, приложите усилия к самостоятельному изучению вопроса. Мне уже надоело заниматься вашим образованием. Вот структура для захвата:
Обещанную вами кодогенерацию с кучей констант, вшитых прям в питоновый код же, с JIT'ом каким-нибудь. Вы же говорили, все так работает:
Список 0..9, а в чём подвох? Мы здесь создаём при помощи функции bar 10 разных лямбд, печатающих константы:
lambda : print (0)
lambda: print (1)
...
lambda: print (10)
которые помещаем в элементы списка lamdas при помощи функции foo.
Попробуйте объяснить работу этой программы (не уверен, что она может быть успешно оттранслирована цитоном):
Конечно, может, и успешно им транслируется.
Что именно, по-вашему, здесь захватывается в полях лямбда-объекта и как?
Простите, а зачем тут вообще что-то захватывать? Здесь же все символы в global scope, захватывать здесь ничего не нужно. Все находится через __Pyx_GetModuleGlobalName() и сразу выполняется по месту. Посидите самостоятельно, поупражняйтесь, посмотрите, как все работает в реальности, в каких случаях нужен захват (и он делается), а в каких - нет.
"Если факты не соответствуют теории, то тем хуже для фактов"? :)
2) это фактически неверно. Функциональные значения (в том числе тела именованных фукций и лямбда-выражения) представляет в CPython так называемый code object, который не является объектом в смысле ООП. Хотя у него есть контейнер в виде класса;
Прогоните как-нибудь текст первого моего примера через Cython (генерируемый им код совместим с CPython - по крайней мере, они сами так декларируют: "Cython provides [...] full runtime compatibility with all still-in-use and future versions of CPython") и посмотрите, что там внутри. Разумеется, там нет никакой кодогенерации на лету с кучей констант. Зато там есть примерно такое:
Разумеется, никаких констант нет, классический механизм захвата объекта по ссылке через создание структуры, поля которой затем используются для вызова внешних функций внутри лямбды.
А вам не приходит в голову более простое объяснение, а именно - никакой отдельный код с константами на каждый случай не генерируется, а вместо этого генерируется "лямбда-класс" с полями-ссылками на захваченные "внешние" объекты (ну, или в случае простейших типов - просто значениями этих объектов), и с методом, код которого генерируется один раз, и который просто работает с соответствующими полями "своего" экземпляра лямбда-класса (как выше написали - "лямбда-объекта"), м?
А в первом случае лямбды содержали числа непосредственно, потому что в питоне так устроена передача параметров разных типов.
Бгг, но ведь лямбда не принимает никаких параметров. Все, что она использует в обеих случаях - это одно целое число. Так почему же в первом случае якобы генерируется куча лямбд с константами (типа прямо в коде эти константы якобы прописаны по вашему мнению), а во втором случае - куча лямбд со ссылками? У вас шаблон еще не треснул?
Какой-то набор слов. В предыдущем случае была якобы (по вашим словам) нагенерирована куча лямбд, печатающих константы 0, 1, и так далее, почему же эта куча лямбд не была нагенерирована в этом случае?
Мы здесь создаём при помощи функции bar 10 разных лямбд, печатающих константы:
[...]
Ни одна из этих лямбд не имеет ни собственных переменных, ни обращается к чужим на этапе применения.
Здорово вас плющит. Теперь с этой точки зрения объясните, пожалуйста, результат выполнения такого кода:
class Data:
i = 0
def __init__(self, i):
self.i = i
def bar(d):
data.append(d)
return lambda : print(d.i)
def foo():
result = []
for idx in range(10):
result.append(bar(Data(idx)))
return result
data = []
lambdas = foo()
for l in lambdas:
l()
print("---")
for d in data:
d.i = d.i + 1
for l in lambdas:
l()
Я не знаток C#, но здесь у вас разница не во времени связывания, а в том, что в первом случае у вас используется обычная функциональная лямбда, т.е. просто записанное безымянное тело функции, а во втором – в цикле создаётся новый объект, содержащий функцию. Имеющий в том числе и свою собственную память.
У вас опять какие-то своеобразные представления о происходящем. В питончике у лямбд тоже вполне себе есть, выражаясь вашим языком, "своя собственная память". Например, что, по-вашему, выведет вот такой код и почему?
def bar(i):
return lambda : print(i)
def foo():
result = []
for idx in range(10):
result.append(bar(idx))
return result
lambdas = foo()
for l in lambdas:
l()
Нет. "Такую же функцию, как системная библиотека C++" в питончике выполняет Python Standard Library (кстати, с кучей платформозависимых оговорок, см. раздел Notes on availability). А PyPl - это пакетный менеджер, его аналогами в C++ являются vcpkg, conan и тому подобное.
А вот и нет. Открываем TI-евский "Optimizing C/C++ Compiler User’s Guide" и читаем:
То есть с одной стороны код будет лучше оптимизироваться компилятором, а с другой погромист должен сам думать, с какими данными эти ассоциативные интринсики можно применять, а с какими нет.
Ну скажем так на cppreference этот момент формулируется следующим образом:
то есть, вообще говоря, знаковое переполнение (даже если оно завершается сатурацией) - это UB, а компилятор, как известно, имеет право выполнять любые оптимизации в расчете на то, что UB не происходит. К слову, у TI-евских DSP есть ассоциативные интринсики (например,
_a_sadd- ассоциативное сложение с насыщением), так вот, их тоже нужно применять с осторожностью, т.к. компилятор их будет переупорядочивать при необходимости.Ну это вы так думаете. Я бы настолько категорично на эту тему не высказывался, но я - это я, а вы - это вы.
Ага, а еще посмотрите как эту SDL перехреначивают каждый мажорный релиз. Вот и в SDL3 по сравнению с SDL2 все перехреначили до неузнаваемости, там и ABI и API весь сломан - все попереименовали, типы поменяли (где раньше
стол был яств теперь там гроб стоитбылint, теперьbool)... И я уверен что еще будут фиксить всякие баги, которых там все еще миллион, со сломом API/ABI, а юзер будет вынужден лепить свои#if SDL_VERSION_ATLEAST( ... )чтобы лавировать между всем этим с разной степенью успешности. В std такому точно не место. Идея запихать в std все на свете заведомо мертворожденная, никто этим заниматься не будет.Вполне себе современные DSP смотрят на вас с недоумением. Кстати, арифметика с насыщением весьма уязвима к потенциальному переупорядочиванию операций компилятором. Например, компилятор имеет право изменять порядок операций типа
a + b + c, и разные компиляторы действительно выполняют это сложение в разном порядке. Вы, думаю, в курсе, но для остальных напомню, что оба компилятора используют стандартную для x86-64 calling convention, где для передачи параметров типаINTEGERиспользуются регистрыrdi,rsi,rdxиrcxпо порядку, т.е. gcc производит сложение как((a + b) + c) + d), а clang - как(a + b) + (c + d). Так вот, предположим в нашем гипотетическом примере сa + b + c, чтоa == INT_MAX,b == INT_MAX, аc == INT_MIN. Тогда, если сложение будет производиться компилятором в порядке(a + b) + c, то результатом будет-1, а если оно будет производиться в порядкеa + (b + c), то результатом будетINT_MAX - 1.Там много чего пишут - еще про Трампа, курдов, токсичную маскулинность и так далее. Написано явно не совсем здоровым человеком, но увлекательно, спору нет - когда в свое время читал, просто-таки не мог оторваться от совершенно неожиданных поворотов сюжета.
Бгг.
А какие должны быть "обращения к лямбде" в C коде, вызов функции что ли? А зачем? А вот обновление её контекста по ходу его изменения, разумеется, производится. Я не просто так акцентировал внимание на том, что генерируемый код - не плюсовый, и понятие "класс" там отсутствует. Производится просто эмуляция "лямбда-объекта" с использованием негустого набора доступных в C средств, вот и все. Забавно, что приходится разжевывать такие элементарные вещи.
Просто конкретно в C нет средств сделать контекст "частью лямбды" в вашем представлении - с помощью конструкций языка. Но тем не менее даже там у лямбда-объекта есть своя "память" в виде структурки с набором захваченных внешних по отношению к нему объектов. А как это выглядит со стороны питона - вам уже написали ниже, как и куда смотреть. Засим откланиваюсь, удачи в дальнейшем образовании, всего хорошего.
Надеюсь, вы видите, что Cython не генерирует плюсовый код? Ах да, не видите, вы же рассчитываете, что другие вам все расскажут и покажут, а вы будете только задавать глупые вопросы с умным видом. Так вот, Cython генерирует код на C. Там нет классов. Лямбда - это обычная функция, которая принимает параметр
__pyx_self, который является грубым аналогом плюсовогоthis(который так же первым параметром неявно принимают в плюсах нестатические методы классов). Через него и можно добраться до контекста/замыкания, как я показал выше, контекст неразрывно связан с конкретным экземпляром "лямбда-объекта", на который собственно и указывает этот__pyx_self.Как говорится, хоть стрижено, хоть брито - все голо.
А иначе же "теории не соответствует", ага?
Вам уже ниже пытаются объяснить, что и в интерпретаторе "так" не работает. Но вы не хотите посмотреть в отладчик, а продолжаете витать в своих странных схоластических фантазиях.
Если внутри лямбды используются только глобальные переменные, то заниматься сохранением контекста нет смысла - глобальные переменные всегда доступны, они не уничтожатся, если вручную не сделать им
del. Здесь действительно достаточно только имен переменных, сами соответствующие им объекты сохранять в контекст не нужно. Локальные объекты внутри какой-то функции - дело другое (см. код ниже).Последний раз показываю. В дальнейшем, пожалуйста, приложите усилия к самостоятельному изучению вопроса. Мне уже надоело заниматься вашим образованием. Вот структура для захвата:
Знакомо, да? Вот ее использование в коде лямбды:
Это поле в ходе выполнения
h()переприсваивается несколько раз, последний раз это выглядит так:Обещанную вами кодогенерацию с кучей констант, вшитых прям в питоновый код же, с JIT'ом каким-нибудь. Вы же говорили, все так работает:
Конечно, может, и успешно им транслируется.
Простите, а зачем тут вообще что-то захватывать? Здесь же все символы в global scope, захватывать здесь ничего не нужно. Все находится через
__Pyx_GetModuleGlobalName()и сразу выполняется по месту. Посидите самостоятельно, поупражняйтесь, посмотрите, как все работает в реальности, в каких случаях нужен захват (и он делается), а в каких - нет.RESTinio не пробовали? Или она не то, что вам нужно?
"Если факты не соответствуют теории, то тем хуже для фактов"? :)
Прогоните как-нибудь текст первого моего примера через Cython (генерируемый им код совместим с CPython - по крайней мере, они сами так декларируют: "Cython provides [...] full runtime compatibility with all still-in-use and future versions of CPython") и посмотрите, что там внутри. Разумеется, там нет никакой кодогенерации на лету с кучей констант. Зато там есть примерно такое:
Экземпляр этой структуры создается и заполняется при создании и возврате лямбды, а
print(i)вызывается из кода лямбды примерно таким образом:Разумеется, никаких констант нет, классический механизм захвата объекта по ссылке через создание структуры, поля которой затем используются для вызова внешних функций внутри лямбды.
А вам не приходит в голову более простое объяснение, а именно - никакой отдельный код с константами на каждый случай не генерируется, а вместо этого генерируется "лямбда-класс" с полями-ссылками на захваченные "внешние" объекты (ну, или в случае простейших типов - просто значениями этих объектов), и с методом, код которого генерируется один раз, и который просто работает с соответствующими полями "своего" экземпляра лямбда-класса (как выше написали - "лямбда-объекта"), м?
Бгг, но ведь лямбда не принимает никаких параметров. Все, что она использует в обеих случаях - это одно целое число. Так почему же в первом случае якобы генерируется куча лямбд с константами (типа прямо в коде эти константы якобы прописаны по вашему мнению), а во втором случае - куча лямбд со ссылками? У вас шаблон еще не треснул?
Какой-то набор слов. В предыдущем случае была якобы (по вашим словам) нагенерирована куча лямбд, печатающих константы 0, 1, и так далее, почему же эта куча лямбд не была нагенерирована в этом случае?
Здорово вас плющит. Теперь с этой точки зрения объясните, пожалуйста, результат выполнения такого кода:
У вас опять какие-то своеобразные представления о происходящем. В питончике у лямбд тоже вполне себе есть, выражаясь вашим языком, "своя собственная память". Например, что, по-вашему, выведет вот такой код и почему?
Это-то как раз очевидно: потому что на каждой итерации создаётся отдельный экземпляр
idx_copy, а вот экземплярidxодин на все итерации.