Python: потоки по-другому

от автора

Введение

Знаете, почему я решил написать эту статью? Я писал программу, где использовал потоки. Во время работы с ними в 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

Итак, на этом и завершу эту статью. Спасибо за внимание!

GitHub


ссылка на оригинал статьи https://habr.com/ru/post/659935/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *