
Введение
Знаете, почему я решил написать эту статью? Я писал программу, где использовал потоки. Во время работы с ними в Python всё больше убеждаешь себя, что тут с ними всё плохо. Нет, не то, чтобы они плохо работали. Просто использовать их, мягко говоря, неудобно. Я решил написать простую, но более удобную библиотеку, и здесь поделюсь процессом.
P.S.: В конце оставлю ссылку на GitHub
Первые шаги
Первое, с чего я решил начать — это удобное создание потоков из функций. Для этого я написал простенький декоратор, выглядит он так:
# thr.py from threading import Thread as thrd __all__ = ['Thr'] # Класс для всего содержимого либы class Thr: # Собственно, декоратор def thread(fn): def thr(*args, **kwargs): thrd(target = fn, args = (*args,), kwargs={**kwargs,}).start() pass return thr pass # конец файла
Вот и всё. Теперь можно очень удобно создавать потоки:
from thr import Thr # пример использования @Thr.thread def func1(a, b, c): if a+b > c: if a+c > b: if b+c > a: print("треугольник существует") pass pass pass print("треугольник не существует") pass # возвращение значений пока не предусмотрено for a, b, c in zip(range(1, 10), range(6, 15), range(11, 20)): func1(a, b, c) pass
Удобнее чем было, так ведь? Но мне стало мало.
Среды для потоков
Мне стало не удобно обеспечивать верное взаимодействий потоков. Например, мне надо чтобы 2 цикла решали последовательности чисел для 3n+1. Тогда, мне приходилось мучить Python глобальными переменными. И я нашел выход!
P.S.: Возможно, это и есть ThreadPoolExecutor, которым я никогда не пользовался. Если это так, то, возможно, просто кому-то мой метод покажется удобнее.
Давайте объясню. Вы создаёте среду для потоков(можно несколько) и немного адаптируете потоковые функции под себя. код библиотеки:
# thr.py from curses.ascii import isalnum from threading import Thread as thrd from random import randrange from sys import exit as exitall __all__ = ['Thr'] # f-str не позволяет использовать "\" напрямую, # пришлось выкручиваться =) nl = "\n" bs = "\b" tb = "\t" rt = "\r" # просто полезная функция def strcleanup(s: str = ""): while s[0] == ' ': s = s[1:] while s[-1] == ' ': s = s[:-1] if not isalnum(s[0]): s = '_' + s s = s.replace(' ', '_') for i in range(len(s)): if not isalnum(s[i]): s = s.replace(s[i], '_') pass pass s += f"{randrange(100, 999)}" return s # Класс для всего содержимого либы class Thr: # класс для сред потоков class Env(object): # поля # потоки thrs: list = None # возвращаемые значения rets: dict = None # название среды name: str = None # методы # инициализация def __init__(self, name): self.thrs = [] self.rets = {} self.__name__ = self.name = name # self.name на всякий случай. # __name__ - магическая переменная, вдруг поменяется. pass # в строку __str__ = lambda self:\ f"""ThreadSpace "{self.name}": {len(self.thrs)} threads""" # тоже в строку, но скорее для дебага, чем для печати юзеру __repr__ = lambda self:\ f"""ThreadSpace "{self.name}" threads: {(nl+" ").join(self.thrs)} total: {len(self.thrs)} """ def __add__(self, other): self.thrs = {**self.thrs, **other.thrs} pass # Декоратор/метод для добавления в список потоков. def append(self, fn): # функции нужен docstring ID = strcleanup(fn.__doc__.casefold()) self.thrs += [ID] self.rets[ID] = None # class Thrd(object): ID = None space = None fn = None thr = None runned = None ret = None def __init__(slf, ID, self, fn): slf.ID = ID slf.space = self slf.fn = fn slf.thr = None slf.runned = False slf.ret = False pass def run(slf, *args): if slf.runned: print(f"Exception: Thread \"{slf.ID[:-3]}\" of threadspace \"{slf.space.name}\" already started") exitall(1) pass slf.thr = thrd(target = slf.fn, args = (slf, slf.space, slf.ID, *args,)) slf.thr.start() slf.runned = True pass def join(slf): if not slf.runned: print(f"Exception: Thread \"{slf.ID[:-3]}\" of threadspace \"{slf.space.name}\" not started yet") exitall(1) pass slf.thr.join() slf.runned = False pass def get(slf): if not slf.ret: print(f"Exception: Thread \"{slf.ID[:-3]}\" of threadspace \"{slf.space.name}\" didn`t return anything yet") exitall(1) pass slf.runned = False return slf.space.rets[slf.ID] def getrun(slf, *args): slf.run(*args) slf.join() return slf.get() pass return Thrd(ID, self, fn) pass # Декоратор для "голого" потока def thread(fn): def thr(*args, **kwargs): thrd(target = fn, args = (*args,), kwargs={**kwargs,}).start() pass return thr pass # конец файла
Пример вывода об ошибке:
Traceback (most recent call last): File "test.py", line 37, in <module> loop.run() File "thr.py", line 93, in run raise Exception(...) Exception: Thread "3n_1_mainloop" of threadspace "3n+1" already started
Как быстро вырос объём кода по сравнению с предыдущим вариантом! Итак, пример использования:
from random import randint from thr import Thr Space = Thr.Env("3n+1") @Space.append def hdl(t, spc, ID, num): """3n+1_handle""" if num % 2 == 0: # значения возвращать так t.ret = True spc.rets[ID] = num/2 return # значения возвращать так t.ret = True spc.rets[ID] = 3*num+1 return @Space.append def loop(t, spc, ID, num): """3n+1_mainloop""" steps = 0 while num not in (4, 2, 1): num = hdl.getrun(num) steps += 1 pass # значения возвращать так t.ret = True spc.rets[ID] = steps return print() print(Space) print() print(repr(Space)) ticks = 0 num = randint(5, 100) loop.run(num) while not loop.ret: ticks += 1 pass print(f"loop reached 4 -> 2 -> 1 trap,\n" f"time has passed (loop ticks):\n" f"{ticks}, steps has passed: {loop.get()}, start number: {num}")
Вывод:
ThreadSpace "3n+1": 2 threads ThreadSpace "3n+1" threads: 3n_1_handle840 3n_1_mainloop515 total: 2 loop reached 4 -> 2 -> 1 trap, time has passed (loop ticks): 13606839, steps has passed: 33, start number: 78
Итак, на этом и завершу эту статью. Спасибо за внимание!
ссылка на оригинал статьи https://habr.com/ru/post/659935/
Добавить комментарий