Найти - Пользователи
Полная версия: Объект в потоке
Начало » Python для новичков » Объект в потоке
1 2 3
Kogrom
Вероятно, у меня проблема в том, что не могу правильно задать строку для поиска ибо вещь вроде бы очевидная.

В документации приводится поток как функция. А мне бы надо, чтобы некий объект работал параллельно системе, но получал от неё команды. Проще говоря, любой метод такого объекта должен выполняться в потоке. Но создавать для каждого метода свой поток неудобно - они будут не согласованы.

Чтобы было понятнее, наворотил такой “велосипед” (редакция 2):

import threading
import time


class ThreadClass:

class FuncObject:
def __init__(self):
self.clear()

def clear(self):
self.func = None
self.args = []
self.kwargs = {}
self.changed = False

def __init__(self, cls):
self.enable = True
self._funcObject = self.FuncObject()
self.event = threading.Event()
self.event.clear()
thread = threading.Thread(target=self._ret_func, args=(cls,))
thread.start()

def set_func(self, func, args=[], kwargs={}):
self._funcObject.func = func
self._funcObject.args = args
self._funcObject.kwargs = kwargs
self._funcObject.changed = True
self.event.set()

def _ret_func(self, cls):
obj = cls()
while(self.enable):

self.event.wait(10)
if self.event.is_set():
self.event.clear()

if self.enable == False:
break

if self._funcObject.changed:
if self._funcObject.func in cls.__dict__:
if self._funcObject.args:
cls.__dict__[self._funcObject.func](obj, *self._funcObject.args)
elif self._funcObject.kwargs:
cls.__dict__[self._funcObject.func](obj, **self._funcObject.kwargs)
else:
cls.__dict__[self._funcObject.func](obj)

self._funcObject.clear()



def stop(self):
self.enable = False
self.event.set()

if __name__ == '__main__':

class TestClass:
def a_func(self):
print "a_func"

def b_func(self, data):
print "b_func", data


a = ThreadClass(TestClass)
b = ThreadClass(TestClass)
for i in xrange(5):
a.set_func("a_func")
b.set_func("a_func")
time.sleep(0.1)
a.set_func("b_func", ("args test a: %s"%i,))
b.set_func("b_func", ("args test b",))
time.sleep(0.1)
a.set_func("b_func", kwargs={"data": "kwargs test a"})
b.set_func("b_func", kwargs={"data": "kwargs test b"})
time.sleep(0.1)
a.stop()
b.stop()
Однако в “велосипеде” есть несколько недостатков, которые я не довёл до ума, так как предполагаю, что есть готовое решение. Может кто-нибудь знает как это делается по грамотному?

Добавлено позже: Так как ответа не нашел, то доработал это (внёс события) и пока доволен. Пока не дойдёт до GUI…
Kogrom
В общем, получил советы и примеры от умного человека с ником Zart. Предложил он примерно такую заготовку:

from Queue import Queue
from threading import Thread
from collections import Callable

class ThreadProxy(object):

def __init__(self, obj):
self.__obj = obj
self.__requests = Queue()
self.__worker = Thread(target=self.__run)
self.__worker.start()

def stop(self):
self.__requests.put(None)
self.__worker.join()

def __run(self):
while True:
req = self.__requests.get()
if req is None:
self.__requests.task_done()
break
func, args, kw = req
func(*args, **kw)
self.__requests.task_done()

def __getattr__(self, func):
attr = getattr(self.__obj, func)

def _proxy(*args, **kw):
self.__requests.put((attr, args, kw))

if isinstance(attr, Callable):
return _proxy
else:
return attr

if __name__ == '__main__':

import time

class TestClass:

def __init__(self):
self.data = "data"

def a_func(self):
print "a_func"

def b_func(self, data):
print "b_func", data

a = ThreadProxy(TestClass())
b = ThreadProxy(TestClass())

for i in xrange(5):
a.a_func()
b.a_func()
time.sleep(0.1)
a.b_func("args test a: %s" % i)
b.b_func("args test b")
time.sleep(0.1)
a.b_func(data="kwargs test a")
b.b_func(data="kwargs test b")
time.sleep(0.1)
print a.data

a.stop()
b.stop()
В общем, это намного лучше моего варианта: удобнее и, скорее всего, надёжнее. Ещё советовал вместо stop использовать __del__, но, думаю, тут он погорячился.
Kogrom
Ещё вариант. Практически без моих правок (отступы не в счёт):

from Queue import Queue
from threading import Thread

class ThreadProxyClass(object):
def __init__(self, obj):
self.__obj = obj
self.__requests = Queue()
self.__worker = Thread(target=self.__run)
self.__worker.daemon = True # argh...
self.__worker.start()
def stop(self):
self.__requests.put(None)
self.__worker.join()
def __run(self):
while True:
req = self.__requests.get()
try:
if req is None: break
func, args, kw = req
func(*args, **kw)
except:
pass
finally:
self.__requests.task_done()
def __getattr__(self, key):
attr = getattr(self.__obj, key)
if not callable(attr): return attr
def _proxy(*args, **kw):
self.__requests.put((attr, args, kw))
return _proxy
def __setattr__(self, key, val):
if key.startswith('_' + self.__class__.__name__):
return object.__setattr__(self, key, val)
return setattr(self.__obj, key, val)
def __delattr__(self, key):
if key.startswith('_' + self.__class__.__name__):
return object.__delattr__(self, key)
return delattr(self.__obj, key)

if __name__ == '__main__':
import time

class TestClass:
def a_func(self):
print "a_func"

def b_func(self, data):
print "b_func", data

a = ThreadProxyClass(TestClass())
b = ThreadProxyClass(TestClass())

