Страницы

Поиск по вопросам

Показаны сообщения с ярлыком python-internals. Показать все сообщения
Показаны сообщения с ярлыком python-internals. Показать все сообщения

среда, 29 января 2020 г.

Магические методы

#python #ооп #python_internals


Как реализована архитектура магических методов в python?
Например:

class SomeClass:
    def __init__(self, x):
        self.x = x

    def __repr__(self):
        return "({})".format(self.x)

a = SomeClass(23)
print(repr(a))


Т.е. как происходит определние вызова нужного метода нужного класса? (repr класса
SomeClass)?
    


Ответы

Ответ 1



Немного дополню предыдущие ответы. Согласно официальной документации, вот тут, специальные методы (именно так называются магические методы в документации) это подход python к перегрузке операторов, позволяющий классам определять свое поведение в отношении операторов языка. Из этого можно заключить, что интерпретатор имеет "некую таблицу" (назовём это так) соответствия операторов к методам класса. Перегружая эти методы, вы можете управлять "поведением" операторов языка относительно вашего класса. Также, если в некотором классе сделать любой специальный (магический) метод None, то это будет означать, что данная операция для некоторого класса не доступна, а соответствующий оператор, применённый к этому классу, вернет TypeError. Сигнатуру функции repr для СPython, вы можете посмотреть только в исходниках на С. В модуле builtins.py она выглядит следующим образом def repr(obj): # real signature unknown; restored from __doc__ """ Return the canonical string representation of the object. For many object types, including most builtins, eval(repr(obj)) == obj. """ pass Но думаю тут и так понятно. Создавая свой класс без наследников, вы неявно наследуете класс object со всеми его специальными методами включая и repr. В модуле builtins.py это выглядит так: class object: """ The most base type """ .... # ещё порядка 20 магических методов def __repr__(self, *args, **kwargs): # real signature unknown """ Return repr(self). """ pass Таким образом можно сделать, ещё один вывод, что даже "пустой" пользовательский класс, готов для применения, в том или ином виде, операторами (функциями) языка. И ещё немного о соответствии тут

Ответ 2



Магический метод — это просто метод, который вызывается неявно, часто чтобы выполнить операцию для типа. К примеру, можно определить __bool__ метод, чтобы указать является ли объект True/False в булевом контексте (если поведение по умолчанию, основанное на __len__ или True, не подходит). Очень многие аспекты поведения объектов в Питоне могут быть изменены таким образом. К примеру, plumbum модуль, за счёт перегрузки операторов, встраивает sh-подобный язык прямо в Питон: #!/usr/bin/env python from plumbum.cmd import awk, sort (awk["-f", "script.awk"] << "input data" | sort > "outfile.txt")() Как реализована архитектура магических методов в python? Как происходит определение вызова нужного метода нужного класса? repr(SomeClass()) приводит к вызову SomeClass.__repr__ (если определён), потому что документация repr() функции так говорит. В целом, ничего особенно не происходит, к примеру, вы можете свой протокол определить: def quack(obj): return getattr(obj, 'quack', None)() Любой объект, для которого определён метод quack(), независимо от его базовых классов, можно передать в функцию quack(): class Duck: def quack(self): return "Quack! Quack! Quack!" print(quack(Duck())) Для остальных классов TypeError выбрасывается: try: quack(1) except TypeError: print("целое число не квакает") Отличие quack() от настоящих магических методов, что не используется двойное подчёркивание в имени метода, по соглашению, зарезервированное для протоколов, определяемых самим языком и нет требования определить метод в самом классе (определения из Мета-класса или присваивание метода объекту напрямую могут не работать для магических методов): >>> class C: ... pass >>> c = C() >>> c.__len__ = lambda: 0 >>> len(c) Traceback (most recent call last): File "", line 1, in TypeError: object of type 'C' has no len() Для сравнения, quack() можно прямо самому объекту присвоить: >>> c.quack = lambda: "ga ga" >>> quack(c) 'ga ga' Но чтобы len() работала, необходимо определение в сам класс помещать: >>> class C: ... def __len__(self): ... return 0 >>> len(C()) 0 Это сделано для оптимизации скорости вызова подобных методов и чтобы избежать "metaclass confusion", описанную выше по ссылке. В CPython, почти все специальные методы (термин для магических методов из спецификации языка) хранятся в специальных слотах в типе.

Ответ 3



посмотрите на этот вопрос так def __repr__(self): return "({x})".format(x=self.x) class SomeClass:pass a = SomeClass() a.x = 23 print(__repr__(a))

четверг, 2 января 2020 г.

Размер списка в Python и объём оперативной памяти

#python #python_internals


При исполнении следующего кода    

n = 10 ** 9
alist = [0] * n


Компьютер начинает работать очень медленно (похоже из-за нехватки оперативной памяти?).
Если я правильно понял, в этом случае список создаётся и вмещается в память, но занимает
такое её количество, что всё начинает "тормозить". При этом, например, если размер
списка равен 10 ** 10, то сразу выдаётся MemoryError. Получается, что пайтон не может
вместить список такого размера в оперативную память (её физически не хватает) или стоят
какие-то ограничения на размер списка?  

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

Обновление

Списки такого размера мне понадобились при нахождении всех простых чисел меньше n
с помощью решета Эратосфена. Я начал с n = 100 и постепенно увеличивал размер списка.
Вплоть до  10 ** 8 всё работало, а потом начались неприятные тормоза. Соответственно,
хотел узнать как можно защититься (от себя) в таких случаях. Может есть какие-нибудь
рекомендации по максимальному размеру списка в процентах от объёма оперативной памяти,
чтобы не возникало таких зависаний?
    


Ответы

Ответ 1



[0] * n список в CPython требует примерно n * <размер указателя> байтов (можно посмотреть на реализации функций list_repeat(), PyList_New(n), PyMem_Malloc(), чтобы увидеть случаи когда MemoryError сразу выбрасывается без попытки выделения памяти -- на 32-битной платформе эти пределы достаточно низкие: n = 536870911 (((2**32 - 1) >> 1) // 4), на 64-битной платформе маловероятно что эти условия сработают -- другими словами: вы можете создавать списки настолько большими насколько у вас памяти хватит -- практически ограничений нет. Защита простая: узнайте сколько у вас на компьютере памяти и не пытайтесь выделить больше этого кол-ва. По конкретной задаче: можно уменьшить потребление памяти в 4-8 раз, если использовать array, numpy.array вместо списка. Можно ещё снизить, если предварительно исключить маленькие простые числа (wheel-optimizations), cм. Fastest way to list all primes below N. Можно ещё gmpy2 использовать (до 16 раз меньше памяти), см. Speed up bitstring/bit operations in Python? Для n более 10**9 может иметь смысл использовать решето Аткина, которое требует N1/2+o(1) бит памяти, вместо решета Эратосфена O(N1/2(log log N)/log N)). Существуют алгоритмы без верхней границы, если не известно заранее сколько простых чисел надо. Если нужны бо́ льшие числа, то можно использовать алгоритмы, которые работают посегментно (позволяют как верхную так и нижнюю границу указать), например, pyprimesieve.

Ответ 2



для списка символов более точно размер списка указан в вопросе если коротко, то зависит еще и от разрядности машины. Более того список требует на свое "поддержание" на каждые элемент больше памяти, чем сами хранимые данные. Для каждого элемента от 40 до 79 байта плюс к длине элемента. Так что лучше использовать массивы array или numpy.

Ответ 3



Можно попробовать использовать другой алгоритма поиска простых чисел. К примеру http://python-dao.rpisarev.org.ua/2015/08/finding-prime-numbers-in-given-interval.html

пятница, 27 декабря 2019 г.

В чем разница между двумя циклами for: при удалении элементов во время обхода списка

#python #циклы #list #список #python_internals


Почему интерпретатор в первом случае убирает только 3 нуля ['1', '0', '0', '0'],
а во втором удаляет полностью, в чем разница? For - работает с каждым итерируемым объектом
по очереди, почему он пропускает 3 нуля?

list1 = ['1', '0', '0', '0', '0', '0', '0', '0', '0', '1']
for _ in list1:
    list1.remove('0') 
print(list1)

nuly = ['3', '2', '0', '0', '3', '5', '0', '0', '0', '8', '0'] 
for _ in nuly:
    nuly.remove('0')
print(nuly)

    


Ответы

Ответ 1



Оба цикла одинаковые. Только входные списки разные. Вы удаляете элементы из списка, одновременно обходя его. При обходе списка, цикл for не копирует его, а создаёт итератор, который элементы из списка по одному возвращает. Поэтому когда вы удаляете элемент (.remove('0') ищет один '0' в списке и удаляет его), то список изменяется и эти изменения видны в итераторе. В текущей реализации СPython, итератор списка хранит ссылку на сам список и текущий индекс в нём. Элементы возвращаются пока длина списка больше текущего индекса. Вот суть next(list_iterator) вызова, возвращающего следующий элемент на каждой итерации: listiter_next(listiterobject *it) { ... if (it->it_index < PyList_GET_SIZE(it->it_seq)) { item = PyList_GET_ITEM(seq, it->it_index); ++it->it_index; return item; } ... } Что на Питоне выглядит как: if i < len(lst): item = lst[i] i += 1 return item Если по шагам выполнить код на pythontutor.com: #XXX BROKEN lst = [0, '0', '0', '0', '0', '0', '0', '0', '0', 9] for i, x in enumerate(lst): lst.remove('0') size = len(lst) print(lst) # -> [0, '0', '0', '0', 9] Можно увидеть, что цикл продолжается до тех пор пока i < size. Поэтому цикл может завершиться до того как все '0' элементы удалены. Если вы хотите удалить '0' из списка, то обычный способ: lst[:] = [x for x in lst if x != '0'] Если не создавать временный список, то можно, обходя список, переносить значения, которые хочется оставить в начало списка, а затем удалить все элементы в конце: def remove_all(seq, value): pos = 0 for item in seq: if item != value: seq[pos] = item pos += 1 del seq[pos:] remove_all(lst, '0') Оба решения линейные—O(n). Первое решение требует O(n-k) дополнительной памяти, где k = lst.count('0'). Если известно, что в большом списке, только несколько значений нужно удалить (k маленькое и не зависит от n), то можно использовать удаление del lst[i], обходя список в обратном порядке (так как удаление не влияет на элементы в начале списка): for i in reversed(range(len(lst))): if lst[i] == '0': del lst[i] # O(n) * k == O(n * k) В общем случае это квадратичный алгоритм O(n**2). Чем плохи квадратичные алгоритмы Квадратичные решения могут быть заметно медленнее для больших n, чем линейные. К примеру, линейный алгоритм для списка с миллионом элементов требует не больше чем C1 * 1000_000 шагов (инструкций), в то время как квадратичный алгоритм C2 * 1000_000_000_000, где C1, C2 константы, не зависящие от размера входного списка. C1, C2 примерно (по порядку величины) равны в этом случае, поэтому линейный алгоритм гораздо более предпочтителен, если k ~ n. Если миллион инструкций выполняются примерно за миллисекунду (даже моргнуть не успеете), то квадратичный алгоритм займёт целый день, если у кого-то терпения хватит ждать или батарейка не сядет пока закончится выполнение. Миллион элементов не является каким-то большим вводом в современных условиях (телефоны гигабайты памяти имеют). Как правило можно игнорировать константы (C1, C2 в примере) вне горячих точек (hot spots), к примеру, если константа на порядок изменится (в 10 раз), то миллион инструкций линейного алгоритма займёт в 10 раз дольше: ~10 миллисекунд (всё равно быстрее чем моргнуть успеете) и гораздо меньше многих часов для квадратичного алгоритма с ~1012 операций. Подытоживая: записывая алгоритм, стоит ориентироваться на простоту, читаемость и может ли он в принципе выполнить поставленную задачу. Микро-оптимизациями, которые уродуют код, улучшая только константу (C1, C2 в примере), лучше не заниматься, если profiler не говорит обратного. Если заранее не известно, что ввод ограничен по размеру, то стоит обратить внимание на порядок роста (big O) используемого алгоритма. В частности, если это заметно не затрудняет реализацию, то линейные алгоритмы (O(n)) гораздо более предпочтительны по сравнению с квадратичными (O(n*n)). Примеры из реального мира: https://accidentallyquadratic.tumblr.com/

Ответ 2



Различие состоит в количестве элементов в контейнерах и количестве нулей в каждом из них. Когда вы удаляете 0, то размер контейнера уменьшается. Например, для первого случая это можно представить следующим образом. Пусть идентификатор i является индексом элементов последовательности, а идентификатор n - общим количеством элементов в последовательности. После каждой итерации цикла индекс увеличивается на единицу, чтобы обратиться к следующему элементу. Итак, для первого цикла начальные значения i = 0, n = 10. Теперь пройдемся по итерациям цикла i = 0; remove( 0 ); n = 9 i = 1; remove( 0 ); n = 8 i = 2; remove( 0 ); n = 7 i = 3; remove( 0 ); n = 6 i = 4; remove( 0 ); n = 5 i = 5; n <= i итерация не выполняется. В результате имеем, что было выполнено 5 удалений. Это схематическое объяснение работы цикла. Реализация цикла может быть иной, но тем не менее данная модель демонстрирует, что используемый подход к удалению элементов из последовательности некорректен и ведет к непредсказуемому результату.

понедельник, 23 декабря 2019 г.

Имя переменной, переданной в функцию

#python #python_3x #python_internals


Можно ли как-то узнать имя переменной, которую передали в функцию:

def f(x):
    ...

y = 1
z = 2
f(y) # напеччатет y
f(z) # напеччатет z

    


Ответы

Ответ 1



Это можно сделать в CPython, но не рекомендуется -- в зависимости от вашей конкретной задачи, могут более удачные решения существовать, чем пытаться имя объекта из вызываемого окружения определить: >>> import inspect >>> def f(x): ... caller_locals = inspect.currentframe().f_back.f_locals ... print(*[name for name, value in caller_locals.items() if x is value]) >>> y = 1 >>> z = 2 >>> f(y) y >>> f(z) z В зависимости от того как REPL реализована, можно различить f(y) от f(z) вызовы, даже если y = z = 1: >>> import dis >>> import inspect >>> def f(x): ... caller_frame = inspect.currentframe().f_back ... dis.dis(caller_frame.f_code) ... >>> y = z = 1 >>> f(y) 1 0 LOAD_NAME 0 (f) 2 LOAD_NAME 1 (y) 4 CALL_FUNCTION 1 6 PRINT_EXPR 8 LOAD_CONST 0 (None) 10 RETURN_VALUE >>> f(z) 1 0 LOAD_NAME 0 (f) 2 LOAD_NAME 1 (z) 4 CALL_FUNCTION 1 6 PRINT_EXPR 8 LOAD_CONST 0 (None) 10 RETURN_VALUE >>> f(1) 1 0 LOAD_NAME 0 (f) 2 LOAD_CONST 0 (1) 4 CALL_FUNCTION 1 6 PRINT_EXPR 8 LOAD_CONST 1 (None) 10 RETURN_VALUE Имя переданной переменной присутствует в байт-коде вызывающего frame. Программно можно найти вызов функции (CALL_FUNCTION инструкция) и посмотреть какое именно имя передано (если вообще имя было использовано) из caller_frame.f_code.co_names списка. К примеру в CPython 2: >>> import inspect >>> import byteplay # $ pip install byteplay >>> def f(x): ... bc = byteplay.Code.from_code(inspect.currentframe().f_back.f_code) ... i = next(i for i, (opcode, _) in enumerate(bc.code) if opcode == byteplay.CALL_FUNCTION) ... print bc.code[i-1][1] ... >>> y = z = 1 >>> f(y) y >>> f(z) z >>> f(1) 1 Disclaimer: ответ для образовательных целей, не для использования в рабочем коде.

Ответ 2



можно грязным хаком :) def f(x, g=globals()): print([n for n in g if id(g[n]) == id(x)]) y = 1 z = 2 f(y) # напеччатет y f(z) # напеччатет z >>> ['y'] >>> ['z']

Ответ 3



Я как-то нашёл это решение на каком-то сайте заморском. Там говорили, что оно нестабильно, и это действительно было так. Но я более менее разобрался в нём и даже немного модифицировал, но до сих пор считаю это магией и не до конца понимаю как это работает. Единственное что, попробовал я сейчас сделать декораторы и запросить имя переменной через них. К сожалению, результат совсем не тот. И дело в том, что строка при вызове декораторов не показывает ничего, кроме названия самой функции в ней import traceback import re def get_name(__x, level=0): # 0 - global, 1 - local, может быть есть и 2... name = traceback.extract_stack()[level][3] name = re.sub(r'.*get_name\(', '', name) name = re.search(r'[^,|()]*', name).group(0) return name def foo(level=1): a = 2 var_1 = 4 print(get_name(a, level)) print(get_name(var_1, level)) def goo(): foo() def dec(): goo() a = 1 b = 3 var1 = 2 var2 = 4 print(get_name(a)) print(get_name(b)) print(get_name(var1)) print(get_name(var2)) dec() # goo() - после преобразования - goo goo() # foo() - после преобразования - foo foo() # а тут уже внутри переменные

Ответ 4



Еще можно так: print([name for name, value in locals().items() if value is x]) Правда, как и в примерах выше, при x = y = 1 вернет ['x', 'y']

суббота, 14 декабря 2019 г.

Python len() и .__len__() в чем разница?

#python #python_internals


В чем разница между len() и .__len__()? И могут ли они возвращать разные значения?
    


Ответы

Ответ 1



__len__ это магический метод, который реализует len операцию. Как и любой другой специальный метод, он вызывается специальным образом (должен быть определён в самом классе), то есть len(o) не всегда эквивалентно o.__len__(). Подробнее в ответе о магических методах. Дополнительно, значения len() ограничены sys.maxsize: >>> import sys >>> class Big: ... def __len__(self): ... return sys.maxsize + 1 ... >>> len(Big()) Traceback (most recent call last) ... OverflowError: cannot fit 'int' into an index-sized integer >>> Big().__len__() 9223372036854775808 На практике, иногда полезно иметь последовательность с большой длиной: Get the highest possible gmtime for any architecture Weighted random sample in python

Ответ 2



Для встроенных классов (list, dict, tuple, ...) функция len(o) не вызывает o.__len__(), а напрямую вызывает функцию o->tp_as_sequence->sq_length(o) (реализована на С) (благодарю @jfs за подсказку). Для остальных классов функция len(o) вызывает o.__len__(). Интересное наблюдение: In [137]: l = list(range(10**6)) In [138]: %timeit len(l) The slowest run took 8.91 times longer than the fastest. This could mean that an intermediate result is being cached. 1000000 loops, best of 3: 314 ns per loop In [139]: %timeit l.__len__() The slowest run took 8.42 times longer than the fastest. This could mean that an intermediate result is being cached. 1000000 loops, best of 3: 388 ns per loop Похоже для объектов встроенных классов obj.__len__() немного медленнее по сравнению с len(obj). Если же реализовать __len__ в собственном классе, то будет наоборот, т.к. len(obj) это обертка (wrapper) над obj.__len__() (для объектов "не встроенных" классов) Пример: In [152]: class my_len: ...: def __len__(self): ...: return 1 ...: In [153]: a = my_len() In [154]: %timeit len(a) The slowest run took 8.22 times longer than the fastest. This could mean that an intermediate result is being cached. 1000000 loops, best of 3: 624 ns per loop In [155]: %timeit a.__len__() The slowest run took 8.95 times longer than the fastest. This could mean that an intermediate result is being cached. 1000000 loops, best of 3: 365 ns per loop UPDATE: вот ответ поясняющий почему len(o) для встроенных типов работает быстрее чем o.__len__(): The builtin len() function does not look up the .__len__ attribute. It looks up the tp_as_sequence pointer, which in turn has a sq_length attribute. The .__len__ attribute on built-in objects is indirectly mapped to the same slot, and it is that indirection (plus the attribute lookup) that takes more time. For Python-defined classes, the type object looks up the .__len__ method when the sq_length is requested.

Ответ 3



да они возвращают разные значения. Например len() возвращает количество елементов в списке >>> s = ['a', 'b', 'c', 'd', 'q'] >>> len(s) 5 А метод len может использоваться только в классе и для списков возвращает его содержимое >>> class qwerty: def __len__(self): q = ['a', 'b', 'c', 'd', 'q'] return q >>> qwerty().__len__() ['a', 'b', 'c', 'd', 'q'] >>>

вторник, 10 декабря 2019 г.

Перехват сигналов в Python

#cpp #python #процесс #обработка_сигналов #python_internals


Есть две программы, общающиеся между собой по именованным каналам. Одна на C++, вторая
на Python. Причём первая запускает вторую (стандартным способом, через fork + exec).

Участок коммуникации родительской программы (C++):

// ...
int pipeDescr;
std::string outputPipeName{"inputPipe"};
std::string inputPipeName{"outputPipe"};
char *message = new char[BUFSIZE];
// ...
while (true)
{
    int bytesNumber = 0;
    // ...
    if ((pipeDescr = open(outputPipeName.c_str(), O_WRONLY)) <= 0)
        break;

    bytesNumber = write(pipeDescr, message, strlen(message));
    if (bytesNumber <= 0)
        break;

    close(pipeDescr);

    message[0] = '\0';
    if ((pipeDescr = open(inputPipeName.c_str(), O_RDONLY)) <= 0)
        break;

    bytesNumber = read(pipeDescr, message, BUFSIZE);
    if (bytesNumber <= 0)
        break;

    close(pipeDescr);
    // ...
}


В отдельном потоке работает функция watchDog, которая несёт ответственность за работу
дочернего приложения на Python.

void watchDog(int clientSocket, pid_t pid, bool &stopWatchDog)
{
    while (true)
    {
        // Дочерняя программа завершилась с ошибкой
        if (waitpid(pid, NULL, WNOHANG) != 0)
        {
            // ...
        }

        // Родительская программа закрывается
        mutexClosing.lock();
        if (closing)
        {
            mutexClosing.unlock();
            if (waitpid(pid, NULL, WNOHANG) == 0)
            {
                kill(pid, SIGTERM);
                waitpid(pid, NULL, 0);
            }
            break;
        }
        mutexClosing.unlock();

        // Программное отключение WatchDog-а
        mutexWatchDog.lock();
        if (stopWatchDog)
        {
            if (waitpid(pid, NULL, WNOHANG) == 0)
            {
                kill(pid, SIGTERM);
                waitpid(pid, NULL, 0);
            }
            mutexWatchDog.unlock();
            break;
        }
        mutexWatchDog.unlock();
    }
}


При этом есть три исхода:


падение дочерней программы
завершение родительской программы
завершение дочерней программы без завершения родительской


В последних двух вариантах необходимо плавно завершить дочернюю программу, поэтому,
я отправляю ей сигнал SIGTERM и ожидаю завершения.

Участок коммуникации дочерней программы (Python):

def sigterm_handler(signal, frame):
    print('\nGot sigterm!\n')
    sys.exit(0)

def main():
    input_pipe_name = "inputPipe"
    output_pipe_name = "outputPipe"
    # ...
    signal.signal(signal.SIGTERM, sigterm_handler)
    # ...
    while True:
        pipe_descr = os.open(input_pipe_name, os.O_RDONLY)
        request = os.read(pipe_descr, 10000)
        os.close(pipe_descr)

        reply = work_func(request)

        pipe_descr = os.open(output_pipe_name, os.O_WRONLY)
        os.write(pipe_descr, bytes(reply, 'UTF-8'))
        os.close(pipe_descr)


В код я добавил обработку сигнала SIGTERM. Однако, при передаче этого сигнала, функция
sigterm_handler не вызывается.

Но! Если написать что-то типа этого:

def main():
    signal.signal(signal.SIGTERM, sigterm_handler)

    while True:
        print('waiting...')
        time.sleep(2)


То функция вызовется.

Подскажите, как решить данную проблему!
    


Ответы

Ответ 1



При получении сигнала, обработчик в Си выставляет флаг и сразу же завершается (здесь и далее я описываю СPython реализацию). Обработчик, написанный на Питоне, выполняется только когда контроль возвращается к главному потоку интерпретатора, что происходит позже (например, на следующем байткоде) или никогда. Цитата из официальной документации: A Python signal handler does not get executed inside the low-level (C) signal handler. Instead, the low-level signal handler sets a flag which tells the virtual machine to execute the corresponding Python signal handler at a later point(for example at the next bytecode instruction) Когда контроль возвращается зависит от того, где выполнение происходит в данный момент (разное поведение возможно для разных функций на разных версиях Питона на разных платформах). Например, os.open() на POSIX системе на Python 3.5, сводится к: do { Py_BEGIN_ALLOW_THREADS fd = open(path->narrow, flags, mode); Py_END_ALLOW_THREADS } while (fd < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals())); Py_BEGIN_ALLOW_THREADS макрос отпускает GIL, что позволяет другим потокам выполнять Питон-код, пока текущий поток блокирован на open(2) системном вызове. При установлении своего обработчика signal.signal() вызов сбрасывает SA_RESTART флаг поэтому системные вызовы такие как open(2) прерываются сигналом и возвращают EINTR, что в данном случае вызывает PyErr_CheckSignals() функцию, которая ничего не делает, если вызов не из главного потока в программе. В главном потоке PyErr_CheckSignals() проверяет был ли сигнал (по флагу, установленному Си обработчиком) и вызывает обработчик, написанный на Питоне. Если обработчик выбросит исключение, то PyErr_CheckSignals() возвращает не ноль и цикл прерывается, что ведёт к возникновению исключения на месте вызова os.open в Питон коде, когда os.open был вызван из главного потока (иначе PyErr_CheckSignals() возвращает 0). В других случаях возможно много вариантов: отпущен/не отпущен GIL (в Си коде), прерывается/автоматически перезапускается ли сам блокирующий вызов (от платформы, Си библиотеки, версии Питона может зависеть), вызывается ли PyErr_CheckSignals() в главном потоке, не сбрасывается ли где-то флаг, что сигнал произошёл до вызова обработчика (signal(SIGINT, custom_handler) не работает на Windows на Python 2.7). Если вы не можете изменить ваш блокирующий Си код, чтобы он PyErr_CheckSignals() вызывал, как это делает os.open при получении сигнала, то чтобы обойти это: вызывайте блокирующий код в фоновом потоке, а в главном потоке, спите с небольшим интервалом (это не спасёт, если ваше Си расширение для CPython не отпускает GIL, как это к примеру re модуль может делать): import threading background_thread = threading.Thread(target=fifo_loop) background_thread.daemon = True background_thread.start() while background_thread.is_alive(): background_thread.join(1) # здесь никакого другого кода, это весь цикл Обратите внимание, что это отличается от предложения добавить time.sleep(1) в ваш цикл, который с FIFO(7) работает. Если сигнал произойдёт вне вызова time.sleep(1) в вашем fifo цикле, то проблема так и останется. Обходное решение работает, потому что цикл с fifo исполняется в фоновом потоке, а главный поток только спит с перерывами. В Питоне 3, можно background_thread.join() использовать без timeout (в Питоне 2, этот .join() не прерывался сигналами).

Ответ 2



после while True: необходимо добавить time.sleep(1), так как нужно немного ждать, чтобы выдать сигнал для процесса. p.s. взято из комментариев

суббота, 7 декабря 2019 г.

Почему один способ проверки чисел в списке медленнее второго?

#python #python_3x #производительность #python_internals


import time

lst = [-3, 3, 7, 0, -10, 23, -9, -8, -5, -10, 9, 3,
 -2, 8, -3, 6, -1, 0, 10, -1, -6, -6, 10, -7, 3, 8,
 0, 7, 1, 5, -3, -6, 4, 6, -6, -4, -3, 10, 10, -5,
 -7, 0, -4, -8, 2, 9, 0, -10, -3, 3, -4, 9, -7, -8,
 0, -1, 1, 7, 2, -1, 3, 0, 9, -9, 4, 7, 6, 10, 8,
 -6, 3, 1, 1, 9, -8, -8, 2, 4, 10, 1, 5, -1, -1, 5,
 -9, 9, -3, 3, 0, -6, 2, 5, 10, 10, 5, -6, -10,  -2, -9, 'СТРОКА']


start1 = time.clock()
a =  all(isinstance(item, (int, float, complex)) for item in lst)
finish1 = time.clock()
print("Результат: {}, время:{:.2}ms".format(a, (finish1 - start1) * 1000))

start2 = time.clock()
def is_numbers(iterable):
    for item in iterable:
        if not isinstance(item, (int, float, complex)):
            return False
    return True
b = is_numbers(lst)
finish2 = time.clock()
print("Результат: {}, время:{:.2}ms".format(b, (finish2 - start2) * 1000))

Результат:

>>> 
====== RESTART: /home/dzmitry/adasdsadasdasdsad.py ======
Результат: False, время:0.081ms
Результат: False, время:0.042ms
>>> 
====== RESTART: /home/dzmitry/adasdsadasdasdsad.py ======
Результат: False, время:0.085ms
Результат: False, время:0.043ms
>>> 

Функция all() эквивалентна:


def all(iterable):
    for element in iterable:
        if not element:
            return False
    return True


Получается, что первый и второй способ равнозначны, за исключением использования
генераторного выражения. Почему первый способ медленнее в 1.5-2 раза?
    


Ответы

Ответ 1



Дело не в проверке типов—разница во времени сохраняется даже если заменить isinstance(item, ..) вызов на f(item), где f функция ничего не делает, а просто возвращает True значение: f = lambda item: True (чтобы all() весь lst список просматривала без раннего выхода). Более того, разница остаётся, если вообще вызов функции убрать: In [1]: %timeit all(True for _ in range(1000)) 10000 loops, best of 3: 69.7 µs per loop In [2]: def loop(): ...: for _ in range(1000): ...: if not True: ...: return False ...: return True ...: In [3]: %timeit loop() 10000 loops, best of 3: 30.3 µs per loop Явный for-цикл может быть эффективней генератора в CPython (результат также может зависеть от платформы). all_(), реализованная вручную, слегка медленнее встроенной версии all(), которая реализует похожий цикл в C: def all_(iterable): for item in iterable: if not item: return False return True то есть важно что генератор используется, а не то как all() реализована: In [4]: %timeit all_(range(1, 1000)) 10000 loops, best of 3: 41.1 µs per loop In [5]: %timeit all_(i for i in range(1, 1000)) 10000 loops, best of 3: 93.9 µs per loop Даже генератор списков (listcomp—списковое включение) может быть эффективней эквивалентного genexpr (хотя в Jython это с точностью до наоборот): In [6]: %timeit [True for _ in range(1000)] 10000 loops, best of 3: 44.4 µs per loop In [7]: %timeit list(True for _ in range(1000)) 10000 loops, best of 3: 83.2 µs per loop Хотя между этими выражениями не должно быть особой разницы в Питоне 3: Why this list comprehension is faster than equivalent generator expression? Байт-код ничего особо подозрительного не показывает: >>> import dis >>> dis.dis(lambda: [True for _ in range(1000000)]) 1 0 LOAD_CONST 1 ( at 0x610efc445eea, file "", line 1>) 3 LOAD_CONST 2 ('..') 6 MAKE_FUNCTION 0 9 LOAD_GLOBAL 0 (range) 12 LOAD_CONST 3 (1000000) 15 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 18 GET_ITER 19 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 22 RETURN_VALUE >>> dis.dis(lambda: list(True for _ in range(1000000))) 1 0 LOAD_GLOBAL 0 (list) 3 LOAD_CONST 1 ( at 0x610efc4bc0b2, file "", line 1>) 6 LOAD_CONST 2 ('..') 9 MAKE_FUNCTION 0 12 LOAD_GLOBAL 1 (range) 15 LOAD_CONST 3 (1000000) 18 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 21 GET_ITER 22 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 25 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 28 RETURN_VALUE Первый фрагмент: создаёт функцию из listcomp объекта и вызывает её с результатом iter(range(1000000)). Второй фрагмент: создаёт функцию из genexpr и вызывает её с тем же аргументом, дополнительно вызывается list() функция с результатом. listcomp реализуется достаточно прямолинейно: >>> code = compile('[True for _ in range(1000000)]', '', 'eval') >>> code.co_consts[0] at 0x610efc445f2c, file "", line 1> >>> dis.dis(code.co_consts[0]) 1 0 BUILD_LIST 0 3 LOAD_FAST 0 (.0) >> 6 FOR_ITER 12 (to 21) 9 STORE_FAST 1 (_) 12 LOAD_CONST 0 (True) 15 LIST_APPEND 2 18 JUMP_ABSOLUTE 6 >> 21 RETURN_VALUE Что близко к: def listcomp(it): L = [] for _ in it: L.append(True) return L где it = iter(range(1000000)), полученный ранее. genexpr выглядит похоже: >>> code = compile('list(True for _ in range(1000000))', '', 'eval') >>> code.co_consts[0] at 0x610efc44145d, file "", line 1> >>> dis.dis(code.co_consts[0]) 1 0 LOAD_FAST 0 (.0) >> 3 FOR_ITER 11 (to 17) 6 STORE_FAST 1 (_) 9 LOAD_CONST 0 (True) 12 YIELD_VALUE 13 POP_TOP 14 JUMP_ABSOLUTE 3 >> 17 LOAD_CONST 1 (None) 20 RETURN_VALUE Что близко к: def genexpr(it): for _ in it: yield True созданный генератор (объект) передаётся во встроеннуюlist() функцию, которая реализована вызовом listextend(), что выполняет код близкий к listcomp(it), приведённому выше. Снова разница в производительности не в использовании list(), а в том что ей передан генератор (list(range(1000)) и list(iter(range(1000)) могут быть быстрее как list(i for i in range(1000)) так и [True for _ in range(1000)]). Уже не так удивительно, что даже list([True for _ in range(1000)]), который казалось бы выполняет лишнюю работу, может быть быстрее list(True for _ in range(1000)) (CPython/Pypy 2/3, Ubuntu). Не стоит слишком увлекаться микрооптимизациями—пишите самый простой и понятный код, который решает поставленную задачу. Не изменяйте код, пока измерения не показали, что это необходимо в вашем конкретном случае.

Ответ 2



Посмотреть можно, используя profile. Как видно, время помимо isinstance 98902 0.125 0.000 0.125 0.000 :0(isinstance) уходит на genexpr те генератор 98903 0.406 0.000 0.531 0.000 C:/Scripts/python/2016/4/123.py:21() # -*- coding: UTF-8 -*- import timeit import profile, pstats p = 'profile.txt' def execTime(target_: list, repeat=1): target_[:] = [(fn.__name__, timeit.Timer(fn).timeit(repeat)) for fn in target_] for e, (n, tmt) in enumerate(sorted(target_, key=lambda r: r[1]), start=1): print("{}'time {} {}".format(e, n, tmt)) lst = [-3, 3, 7, 0, -10, 23, -9, -8, -5, -10, 9, 3, -2, 8, -3, 6, -1, 0, 10, -1, -6, -6, 10, -7, 3, 8, 0, 7, 1, 5, -3, -6, 4, 6, -6, -4, -3, 10, 10, -5, -7, 0, -4, -8, 2, 9, 0, -10, -3, 3, -4, 9, -7, -8, 0, -1, 1, 7, 2, -1, 3, 0, 9, -9, 4, 7, 6, 10, 8, -6, 3, 1, 1, 9, -8, -8, 2, 4, 10, 1, 5, -1, -1, 5, -9, 9, -3, 3, 0, -6, 2, 5, 10, 10, 5, -6, -10, -2, -9]*999 + ['СТРОКА'] def test1(): return all(isinstance(item, (int, float, complex)) for item in lst) def test2(): for a in lst: if not isinstance(a, (int, float, complex)): return if __name__ == '__main__': print('-'*20, 1) profile.run('test1()', p) print(pstats.Stats(p).sort_stats('cumtime').print_stats()) print('-'*20, 2) profile.run('test2()', p) print(pstats.Stats(p).sort_stats('cumtime').print_stats()) print('-'*20, 3) execTime([test1, test2], 100) out: -------------------- 1 Mon Sep 19 10:45:57 2016 profile.txt 197811 function calls in 0.672 seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.672 0.672 :0(exec) 1 0.141 0.141 0.672 0.672 :0(all) 1 0.000 0.000 0.672 0.672 C:/Scripts/python/2016/4/123.py:20(test1) 1 0.000 0.000 0.672 0.672 :1() 1 0.000 0.000 0.672 0.672 profile:0(test1()) 98903 0.406 0.000 0.531 0.000 C:/Scripts/python/2016/4/123.py:21() 98902 0.125 0.000 0.125 0.000 :0(isinstance) 1 0.000 0.000 0.000 0.000 :0(setprofile) 0 0.000 0.000 profile:0(profiler) -------------------- 2 Mon Sep 19 10:45:59 2016 profile.txt 98907 function calls in 0.203 seconds Ordered by: cumulative time ncalls tottime percall cumtime percall filename:lineno(function) 1 0.000 0.000 0.203 0.203 :0(exec) 1 0.000 0.000 0.203 0.203 :1() 1 0.094 0.094 0.203 0.203 C:/Scripts/python/2016/4/123.py:23(test2) 1 0.000 0.000 0.203 0.203 profile:0(test2()) 98902 0.109 0.000 0.109 0.000 :0(isinstance) 1 0.000 0.000 0.000 0.000 :0(setprofile) 0 0.000 0.000 profile:0(profiler) -------------------- 3 1'time test2 3.4469346536397873 2'time test1 4.2089339634381275

вторник, 26 ноября 2019 г.

Что такое объект в Python. Почему id(a) == id(b) == id(1)?


Детализирую вопрос.
В книгах по Python пишут, что все в Python есть объект.

Берем традиционный подход к изучению/обучению языкам программирования.
Языки Pascal, C, C++, у них есть понятие  "имя переменной" (идентификатор). С имене
переменной связывается/присваивается значение. Значение хранится в памяти. Имя переменной - это способ (в исходном тексте программы) обратиться к ячейке памяти для получения значения, которое там хранится. 

Не знаю верно ли высказывание (для компилируемых языков): Для  переменных, например
целого типа, в момент выполнения программы на компьютере, процессор работает уже с адресам
ячеек памяти, где хранятся значения переменных. Т.е. не такого, что где-то по какому-то адресу хранится имя переменной и оно связывается с адресом ячейки, в которой хранится значение.

Теперь переходим к Python. В этом языке все является объектом, даже определение функции.
Переменная объект, значение объект и т.д. В разное время с одним и тем же объектом может соотноситься разный другой объект. 

a = 1
print(a)
a = "Hello"
print(a)


Но тогда как понять действие функции id()? По определению стандарта
она возвращает "identify" указанного объекта. Причем, "возвращает целое, гарантированно являющееся уникальным" и постоянным для объекта на время его существования."

Тогда почему 

a = 1
b = 1
print(id(a) == id(b) == id(1))  # True
print("Why?")


ведь a, b, 1 разные объекты?
По-моему вопрос получился...

Используется Python 3.6



Про значения от -5 до 256 наслышан. Имеет место быть

a = 1000000
b = 1000000
print(id(a) == id(b))  # True
print("Why?")


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


Ответы

Ответ 1



В питоне интерпретатор оптимизирован так, что небольшие целые числа представлен одним объектом - это сделано в целях улучшения производительности. Для больших чисел это уже не выполняется. Марк Лутц - Изучаем Python, 4-е издание, 2011, стр. 204: >>> X = 42 >>> Y = 42 # Должно получиться два разных объекта >>> X == Y True >>> X is Y # Тот же самый объект: кэширование в действии! True В этом примере переменные X и Y должны быть равны (==, одно и то же значение), но не эквивалентны (is, один и тот же объект), потому что было выполнено два разных литеральных выражения. Однако из-за того, что малые целые числа и строки кэшируются и используются повторно, оператор is сообщает, что переменные ссылаются на один и тот же объект.

Ответ 2



В разное время с одним и тем же объектом может соотноситься разный другой объект. Вы хотели сказать: в разное время, одно и то же имя может ссылаться на разные объекты Модель в Питоне простая — совершенно не нужно знать что такое адрес, ячейка памяти, указатель. Достаточно знать только об именах и объектах, к которым они привязаны. Вот картинки, которые поясняют разницу между переменными в С подобных языках и именам в Питоне. Ярлыки и шарики достаточны, чтобы любое поведение на уровне Питона объяснить — сперва не верится что такая простая модель все крайние случаи охватывает. ведь a, b, 1 разные объекты? У вас только один объект (единица). Маленькие целые кэшируются в CPython. Разны имена a, b ссылаются на один и тот же объект: a is b (id(a) == id(b)). 1 это константа в исходном коде (думайте как о вызове конструктора для объекта). Подробнее об int объектах: Присваивание в Python. a = 1000000; b = 1000000; print (id(a) == id(b)) # True Для проверки ссылаются ли имена на одни и те же объекты, используйте a is b (здесь это равнозначно id(a) == id(b), но id может быть переопределён в общем случае). Результат может быть True (зависит как Питон код был в байт-код скомпилирован в выбранной реализации), но не обязан быть True: >>> a = 1000000 >>> b = 1000000 >>> print (a is b) False В данном случае, каждое выражение отдельно компилируется, a и b на разные объекты ссылаются. Сравните: >>> def f(): ... a = 1000000 ... b = 1000000 ... print(a is b) >>> f() True Код функции как одно целое компилируется, поэтому a is b может (но не обязано) быт True. Один это объект или несколько может зависеть от реализации Питона (CPython, Pypy Jython, etc) и даже конкретной версии реализации. Реальный код не должен зависеть от подобных деталей реализации. Как явно сказано по ссылке: не используйте проверку на идентичность для сравнения чисел (разные объекты могут иметь одно значение). Используйте a == b, чтобы узнать равны ли числа. Питоне все объект. Нет имен переменных. Отсюда и мой вопрос. Конечно в Питоне есть имена. a и b это имена в вашем вопросе. То что кодом Питона в разных представлениях можно манипулировать как простым объекто (в функцию передать как параметр, вернуть из функции, вызвать методы, итд) никак не отменяет наличие имён. Примеры: на уровне AST — имена это узлы типа _ast.Name. Более сложный пример это MacroPy на уровне байт-кода (byteplay) исходного кода (lib2to3) Также, для манипуляций Питон кода, может быть полезно знать о compile(), eval() exec() встроенных функциях, dis, uncompyle6, inspect модулях. К примеру, inspect.signature( позволяет манипулировать описанием функции как объектом. В уже упомянутой ссылке показано как саму память, занимаемую объектом в CPython реализации, можно также как обычным Питон объектом манипулировать с помощью ctypes (пример изменяет неизменяемый на уровне Питона объект типа int).

Ответ 3



переменные хранят только ссылки на объект оператор = присваивает переменной ссылку примитивные типы (литералы: числа, строки) с одинаковым значением представлены единственным объектом

пятница, 11 января 2019 г.

Размер списка в Python и объём оперативной памяти

При исполнении следующего кода
n = 10 ** 9 alist = [0] * n
Компьютер начинает работать очень медленно (похоже из-за нехватки оперативной памяти?). Если я правильно понял, в этом случае список создаётся и вмещается в память, но занимает такое её количество, что всё начинает "тормозить". При этом, например, если размер списка равен 10 ** 10, то сразу выдаётся MemoryError. Получается, что пайтон не может вместить список такого размера в оперативную память (её физически не хватает) или стоят какие-то ограничения на размер списка?
Хотелось бы разобраться, почему так происходит в этих двух случаях. Также хотел узнать, как лучше защититься от зависаний вследствие нехватки оперативной памяти (каждый раз заранее прикидывать, какой объём памяти будет занимать список, написать какой-нибудь код, где будет проверяться это дело или ещё как-то?).
Обновление
Списки такого размера мне понадобились при нахождении всех простых чисел меньше n с помощью решета Эратосфена. Я начал с n = 100 и постепенно увеличивал размер списка. Вплоть до 10 ** 8 всё работало, а потом начались неприятные тормоза. Соответственно, хотел узнать как можно защититься (от себя) в таких случаях. Может есть какие-нибудь рекомендации по максимальному размеру списка в процентах от объёма оперативной памяти, чтобы не возникало таких зависаний?


Ответ

[0] * n список в CPython требует примерно n * <размер указателя> байтов (можно посмотреть на реализации функций list_repeat(), PyList_New(n), PyMem_Malloc(), чтобы увидеть случаи когда MemoryError сразу выбрасывается без попытки выделения памяти -- на 32-битной платформе эти пределы достаточно низкие: n = 536870911 (((2**32 - 1) >> 1) // 4), на 64-битной платформе маловероятно что эти условия сработают -- другими словами: вы можете создавать списки настолько большими насколько у вас памяти хватит -- практически ограничений нет.
Защита простая: узнайте сколько у вас на компьютере памяти и не пытайтесь выделить больше этого кол-ва.
По конкретной задаче: можно уменьшить потребление памяти в 4-8 раз, если использовать array, numpy.array вместо списка. Можно ещё снизить, если предварительно исключить маленькие простые числа (wheel-optimizations), cм. Fastest way to list all primes below N. Можно ещё gmpy2 использовать (до 16 раз меньше памяти), см. Speed up bitstring/bit operations in Python?
Для n более 10**9 может иметь смысл использовать решето Аткина, которое требует N1/2+o(1) бит памяти, вместо решета Эратосфена O(N1/2(log log N)/log N))
Существуют алгоритмы без верхней границы, если не известно заранее сколько простых чисел надо.
Если нужны бо́ льшие числа, то можно использовать алгоритмы, которые работают посегментно (позволяют как верхную так и нижнюю границу указать), например, pyprimesieve

вторник, 20 ноября 2018 г.

Имя переменной, переданной в функцию

Можно ли как-то узнать имя переменной, которую передали в функцию:
def f(x): ...
y = 1 z = 2 f(y) # напеччатет y f(z) # напеччатет z


Ответ

Это можно сделать в CPython, но не рекомендуется -- в зависимости от вашей конкретной задачи, могут более удачные решения существовать, чем пытаться имя объекта из вызываемого окружения определить:
>>> import inspect >>> def f(x): ... caller_locals = inspect.currentframe().f_back.f_locals ... print(*[name for name, value in caller_locals.items() if x is value]) >>> y = 1 >>> z = 2 >>> f(y) y >>> f(z) z

В зависимости от того как REPL реализована, можно различить f(y) от f(z) вызовы, даже если y = z = 1
>>> import dis >>> import inspect >>> def f(x): ... caller_frame = inspect.currentframe().f_back ... dis.dis(caller_frame.f_code) ... >>> y = z = 1 >>> f(y) 1 0 LOAD_NAME 0 (f) 2 LOAD_NAME 1 (y) 4 CALL_FUNCTION 1 6 PRINT_EXPR 8 LOAD_CONST 0 (None) 10 RETURN_VALUE >>> f(z) 1 0 LOAD_NAME 0 (f) 2 LOAD_NAME 1 (z) 4 CALL_FUNCTION 1 6 PRINT_EXPR 8 LOAD_CONST 0 (None) 10 RETURN_VALUE >>> f(1) 1 0 LOAD_NAME 0 (f) 2 LOAD_CONST 0 (1) 4 CALL_FUNCTION 1 6 PRINT_EXPR 8 LOAD_CONST 1 (None) 10 RETURN_VALUE
Имя переданной переменной присутствует в байт-коде вызывающего frame. Программно можно найти вызов функции (CALL_FUNCTION инструкция) и посмотреть какое именно имя передано (если вообще имя было использовано) из caller_frame.f_code.co_names списка. К примеру в CPython 2:
>>> import inspect >>> import byteplay # $ pip install byteplay >>> def f(x): ... bc = byteplay.Code.from_code(inspect.currentframe().f_back.f_code) ... i = next(i for i, (opcode, _) in enumerate(bc.code) if opcode == byteplay.CALL_FUNCTION) ... print bc.code[i-1][1] ... >>> y = z = 1 >>> f(y) y >>> f(z) z >>> f(1) 1
Disclaimer: ответ для образовательных целей, не для использования в рабочем коде.

пятница, 19 октября 2018 г.

Python len() и .__len__() в чем разница?

В чем разница между len() и .__len__()? И могут ли они возвращать разные значения?


Ответ

__len__ это магический метод, который реализует len операцию. Как и любой другой специальный метод, он вызывается специальным образом (должен быть определён в самом классе), то есть len(o) не всегда эквивалентно o.__len__(). Подробнее в ответе о магических методах
Дополнительно, значения len() ограничены sys.maxsize
>>> import sys >>> class Big: ... def __len__(self): ... return sys.maxsize + 1 ... >>> len(Big()) Traceback (most recent call last) ... OverflowError: cannot fit 'int' into an index-sized integer >>> Big().__len__() 9223372036854775808
На практике, иногда полезно иметь последовательность с большой длиной:
Get the highest possible gmtime for any architecture Weighted random sample in python

понедельник, 15 октября 2018 г.

Перехват сигналов в Python

Есть две программы, общающиеся между собой по именованным каналам. Одна на C++, вторая на Python. Причём первая запускает вторую (стандартным способом, через fork + exec).
Участок коммуникации родительской программы (C++):
// ... int pipeDescr; std::string outputPipeName{"inputPipe"}; std::string inputPipeName{"outputPipe"}; char *message = new char[BUFSIZE]; // ... while (true) { int bytesNumber = 0; // ... if ((pipeDescr = open(outputPipeName.c_str(), O_WRONLY)) <= 0) break;
bytesNumber = write(pipeDescr, message, strlen(message)); if (bytesNumber <= 0) break;
close(pipeDescr);
message[0] = '\0'; if ((pipeDescr = open(inputPipeName.c_str(), O_RDONLY)) <= 0) break;
bytesNumber = read(pipeDescr, message, BUFSIZE); if (bytesNumber <= 0) break;
close(pipeDescr); // ... }
В отдельном потоке работает функция watchDog, которая несёт ответственность за работу дочернего приложения на Python.
void watchDog(int clientSocket, pid_t pid, bool &stopWatchDog) { while (true) { // Дочерняя программа завершилась с ошибкой if (waitpid(pid, NULL, WNOHANG) != 0) { // ... }
// Родительская программа закрывается mutexClosing.lock(); if (closing) { mutexClosing.unlock(); if (waitpid(pid, NULL, WNOHANG) == 0) { kill(pid, SIGTERM); waitpid(pid, NULL, 0); } break; } mutexClosing.unlock();
// Программное отключение WatchDog-а mutexWatchDog.lock(); if (stopWatchDog) { if (waitpid(pid, NULL, WNOHANG) == 0) { kill(pid, SIGTERM); waitpid(pid, NULL, 0); } mutexWatchDog.unlock(); break; } mutexWatchDog.unlock(); } }
При этом есть три исхода:
падение дочерней программы завершение родительской программы завершение дочерней программы без завершения родительской
В последних двух вариантах необходимо плавно завершить дочернюю программу, поэтому, я отправляю ей сигнал SIGTERM и ожидаю завершения.
Участок коммуникации дочерней программы (Python):
def sigterm_handler(signal, frame): print('
Got sigterm!
') sys.exit(0)
def main(): input_pipe_name = "inputPipe" output_pipe_name = "outputPipe" # ... signal.signal(signal.SIGTERM, sigterm_handler) # ... while True: pipe_descr = os.open(input_pipe_name, os.O_RDONLY) request = os.read(pipe_descr, 10000) os.close(pipe_descr)
reply = work_func(request)
pipe_descr = os.open(output_pipe_name, os.O_WRONLY) os.write(pipe_descr, bytes(reply, 'UTF-8')) os.close(pipe_descr)
В код я добавил обработку сигнала SIGTERM. Однако, при передаче этого сигнала, функция sigterm_handler не вызывается.
Но! Если написать что-то типа этого:
def main(): signal.signal(signal.SIGTERM, sigterm_handler)
while True: print('waiting...') time.sleep(2)
То функция вызовется.
Подскажите, как решить данную проблему!


Ответ

При получении сигнала, обработчик в Си выставляет флаг и сразу же завершается (здесь и далее я описываю СPython реализацию). Обработчик, написанный на Питоне, выполняется только когда контроль возвращается к главному потоку интерпретатора, что происходит позже (например, на следующем байткоде) или никогда. Цитата из официальной документации
A Python signal handler does not get executed inside the low-level (C) signal handler. Instead, the low-level signal handler sets a flag which tells the virtual machine to execute the corresponding Python signal handler at a later point(for example at the next bytecode instruction)
Когда контроль возвращается зависит от того, где выполнение происходит в данный момент (разное поведение возможно для разных функций на разных версиях Питона на разных платформах). Например, os.open() на POSIX системе на Python 3.5, сводится к
do { Py_BEGIN_ALLOW_THREADS fd = open(path->narrow, flags, mode); Py_END_ALLOW_THREADS } while (fd < 0 && errno == EINTR && !(async_err = PyErr_CheckSignals()));
Py_BEGIN_ALLOW_THREADS макрос отпускает GIL, что позволяет другим потокам выполнять Питон-код, пока текущий поток блокирован на open(2) системном вызове.
При установлении своего обработчика signal.signal() вызов сбрасывает SA_RESTART флаг поэтому системные вызовы такие как open(2) прерываются сигналом и возвращают EINTR, что в данном случае вызывает PyErr_CheckSignals() функцию, которая ничего не делает, если вызов не из главного потока в программе. В главном потоке PyErr_CheckSignals() проверяет был ли сигнал (по флагу, установленному Си обработчиком) и вызывает обработчик, написанный на Питоне. Если обработчик выбросит исключение, то PyErr_CheckSignals() возвращает не ноль и цикл прерывается, что ведёт к возникновению исключения на месте вызова os.open в Питон коде, когда os.open был вызван из главного потока (иначе PyErr_CheckSignals() возвращает 0).
В других случаях возможно много вариантов: отпущен/не отпущен GIL (в Си коде), прерывается/автоматически перезапускается ли сам блокирующий вызов (от платформы, Си библиотеки, версии Питона может зависеть), вызывается ли PyErr_CheckSignals() в главном потоке, не сбрасывается ли где-то флаг, что сигнал произошёл до вызова обработчика (signal(SIGINT, custom_handler) не работает на Windows на Python 2.7).
Если вы не можете изменить ваш блокирующий Си код, чтобы он PyErr_CheckSignals() вызывал, как это делает os.open при получении сигнала, то чтобы обойти это: вызывайте блокирующий код в фоновом потоке, а в главном потоке, спите с небольшим интервалом (это не спасёт, если ваше Си расширение для CPython не отпускает GIL, как это к примеру re модуль может делать):
import threading
background_thread = threading.Thread(target=fifo_loop) background_thread.daemon = True background_thread.start() while background_thread.is_alive(): background_thread.join(1) # здесь никакого другого кода, это весь цикл
Обратите внимание, что это отличается от предложения добавить time.sleep(1) в ваш цикл, который с FIFO(7) работает. Если сигнал произойдёт вне вызова time.sleep(1) в вашем fifo цикле, то проблема так и останется.
Обходное решение работает, потому что цикл с fifo исполняется в фоновом потоке, а главный поток только спит с перерывами. В Питоне 3, можно background_thread.join() использовать без timeout (в Питоне 2, этот .join() не прерывался сигналами).

среда, 10 октября 2018 г.

Почему один способ проверки чисел в списке медленнее второго?

import time
lst = [-3, 3, 7, 0, -10, 23, -9, -8, -5, -10, 9, 3, -2, 8, -3, 6, -1, 0, 10, -1, -6, -6, 10, -7, 3, 8, 0, 7, 1, 5, -3, -6, 4, 6, -6, -4, -3, 10, 10, -5, -7, 0, -4, -8, 2, 9, 0, -10, -3, 3, -4, 9, -7, -8, 0, -1, 1, 7, 2, -1, 3, 0, 9, -9, 4, 7, 6, 10, 8, -6, 3, 1, 1, 9, -8, -8, 2, 4, 10, 1, 5, -1, -1, 5, -9, 9, -3, 3, 0, -6, 2, 5, 10, 10, 5, -6, -10, -2, -9, 'СТРОКА']
start1 = time.clock() a = all(isinstance(item, (int, float, complex)) for item in lst) finish1 = time.clock() print("Результат: {}, время:{:.2}ms".format(a, (finish1 - start1) * 1000))
start2 = time.clock() def is_numbers(iterable): for item in iterable: if not isinstance(item, (int, float, complex)): return False return True b = is_numbers(lst) finish2 = time.clock() print("Результат: {}, время:{:.2}ms".format(b, (finish2 - start2) * 1000))
Результат:
>>> ====== RESTART: /home/dzmitry/adasdsadasdasdsad.py ====== Результат: False, время:0.081ms Результат: False, время:0.042ms >>> ====== RESTART: /home/dzmitry/adasdsadasdasdsad.py ====== Результат: False, время:0.085ms Результат: False, время:0.043ms >>>
Функция all() эквивалентна
def all(iterable): for element in iterable: if not element: return False return True
Получается, что первый и второй способ равнозначны, за исключением использования генераторного выражения. Почему первый способ медленнее в 1.5-2 раза?


Ответ

Дело не в проверке типов—разница во времени сохраняется даже если заменить isinstance(item, ..) вызов на f(item), где f функция ничего не делает, а просто возвращает True значение: f = lambda item: True (чтобы all() весь lst список просматривала без раннего выхода). Более того, разница остаётся, если вообще вызов функции убрать:
In [1]: %timeit all(True for _ in range(1000)) 10000 loops, best of 3: 69.7 µs per loop
In [2]: def loop(): ...: for _ in range(1000): ...: if not True: ...: return False ...: return True ...:
In [3]: %timeit loop() 10000 loops, best of 3: 30.3 µs per loop
Явный for-цикл может быть эффективней генератора в CPython (результат также может зависеть от платформы).
all_(), реализованная вручную, слегка медленнее встроенной версии all(), которая реализует похожий цикл в C
def all_(iterable): for item in iterable: if not item: return False return True
то есть важно что генератор используется, а не то как all() реализована:
In [4]: %timeit all_(range(1, 1000)) 10000 loops, best of 3: 41.1 µs per loop In [5]: %timeit all_(i for i in range(1, 1000)) 10000 loops, best of 3: 93.9 µs per loop

Даже генератор списков (listcomp—списковое включение) может быть эффективней эквивалентного genexpr (хотя в Jython это с точностью до наоборот):
In [6]: %timeit [True for _ in range(1000)] 10000 loops, best of 3: 44.4 µs per loop
In [7]: %timeit list(True for _ in range(1000)) 10000 loops, best of 3: 83.2 µs per loop
Хотя между этими выражениями не должно быть особой разницы в Питоне 3: Why this list comprehension is faster than equivalent generator expression?
Байт-код ничего особо подозрительного не показывает:
>>> import dis >>> dis.dis(lambda: [True for _ in range(1000000)]) 1 0 LOAD_CONST 1 ( at 0x610efc445eea, file "", line 1>) 3 LOAD_CONST 2 ('..') 6 MAKE_FUNCTION 0 9 LOAD_GLOBAL 0 (range) 12 LOAD_CONST 3 (1000000) 15 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 18 GET_ITER 19 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 22 RETURN_VALUE >>> dis.dis(lambda: list(True for _ in range(1000000))) 1 0 LOAD_GLOBAL 0 (list) 3 LOAD_CONST 1 ( at 0x610efc4bc0b2, file "", line 1>) 6 LOAD_CONST 2 ('..') 9 MAKE_FUNCTION 0 12 LOAD_GLOBAL 1 (range) 15 LOAD_CONST 3 (1000000) 18 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 21 GET_ITER 22 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 25 CALL_FUNCTION 1 (1 positional, 0 keyword pair) 28 RETURN_VALUE
Первый фрагмент: создаёт функцию из listcomp объекта и вызывает её с результатом iter(range(1000000)). Второй фрагмент: создаёт функцию из genexpr и вызывает её с тем же аргументом, дополнительно вызывается list() функция с результатом.
listcomp реализуется достаточно прямолинейно:
>>> code = compile('[True for _ in range(1000000)]', '', 'eval') >>> code.co_consts[0] at 0x610efc445f2c, file "", line 1> >>> dis.dis(code.co_consts[0]) 1 0 BUILD_LIST 0 3 LOAD_FAST 0 (.0) >> 6 FOR_ITER 12 (to 21) 9 STORE_FAST 1 (_) 12 LOAD_CONST 0 (True) 15 LIST_APPEND 2 18 JUMP_ABSOLUTE 6 >> 21 RETURN_VALUE
Что близко к:
def listcomp(it): L = [] for _ in it: L.append(True) return L
где it = iter(range(1000000)), полученный ранее.
genexpr выглядит похоже:
>>> code = compile('list(True for _ in range(1000000))', '', 'eval') >>> code.co_consts[0] at 0x610efc44145d, file "", line 1> >>> dis.dis(code.co_consts[0]) 1 0 LOAD_FAST 0 (.0) >> 3 FOR_ITER 11 (to 17) 6 STORE_FAST 1 (_) 9 LOAD_CONST 0 (True) 12 YIELD_VALUE 13 POP_TOP 14 JUMP_ABSOLUTE 3 >> 17 LOAD_CONST 1 (None) 20 RETURN_VALUE
Что близко к:
def genexpr(it): for _ in it: yield True
созданный генератор (объект) передаётся во встроеннуюlist() функцию, которая реализована вызовом listextend(), что выполняет код близкий к listcomp(it), приведённому выше. Снова разница в производительности не в использовании list(), а в том что ей передан генератор (list(range(1000)) и list(iter(range(1000)) могут быть быстрее как list(i for i in range(1000)) так и [True for _ in range(1000)]).
Уже не так удивительно, что даже list([True for _ in range(1000)]), который казалось бы выполняет лишнюю работу, может быть быстрее list(True for _ in range(1000)) (CPython/Pypy 2/3, Ubuntu).
Не стоит слишком увлекаться микрооптимизациями—пишите самый простой и понятный код, который решает поставленную задачу. Не изменяйте код, пока измерения не показали, что это необходимо в вашем конкретном случае.

четверг, 4 октября 2018 г.

Присваивание целых (int) в Питоне

Допустим, есть следующий код:
a = 32 b = a
print(a is b)
Как сделать так, чтобы b становилось ссылкой на новое значение в памяти, а не на то значение, на которое ссылается a, т.е. как сделать так, чтобы операция print(a is b) напечатала False?


Ответ

b = a заставляет b имя ссылаться на тот же объект что и a имя.
Операция a is b проверяет указывают ли оба имени на один и тот же объект, поэтому всегда всегда a is b будет True после a = b в Питоне.
Если вы хотите, чтобы имя b ссылалось на объект отличный от a, то просто присвойте любой другой объект, например, b = None для примера в вопросе или можно вообще удалить имя del b (при попытке использования после этого исключение возникнет).

Если вы хотите, чтобы a и b были равны (a == b == 32), но были бы разными объектами (a is not b), то ответ может зависеть от конкретной реализации Питона.
Например, в CPython маленькие целые числа кэшируются поэтому 32 is (16*2) is (31+1)... то есть существует только один объект, который представляет число 32 в СPython. Целые числа неизменяемы в Питоне, поэтому арифметика не изменится от того один и тот же объект для 32 возвращается каждый раз или разные.
Нужно обратить внимание, что не следует оператор is использовать для сравнения чисел. Так как разные объекты могут иметь одно значение (быть равными), например, 1000 == (999+1), но 1000 is not (999+1) (в данном случае (999+1) возвращает новый объект, отличный от объекта, созданного для 1000 константы). is можно использовать для сравнения с singleton—объект, который в единственном экземпляре существует, такой как None. Питон не обещает, что объекты, представляющие числа, существуют в единственном экземпляре, поэтому используйте == для сравнения целых чисел.

Есть несколько решений буквальной задачи (a == b == 32 and a is not b), если её рассматривать как загадку (не для использования на практике).
Чтобы обойти кэширование маленьких чисел, можно создать производный от int класс:
>>> a = 32 >>> class Int(int): ... pass ... >>> b = Int(a) >>> a == b == 32 and a is not b True
Или использовать float или любой другой тип, который можно с целыми сравнивать:
>>> b = 32. >>> a == b == 32 and a is not b True
Наконец, если вы хотите, чтобы type(a) == type(b) == int, где int это встроенный PyLongObject тип, то возможно сделать злую вещь и поменять значение известной константы 42
>>> import ctypes >>> import sys >>> a = 32 >>> b = 42 >>> (ctypes.c_ubyte * sys.getsizeof(b)).from_address(id(b))[-4] = a >>> a == b == 32 and a is not b True >>> type(a) == type(b) == int True >>> (32 + 10) == 32 True
id(b) возвращает адрес объекта в памяти, представляющего 42, в CPython sys.getsizeof(b) возвращает размер объекта b в байтах (ctypes.c_ubyte * sys.getsizeof(b)) задаёт тип массива байтов, который может вместить b объект (unsigned char[28]) .from_address() создаёт массив из памяти, начиная с адреса, где b объект расположен arr[-4] = a присваивает четвёртому с конца байту значение 32. Что имеет эффект изменения константы 42 (связано с тем как _longobject в памяти представлен (единственная цифра/digit меняется), в частности это говорит, что python3 команда использует PYLONG_BITS_IN_DIGIT == 30 , что sys.int_info подтверждает и порядок байтов от младшего к старшему на моей машине, что sys.byteorder также подтверждает).
Код был вдохновлён статьёй: «...Как избавиться от коллеги, пишущего на Питоне».

Присваивание целых (int) в Питоне

Допустим, есть следующий код:
a = 32 b = a
print(a is b)
Как сделать так, чтобы b становилось ссылкой на новое значение в памяти, а не на то значение, на которое ссылается a, т.е. как сделать так, чтобы операция print(a is b) напечатала False?


Ответ

b = a заставляет b имя ссылаться на тот же объект что и a имя.
Операция a is b проверяет указывают ли оба имени на один и тот же объект, поэтому всегда всегда a is b будет True после a = b в Питоне.
Если вы хотите, чтобы имя b ссылалось на объект отличный от a, то просто присвойте любой другой объект, например, b = None для примера в вопросе или можно вообще удалить имя del b (при попытке использования после этого исключение возникнет).

Если вы хотите, чтобы a и b были равны (a == b == 32), но были бы разными объектами (a is not b), то ответ может зависеть от конкретной реализации Питона.
Например, в CPython маленькие целые числа кэшируются поэтому 32 is (16*2) is (31+1)... то есть существует только один объект, который представляет число 32 в СPython. Целые числа неизменяемы в Питоне, поэтому арифметика не изменится от того один и тот же объект для 32 возвращается каждый раз или разные.
Нужно обратить внимание, что не следует оператор is использовать для сравнения чисел. Так как разные объекты могут иметь одно значение (быть равными), например, 1000 == (999+1), но 1000 is not (999+1) (в данном случае (999+1) возвращает новый объект, отличный от объекта, созданного для 1000 константы). is можно использовать для сравнения с singleton—объект, который в единственном экземпляре существует, такой как None. Питон не обещает, что объекты, представляющие числа, существуют в единственном экземпляре, поэтому используйте == для сравнения целых чисел.

Есть несколько решений буквальной задачи (a == b == 32 and a is not b), если её рассматривать как загадку (не для использования на практике).
Чтобы обойти кэширование маленьких чисел, можно создать производный от int класс:
>>> a = 32 >>> class Int(int): ... pass ... >>> b = Int(a) >>> a == b == 32 and a is not b True
Или использовать float или любой другой тип, который можно с целыми сравнивать:
>>> b = 32. >>> a == b == 32 and a is not b True
Наконец, если вы хотите, чтобы type(a) == type(b) == int, где int это встроенный PyLongObject тип, то возможно сделать злую вещь и поменять значение известной константы 42
>>> import ctypes >>> import sys >>> a = 32 >>> b = 42 >>> (ctypes.c_ubyte * sys.getsizeof(b)).from_address(id(b))[-4] = a >>> a == b == 32 and a is not b True >>> type(a) == type(b) == int True >>> (32 + 10) == 32 True
id(b) возвращает адрес объекта в памяти, представляющего 42, в CPython sys.getsizeof(b) возвращает размер объекта b в байтах (ctypes.c_ubyte * sys.getsizeof(b)) задаёт тип массива байтов, который может вместить b объект (unsigned char[28]) .from_address() создаёт массив из памяти, начиная с адреса, где b объект расположен arr[-4] = a присваивает четвёртому с конца байту значение 32. Что имеет эффект изменения константы 42 (связано с тем как _longobject в памяти представлен (единственная цифра/digit меняется), в частности это говорит, что python3 команда использует PYLONG_BITS_IN_DIGIT == 30 , что sys.int_info подтверждает и порядок байтов от младшего к старшему на моей машине, что sys.byteorder также подтверждает).
Код был вдохновлён статьёй: «...Как избавиться от коллеги, пишущего на Питоне».

среда, 3 октября 2018 г.

Что такое объект в Python. Почему id(a) == id(b) == id(1)?

Детализирую вопрос. В книгах по Python пишут, что все в Python есть объект.
Берем традиционный подход к изучению/обучению языкам программирования. Языки Pascal, C, C++, у них есть понятие "имя переменной" (идентификатор). С именем переменной связывается/присваивается значение. Значение хранится в памяти. Имя переменной - это способ (в исходном тексте программы) обратиться к ячейке памяти для получения значения, которое там хранится.
Не знаю верно ли высказывание (для компилируемых языков): Для переменных, например, целого типа, в момент выполнения программы на компьютере, процессор работает уже с адресами ячеек памяти, где хранятся значения переменных. Т.е. не такого, что где-то по какому-то адресу хранится имя переменной и оно связывается с адресом ячейки, в которой хранится значение.
Теперь переходим к Python. В этом языке все является объектом, даже определение функции. Переменная объект, значение объект и т.д. В разное время с одним и тем же объектом может соотноситься разный другой объект.
a = 1 print(a) a = "Hello" print(a)
Но тогда как понять действие функции id()? По определению стандарта она возвращает "identify" указанного объекта. Причем, "возвращает целое, гарантированно являющееся уникальным" и постоянным для объекта на время его существования."
Тогда почему
a = 1 b = 1 print(id(a) == id(b) == id(1)) # True print("Why?")
ведь a, b, 1 разные объекты? По-моему вопрос получился...
Используется Python 3.6

Про значения от -5 до 256 наслышан. Имеет место быть
a = 1000000 b = 1000000 print(id(a) == id(b)) # True print("Why?")
На счет того, что достаточно "знать об именах и т.д.". Для меня не достаточно, потому и спрашиваю у уважаемых профи. В Питоне все объект. Нет имен переменных. Отсюда и мой вопрос.


Ответ

В питоне интерпретатор оптимизирован так, что небольшие целые числа представлены одним объектом - это сделано в целях улучшения производительности. Для больших чисел это уже не выполняется.
Марк Лутц - Изучаем Python, 4-е издание, 2011, стр. 204:
>>> X = 42 >>> Y = 42 # Должно получиться два разных объекта >>> X == Y True
>>> X is Y # Тот же самый объект: кэширование в действии! True В этом примере переменные X и Y должны быть равны (==, одно и то же значение), но не эквивалентны (is, один и тот же объект), потому что было выполнено два разных литеральных выражения. Однако из-за того, что малые целые числа и строки кэшируются и используются повторно, оператор is сообщает, что переменные ссылаются на один и тот же объект.