LINUX.ORG.RU

python ограничить область видимости для import (или шаринг переменных между модулями)

 , ,


1

1

Дано: a.py - главный модуль b.py - движок shared.py - файл с переменными

Вот так сейчас выглядит код: a.py:

import b
import shared

shared.shared_var = 1

b.engine()

b.py

import shared


def engine():
    print("работаем с: {}".format(shared.shared_var))

shared.py

shared_var = 0

В чём проблема: a.py вызывается многопоточно, и в итоге начинается каша - переменные внутри shared.py становятся общими для всех потоков.

Мне надо так сделать чтобы у каждого потока была своя область видимости.

Что я пробовал: - Делать import внутри функции def, итог - всё равно тоже самое - Делать не import, а from shared import * - так возникает геморой с тем что вызывая это в a.py я не могу в b.py передать контекст - Тупо всё в функции сувать (все переменные), в итоге на каждый чих мне надо портянку переменных сувать, код раздувается

В общем очень надо сделать так чтобы функции отдельно, а переменные отдельно, и у каждого потока своя область видимости переменных.

PS: вот это уже читал http://ru.stackoverflow.com/questions/358/Глобальные-переменные-в-python



Последнее исправление: rubro (всего исправлений: 1)
Ответ на: комментарий от rubro

Если не то объяснил - дайте знать пожалуйста, по другому напишу.

всё идеально работает когда код в 1 месте лежал, а как начал делить на движок и модуль который ему задание подкидывает - возникла эта проблема с потоками

напиши минимальный нерабочий пример этой ситуации

anonymous
()
Ответ на: комментарий от rubro
from queue import Queue
from threading import Thread

class Worker:
    def __init__(self, task_queue):
        self.task_queue = task_queue
        self.tasks_processed = 0

    def run(self):
        while self.task_queue:
            task = self.task_queue.get()
            task.run(self)
            self.tasks_processed += 1
            self.task_queue.task_done()

class Task:
    def run(self, worker):
        print("Рабочий {} обработал {} задач" \
              .format(id(worker), worker.tasks_processed))

q_src = Queue() #У неё по умолчанию maxsize=0
num_threads = 10

# запуск потоков
for i in range(num_threads):
    worker = Worker(q_src)
    thread = Thread(target=worker.run)
    thread.setDaemon(True)  #Если оно тебе надо. Можно было бы заставлять Worker'ы завершаться.
    thread.start()

# закидываем задания на исполнение
for x in range(50):
    q_src.put(Task())

q_src.join()
print("работа выполнена")

Можно ещё было сделать Worker подклассом Thread, если так проще.

proud_anon ★★★★★
()
Ответ на: комментарий от trashymichael

Принципиальное отличие в том, что я не могу юзать глобальные переменные, так как нет такого же удобного доступа к состоянию программы

rubro
() автор топика
Ответ на: комментарий от anonymous

Сейчас постараюсь всё подготовить

rubro
() автор топика
Ответ на: комментарий от rubro

Большое спасибо вам за пример! Сейчас осмыслю и спрошу детали

Я сейчас работать пойду, детали пусть кто-нибудь другой объясняет (или вечером)

proud_anon ★★★★★
()
Ответ на: комментарий от proud_anon

Я пока что так и не смог врубиться как это работает и как это решает проблему( Ладно мне тоже сейчас пора в оффлайн, огромное вам спасибо за помощь, завтра продолжу изучение

Ребят если кто вопросы задавать будет - сегодня ответить уже не смогу, спасибо всем кто помогает! Я ценю вашу поддержку

rubro
() автор топика

По дизайну тут уже предложений достаточно, а вод про сам подход я бы посоветовал не использовать в данной задаче потоки, а заменить их на коротины (gevent или встроенные asyncio). Получиш небольшой прирост по скорости (оверхед на переключение меньше, чем у потоков) и самое главное - ты точно будеш знать в какой момент происходит передача управления от одного «воркера» к другому. В даном случае это будет происходить только при сетевом взаимодействии, и получивший управление воркер не передаст его никому, пока ты опять явно не вызовеш работу с сетью либо sleep. Сейчас же в случае потоков питон сам решает, когда переключать контекст, отсюда и необходимость следить за гонкой и доступом к общим переменным.

Извиняюсь за ощибки если они есть, первый раз пишу чтото длинное с планшета

genryRar ★★
()
Ответ на: комментарий от genryRar

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

rubro
() автор топика
Ответ на: комментарий от anonymous

Имеем код:

main.py:

from queue import *
from threading import Thread, local
import engine


def doo2():
    # импортируем общие переменные
    import shared
    # в общую переменную закидываем текущий таск
    shared.task = q_src.get()
    # вызываем движок, без параметров (так задумано)
    engine.engine()
    q_src.task_done()


q_src = Queue()
num_threads = 10


# функция обработки потоков
def do_stuff(q_src):
    while True:
        doo2()


# запуск потоков
for i in range(num_threads):
    worker = Thread(target=do_stuff, args=(q_src,))
    worker.setDaemon(True)
    worker.start()

# закидываем задания на исполнение
for x in range(5):
    q_src.put("task_{}".format(str(x)))

q_src.join()
print("работа выполнена")

engine.py

import shared
def engine():
    # смотрим общую переменную на старте
    before = shared.task_status
    # присваиваем ей 1
    shared.task_status = 1
    # смотрим что вышло
    after = shared.task_status
    print("task: {}\t"
          "before: {}\t"
          "after: {}".format(shared.task, before, after))

shared.py

task_status = 0
task = None

Стартуем, получаем вот это:

task: task_0	before: 0	after: 1
task: task_1	before: 1	after: 1
task: task_2	before: 1	after: 1
task: task_3	before: 1	after: 1
task: task_4	before: 1	after: 1
работа выполнена

А надо чтобы всегда получалось вот это:

task: task_0	before: 0	after: 1
task: task_1	before: 0	after: 1
task: task_2	before: 0	after: 1
task: task_3	before: 0	after: 1
task: task_4	before: 0	after: 1
работа выполнена

Другими словами суть задачи: каждый поток на старте должен взять дефолтные значения всех переменных из shared.py, затем после их изменения у каждого потока должны быть свои значения

И суть проблемы: сейчас значения переменных (из shared.py) общие для всех потоков

rubro
() автор топика
Ответ на: комментарий от proud_anon

Я ваш пример немного переделал чтобы мне легче был понять (в ООП полный 0), теперь код вот так выглядит

main.py

from queue import Queue
from threading import Thread
from a import do

class Worker:
    def __init__(self, task_queue):
        self.task_queue = task_queue

    def run(self):
        while self.task_queue:
            task = self.task_queue.get()
            # print(task.cur)
            do(task.cur) # вот как мне task.cur видеть внутри a.py? при этом НЕ передавая его в "do" функцию
            self.task_queue.task_done()

class Task:
    def __init__(self, cur):
        self.cur = cur

q_src = Queue()
num_threads = 10

# запуск потоков
for i in range(num_threads):
    worker = Worker(q_src)
    thread = Thread(target=worker.run)
    thread.setDaemon(True)  #Если оно тебе надо. Можно было бы заставлять Worker'ы завершаться.
    thread.start()

# закидываем задания на исполнение
for x in range(50):
    q_src.put(Task(x))

q_src.join()
print("работа выполнена")

a.py:

def do(var):
    x = "[task: {}]".format(var)
    print(x)

Сам вопрос в строке 13 файла main.py, продублирую его здесь. Сейчас я засунул в do() функцию аргумент и таким образом она увидела таск, а мне надо чтобы весь файл a.py его видел. Если я захочу к файлу a.py подключить импортом b.py то там тоже нужно видеть эту переменную, и если где-то я её поменяю - изменения должны увидеть все - и main.py, и a.py, и b.py

rubro
() автор топика
Ответ на: комментарий от trashymichael

Подскажите пожалуйста, можно имя потока новое установить? Туда бы UUID или что-то такое, а то получается надо после работы потока затирать старое значение обязательно, так как 1 поток обслуживает более 1 задания

rubro
() автор топика
Ответ на: комментарий от trashymichael

Во спасибо. Сейчас пробую сделать по вашему совету - shared.py внутри него словарик и класс с дефолтными переменным, его все модули импортируют и обращаются по ключу - имя потока (слава богу как тут не импортируй а это имя везде одинаковое у каждого потока), значение ключа - это объект (класс который я создал).

Поток берёт задание, создаёт в словаре запись с именем потока и без параметров вызывает функции.

Я надеюсь проблем у меня не будет от того что много потоков ломятся в один словарь туда сюда там читать/писать/удалять. Если вдруг будут - то у меня большие неприятности.

rubro
() автор топика
Ответ на: комментарий от rubro

а это имя везде одинаковое у каждого потока

я хотел сказать что у каждого потока своё имя, и оно не меняется в зависимости от импортов

rubro
() автор топика
Ответ на: комментарий от rubro

Тред полон тупизны чуть более, чем полностью, это какой-то ад. XY во весь рост. ТС, может ты признаешься какая первоначальная задача?

Ты в shared.py вынес сцепленные сущности (причем локальные для потока), зачем, во имя всего святого?

anonymous
()
Ответ на: комментарий от genryRar

Извиняюсь за ощибки если они есть

Конечно есть.

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

anonymous
()
Ответ на: комментарий от anonymous

ТС, может ты признаешься какая первоначальная задача?

Конечно, я её вот тут описывал: python ограничить область видимости для import (или шаринг переменных между модулями) (комментарий)

Ты в shared.py вынес сцепленные сущности (причем локальные для потока), зачем, во имя всего святого?

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

rubro
() автор топика
Ответ на: комментарий от anonymous

Если образно задачу описать, то мне надо чтобы функции вызывали друг друга по цепочке, но данные должны быть рядом сбоку, чтобы каждая функция могла с ними работать не принимая ничего во входных аргументах и не возвращая ничего в выходных.

Это прекрасно работает когда мы НЕ используем модули и многопоточность, когда используем - начинается геморой, импорт получается один на все потоки, а не у каждого свой.

rubro
() автор топика
Ответ на: комментарий от rubro

main.py:

from shared import Shared
from queue import *
from threading import Thread, local
from engine import engine

def doo2():
    engine(q_src.get())
    q_src.task_done()

q_src = Queue()
num_threads = 10

# функция обработки потоков
def do_stuff(q_src):
    while True:
        doo2()

# запуск потоков
for i in range(num_threads):
    worker = Thread(target=do_stuff, args=(q_src,))
    worker.setDaemon(True)
    worker.start()

# закидываем задания на исполнение
for x in range(5):
    q_src.put(Shared(x))

q_src.join()
print("работа выполнена")

engine.py

def engine(shared):
    # смотрим общую переменную на старте
    before = shared.task_status
    # присваиваем ей 1
    shared.task_status = 1
    # смотрим что вышло
    after = shared.task_status
    print("task: {}\t"
          "before: {}\t"
          "after: {}".format(shared.task, before, after))

shared.py

class Shared(object):
    task_status = 0
    task = None
    def __init__(self, x):
        self.name = 'task_{}'.format(x)

anonymous
()
Ответ на: комментарий от anonymous

Большое спасибо за поддержку! Вот только в этом вся и фишка, в том чтобы не передавать ничего внуть функции (def engine(shared)). Почему именно так? Причина в том что engine.py дёргает функции как из самого main.py так и из кучи других модулей, и на каждый пук туда сюда таскать за собой объект Shared и везде его возвращать через return - это говнокод.

rubro
() автор топика
Ответ на: комментарий от anonymous

Мне надо чтобы функции друг друга вызывали по цепочки, но с данными работали так будто они рядом лежат - бери что хочешь и меняй, и все другие функции видят эти изменения.

Если делать как вы описали через подачу входных аргументов функции - у меня начинаются проблемы с тем что если кто-то что-то поменял в функции - надо идти и передавать туда сюда результат изменения, если не передал - значит изменения остались локальными внутри функции, а я хочу в функции установить значение и пусть все другие видят это, так гораздо проще работать.

rubro
() автор топика
Ответ на: комментарий от rubro

Не используй глобальные переменные. Используй явную передачу параметров между функциями или используй классы.

PolarFox ★★★★★
()
Ответ на: комментарий от rubro

бери что хочешь и меняй, и все другие функции видят эти изменения

Когда в питоне возникает такое желание, обычно делается класс.

PolarFox ★★★★★
()
Ответ на: комментарий от PolarFox

Спасибо за советы, а как это на практике выглядит?

Вот допустим я в main.py создал класс (внутри которого нужные мне переменные), дальше как там видеть будут модули этот класс если я его не передаю во входных агрументах функций? (и не хочу передавать)

rubro
() автор топика
Ответ на: комментарий от rubro

В треде создаёшь экземпляр класса и работаешь с ним. Функции работающие с данными класса должны быть либо методами в классе, либо принимать экземпляр класса как параметр.

PolarFox ★★★★★
()
Ответ на: комментарий от rubro

и на каждый пук туда сюда таскать за собой объект Shared и везде его возвращать через return - это говнокод.

Блжад, у тебя сцепление жесткое, как раз твой подход, это эталонный говнокод.

anonymous
()
Ответ на: комментарий от anonymous

зачем ты так переживаешь, всему свое время

anonymous
()
Ответ на: комментарий от anonymous

Блжад, у тебя сцепление жесткое, как раз твой подход, это эталонный говнокод.

Это правда.

proud_anon ★★★★★
()

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

Отдельное спасибо камрадам: @trashymichael - увы не сработал словарь, большой оверхед и данные путаются, но всё равно спасибо @proud_anon - спасибо за примеры! я правда нуб, и поэтому ничего не понял, но как-нибудь пойму, настанет время :) @genryRar - спасибо что не поленились с планшета написать :) я увы не умею пока что асинхронные дела творить @anonymous (29.07.2016 12:18:01) - спасибо анон, я хотел оттуда убежать, но пришлось вернуться @PolarFox - спасибо, как раз передаю класс во входных аргументах @anonymous (29.07.2016 18:25:49) - спасибо анон! вы были последней каплей, я понял что был неправ и всё переписал!

rubro
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.