for i in xrange(5):
a.a_func()
b.a_func()
time.sleep(0.1)
a.b_func("args test a: %s" % i)
b.b_func("args test b")
time.sleep(0.1)
a.b_func(data="kwargs test a")
b.b_func(data="kwargs test b")
time.sleep(0.1)
a.stop()
b.stop()
Ed
А зачем нужны все эти страшные методы с двумя подчеркиваниями? Почему просто не передать метод и параметры в функцию?
Ну как-то так, например:
class ThreadProxy(object):
def __init__(self):
self._requests = Queue()
self._worker = Thread(target=self._run)
self._worker.daemon = True # argh...
self._worker.start()

def run(self, fun, *args, **kw):
self._requests.put((fun, args, kw))

def stop(self):
self._requests.put(None)
self._worker.join()

def _run(self):
while True:
req = self._requests.get()
if req:
func, args, kwargs = req
func(*args, **kwargs)
else:
break

if __name__ == '__main__':
...
testa = TestClass()
a = ThreadProxy()
a.run(testa.b_func, data="kwargs test a")
По-моему гораздо понятнее что происходит.

По поводу вашего кода:
Атрибуты с двумя подчеркиваниями - нехорошо.
За try: except: pass обычно бьют по рукам.
Kogrom
Ed
По-моему гораздо понятнее что происходит.
Нет. Например, у нас есть определённый объект (который выбран для потока), и взаимодействующие с ним другие объекты, но пока всё работает в одном потоке. В вашем случае для перехода к многопоточности придётся перелопачивать весь код. В моём - только сам объект обернуть и можно работать почти как в одном потоке.

Ed
Атрибуты с двумя подчеркиваниями - нехорошо.
За try: except: pass обычно бьют по рукам.
Последний “мопед” не мой, но так как я пытался вникнуть, то отвечу.

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

То, что не ловится конкретное исключение мне и самому не нравится. Как, в общем, и само использование исключений. Возможно, тут нужен AttributeError или NameError.

Теперь совет от автора. То, что обозначили поток фоновым (self.__worker.daemon = True) может позволить заменить .stop, на __del__. Так мы сможем избежать перекрытия атрибута .stop встраиваемого объекта.

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

Мне ещё не нравится, что для окончания цикла передаётся конкретное None. Возможно, лучше заложить какой-то более абстрактный флаг, который, например, будет атрибутом класса-обёртки.
Kogrom
Kogrom
То, что не ловится конкретное исключение мне и самому не нравится. Как, в общем, и само использование исключений. Возможно, тут нужен AttributeError или NameError.
Наверное, ловить исключение надо, но при этом что-то выводить в поток ошибок. Как-то так.
nerijus
Для решения таких задач, нужно создать thread pool и запихивать туда задачи (функции). А то что ты здесь пытаешься намудрить, очень похоже на отсутствие понятия что такое thread вообще. Вот посмотри пример: http://code.activestate.com/recipes/577187-python-thread-pool/ и перестань морочить себе голову со всякими враперами.
Ed
Kogrom
В вашем случае для перехода к многопоточности придётся перелопачивать весь код. В моём - только сам объект обернуть и можно работать почти как в одном потоке.
Может и так. Просто ваши первые варианты тоже были без __getattr__, поэтому я и подумал, что это лишнее. Кстати, по-моему для вызова только __getattr__ переопределить достаточно. Зачем еще __setattr__ и __delattr__ переопределяются непонятно.

Атрибуты с двумя подчеркиваниями с левой стороны - это приватные атрибуты. Использование их - нормальная практика.
Почитайте PEP8 на досуге, помогает :)
Атрибуты с двумя подчеркиваниями рекомендуется использовать для атрибутов, которые хочется скрыть от доступа классов-наследников . Для обычных не публичных атрибутов используется одно подчеркивание слева.

То, что не ловится конкретное исключение мне и самому не нравится. Как, в общем, и само использование исключений. Возможно, тут нужен AttributeError или NameError.
Не нравится - это слишком легко сказано. Таким подходом закрываются любые ошибки в таким образом вызываемых функциях. Попробуйте в вашей функции написать какую-нибудь чушь, скажем bla-bla-bla и запустить. Разницы не заметите, хотя она банально не будет работать. И как такое отлаживать?

Теперь совет от автора.
Код выглядит очень непросто, советы тоже. Ни к чему это, по-моему. Питон - простой язык, незачем его искусственно усложнять.

Мне ещё не нравится, что для окончания цикла передаётся конкретное None. Возможно, лучше заложить какой-то более абстрактный флаг, который, например, будет атрибутом класса-обёртки.
Согласен. Даже ‘stop’ выглядело бы более читабельно.
Ed
nerijus
Для решения таких задач, нужно создать thread pool и запихивать туда задачи (функции). А то что ты здесь пытаешься намудрить, очень похоже на отсутствие понятия что такое thread вообще. Вот посмотри пример: http://code.activestate.com/recipes/577187-python-thread-pool/ и перестань морочить себе голову со всякими враперами.
Это немного не то. Ему нужна прозрачная запускалка, а с пулом так не выйдет.
Хотя понятно, что запускалка фактически однотредовая, в отличие от пула, но почитавши я решил, что ему этого достаточно.
nerijus
Ed
Это немного не то. Ему нужна прозрачная запускалка, а с пулом так не выйдет.
Или ненужна, но просто придумал из за отсутствия опыта как делать правильно.

Ed
Хотя понятно, что запускалка фактически однотредовая, в отличие от пула, но почитавши я решил, что ему этого достаточно.
Пул здесь только для безопасности, да бы не запустить 1000 раз медленные методы и не сожрать всю память. Вы уж меня простите но запускать каждый метод в новом потоке это просто кошмар. Такого я еще нигде не видел (слава богу).
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB