Pull to refresh
13
0.2

Пользователь

Send message

Я имею в виду, что 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.

А то, что у среды исполнения есть свои собственные структуры для хранения данных о состоянии программы - бесспорно.

Как говорится, хоть стрижено, хоть брито - все голо.

В коде на C++, ага?

А иначе же "теории не соответствует", ага?

В интерпретаторе так и работает.

Вам уже ниже пытаются объяснить, что и в интерпретаторе "так" не работает. Но вы не хотите посмотреть в отладчик, а продолжаете витать в своих странных схоластических фантазиях.

Поймите простую вещь: лямбда не знает, в каком контексте она будет применяться, и какое там будет лексическое окружение. Поэтому она должна сохранять весь используемый ею лексический контекст.

Если внутри лямбды используются только глобальные переменные, то заниматься сохранением контекста нет смысла - глобальные переменные всегда доступны, они не уничтожатся, если вручную не сделать им del. Здесь действительно достаточно только имен переменных, сами соответствующие им объекты сохранять в контекст не нужно. Локальные объекты внутри какой-то функции - дело другое (см. код ниже).

или, наоборот, так:

Последний раз показываю. В дальнейшем, пожалуйста, приложите усилия к самостоятельному изучению вопроса. Мне уже надоело заниматься вашим образованием. Вот структура для захвата:

struct __pyx_obj_4test___pyx_scope_struct__h {
  PyObject_HEAD
  PyObject *__pyx_v_d;
};

Знакомо, да? Вот ее использование в коде лямбды:

__pyx_outer_scope = (struct __pyx_obj_4test___pyx_scope_struct__h *) __Pyx_CyFunction_GetClosure(__pyx_self);
__pyx_cur_scope = __pyx_outer_scope;
__pyx_t_1 = __Pyx_PyObject_CallNoArg(__pyx_cur_scope->__pyx_v_d);

Это поле в ходе выполнения h() переприсваивается несколько раз, последний раз это выглядит так:

  __pyx_t_1 = __Pyx_Py3ClassCreate(((PyObject*)&PyType_Type), __pyx_n_s_d, __pyx_empty_tuple, __pyx_t_2, NULL, 0, 0); if (unlikely(!__pyx_t_1)) __PYX_ERR(0, 10, __pyx_L1_error)
  __Pyx_GOTREF(__pyx_t_1);
  __Pyx_GOTREF(__pyx_cur_scope->__pyx_v_d);
  __Pyx_DECREF_SET(__pyx_cur_scope->__pyx_v_d, __pyx_t_1);
  __Pyx_GIVEREF(__pyx_t_1);
  __pyx_t_1 = 0;

Что вы хотели увидеть-то там?

Обещанную вами кодогенерацию с кучей констант, вшитых прям в питоновый код же, с JIT'ом каким-нибудь. Вы же говорили, все так работает:

Список 0..9, а в чём подвох? Мы здесь создаём при помощи функции bar 10 разных лямбд, печатающих константы:

lambda : print (0)

lambda: print (1)

...

lambda: print (10)

которые помещаем в элементы списка lamdas при помощи функции foo.

Попробуйте объяснить работу этой программы (не уверен, что она может быть успешно оттранслирована цитоном):

Конечно, может, и успешно им транслируется.

Что именно, по-вашему, здесь захватывается в полях лямбда-объекта и как?

Простите, а зачем тут вообще что-то захватывать? Здесь же все символы в global scope, захватывать здесь ничего не нужно. Все находится через __Pyx_GetModuleGlobalName() и сразу выполняется по месту. Посидите самостоятельно, поупражняйтесь, посмотрите, как все работает в реальности, в каких случаях нужен захват (и он делается), а в каких - нет.

Например, не существует нормальной и удобной библиотеки для написания REST сервисов.

RESTinio не пробовали? Или она не то, что вам нужно?

1) это не соответствует теории;

"Если факты не соответствуют теории, то тем хуже для фактов"? :)

2) это фактически неверно. Функциональные значения (в том числе тела именованных фукций и лямбда-выражения) представляет в CPython так называемый code object, который не является объектом в смысле ООП. Хотя у него есть контейнер в виде класса;

Прогоните как-нибудь текст первого моего примера через Cython (генерируемый им код совместим с CPython - по крайней мере, они сами так декларируют: "Cython provides [...] full runtime compatibility with all still-in-use and future versions of CPython") и посмотрите, что там внутри. Разумеется, там нет никакой кодогенерации на лету с кучей констант. Зато там есть примерно такое:

struct __pyx_obj_4test___pyx_scope_struct__bar {
  PyObject_HEAD
  PyObject *__pyx_v_i;
};

Экземпляр этой структуры создается и заполняется при создании и возврате лямбды, а print(i) вызывается из кода лямбды примерно таким образом:

__pyx_t_1 = __Pyx_PyObject_CallOneArg(__pyx_builtin_print, __pyx_cur_scope->__pyx_v_i);

Разумеется, никаких констант нет, классический механизм захвата объекта по ссылке через создание структуры, поля которой затем используются для вызова внешних функций внутри лямбды.

А вам не приходит в голову более простое объяснение, а именно - никакой отдельный код с константами на каждый случай не генерируется, а вместо этого генерируется "лямбда-класс" с полями-ссылками на захваченные "внешние" объекты (ну, или в случае простейших типов - просто значениями этих объектов), и с методом, код которого генерируется один раз, и который просто работает с соответствующими полями "своего" экземпляра лямбда-класса (как выше написали - "лямбда-объекта"), м?

А в первом случае лямбды содержали числа непосредственно, потому что в питоне так устроена передача параметров разных типов.

Бгг, но ведь лямбда не принимает никаких параметров. Все, что она использует в обеих случаях - это одно целое число. Так почему же в первом случае якобы генерируется куча лямбд с константами (типа прямо в коде эти константы якобы прописаны по вашему мнению), а во втором случае - куча лямбд со ссылками? У вас шаблон еще не треснул?

Какой-то набор слов. В предыдущем случае была якобы (по вашим словам) нагенерирована куча лямбд, печатающих константы 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()

Хотя непредвзятому человеку невозможно понять без поллитры, почему idx_copy должно захватываться, а idx нет.

Это-то как раз очевидно: потому что на каждой итерации создаётся отдельный экземпляр idx_copy, а вот экземпляр idx один на все итерации.

Information

Rating
3,062-nd
Registered
Activity