与长期运行的python脚本交互

时间:2014-06-18 02:35:04

标签: python

我有一个长期运行的Python脚本,它从Twitter收集推文,我想知道它每隔一段时间做一次。

目前,我使用signal库来捕获中断,此时我调用了print函数。像这样:

import signal

def print_info(count):
    print "#Tweets:", count

#Print out the process ID so I can interrupt it for info
print 'PID:', os.getpid()

#Start listening for interrupts
signal.signal(signal.SIGUSR1, functools.partial(print_info, tweet_count))

每当我想要我的信息时,我会打开一个新的终端并发出我的中断:

$kill -USR1 <pid>

有更好的方法吗?我知道我可以按计划的时间间隔使用我的脚本,但我更感兴趣的是根据需要知道,并且可能也会发布其他命令。

5 个答案:

答案 0 :(得分:2)

向进程发送信号会中断进程。下面你会发现一种使用专用线程来模拟python控制台的方法。控制台暴露为unix插槽。

import traceback
import importlib
from code import InteractiveConsole
import sys
import socket
import os
import threading
from logging import getLogger

# template used to generate file name
SOCK_FILE_TEMPLATE = '%(dir)s/%(prefix)s-%(pid)d.socket'

log = getLogger(__name__)


class SocketConsole(object):
    '''
    Ported form :eventlet.backdoor.SocketConsole:.
    '''
    def __init__(self, locals, conn, banner=None):  # pylint: diable=W0622
        self.locals = locals
        self.desc = _fileobject(conn)
        self.banner = banner
        self.saved = None

    def switch(self):
        self.saved = sys.stdin, sys.stderr, sys.stdout
        sys.stdin = sys.stdout = sys.stderr = self.desc

    def switch_out(self):
        sys.stdin, sys.stderr, sys.stdout = self.saved

    def finalize(self):
        self.desc = None

    def _run(self):
        try:
            console = InteractiveConsole(self.locals)
            # __builtins__ may either be the __builtin__ module or
            # __builtin__.__dict__ in the latter case typing
            # locals() at the backdoor prompt spews out lots of
            # useless stuff
            import __builtin__
            console.locals["__builtins__"] = __builtin__
            console.interact(banner=self.banner)
        except SystemExit:  # raised by quit()
            sys.exc_clear()
        finally:
            self.switch_out()
            self.finalize()


class _fileobject(socket._fileobject):
    def write(self, data):
        self._sock.sendall(data)

    def isatty(self):
        return True

    def flush(self):
        pass

    def readline(self, *a):
        return socket._fileobject.readline(self, *a).replace("\r\n", "\n")


def make_threaded_backdoor(prefix=None):
    '''
    :return: started daemon thread running :main_loop:
    '''
    socket_file_name = _get_filename(prefix)

    db_thread = threading.Thread(target=main_loop, args=(socket_file_name,))
    db_thread.setDaemon(True)
    db_thread.start()
    return db_thread


def _get_filename(prefix):
    return SOCK_FILE_TEMPLATE % {
        'dir': '/var/run',
        'prefix': prefix,
        'pid': os.getpid(),
    }


def main_loop(socket_filename):
    try:
        log.debug('Binding backdoor socket to %s', socket_filename)
        check_socket(socket_filename)

        sockobj = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
        sockobj.bind(socket_filename)
        sockobj.listen(5)
    except Exception, e:
        log.exception('Failed to init backdoor socket %s', e)
        return

    while True:
        conn = None
        try:
            conn, _ = sockobj.accept()
            console = SocketConsole(locals=None, conn=conn, banner=None)
            console.switch()
            console._run()
        except IOError:
            log.debug('IOError closing connection')
        finally:
            if conn:
                conn.close()


def check_socket(socket_filename):
    try:
        os.unlink(socket_filename)
    except OSError:
        if os.path.exists(socket_filename):
            raise

示例程序:

make_threaded_backdoor(prefix='test')
while True:
    pass

示例会话:

mmatczuk@cactus:~$ rlwrap nc -U /var/run/test-3196.socket
Python 2.7.6 (default, Mar 22 2014, 22:59:56) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> import os
>>> os.getpid()
3196
>>> quit()
mmatczuk@cactus:~$ 

这是一个非常强大的工具,可用于:

  • dump threads,
  • 检查进程内存,
  • 按需附加调试器,pydev debugger(适用于eclipse和pycharm),
  • 强制GC,
  • 动态monkeypatch功能定义

甚至更多。

答案 1 :(得分:0)

我亲自将信息写入一个文件,以便我之后使用它,尽管这样做的缺点可能是稍微慢一点,因为它必须每次或每隔几次写一个文件来检索一条推文。

无论如何,如果您将其写入文件"output.txt",则可以打开bash并输入tail output.txt以查找文件中打印的最新10行,或者您可以输入tail -f output.txt,它使用您写入文件的行不断更新终端提示。如果你想停止,只需Ctrl-C

答案 2 :(得分:0)

这是一个长期运行的程序示例,它还维护状态套接字。当客户端连接到套接字时,脚本会将一些状态信息写入套接字。

#!/usr/bin/python

import os
import sys
import argparse
import random
import threading
import socket
import time
import select

val1 = 0
val2 = 0
lastupdate = 0
quit = False

# This function runs in a separate thread.  When a client connects,
# we write out some basic status information, close the client socket,
# and wait for the next connection.
def connection_handler(sock):
    global val1, val2, lastupdate, quit

    while not quit:
        # We use select() with a timeout here so that we are able to catch the
        # quit flag in a timely manner.
        rlist, wlist, xlist = select.select([sock],[],[], 0.5)
        if not rlist:
            continue

        client, clientaddr = sock.accept()
        client.send('%s %s %s\n' % (lastupdate, val1, val2))
        client.close()

# This function starts the listener thread.
def start_listener():
    sock = socket.socket(socket.AF_UNIX)

    try:
        os.unlink('/var/tmp/myprog.socket')
    except OSError:
        pass

    sock.bind('/var/tmp/myprog.socket')
    sock.listen(5)

    t = threading.Thread(
        target=connection_handler,
        args=(sock,))
    t.start()

def main():
    global val1, val2, lastupdate

    start_listener()

    # Here is the part of our script that actually does "work".
    while True:
        print 'updating...'
        lastupdate = time.time()
        val1 = val1 + random.randint(1,10)
        val2 = val2 + random.randint(100,200)
        print 'sleeping...'
        time.sleep(5)

if __name__ == '__main__':
    try:
        main()
    except (Exception,KeyboardInterrupt,SystemExit):
        quit=True
        raise

您可以编写一个简单的Python客户端来连接套接字,或者您可以使用类似socat的内容:

$ socat - unix:/var/tmp/myprog.sock
1403061693.06 6 152

答案 3 :(得分:0)

之前我曾写过类似的应用程序。

这是我做的:

当只需要几个命令时,我就像你一样使用信号,只是为了不使它太复杂。根据命令,我指的是您希望应用程序执行的操作,例如帖子中的print_info

但是当应用程序更新时,需要更多不同的命令,我开始使用特殊线程侦听套接字端口或读取本地文件以接受命令。假设应用程序需要支持prinf_info1 print_info2 print_info3,因此您可以使用客户端连接到目标端口并编写 print_info1 以使应用程序执行命令< em> print_info1 (或者如果您使用的是读取本地文件机制,只需将 print_info1 写入本地文件)。

当使用侦听套接字端口机制时,缺点是编写一个客户端来执行命令会花费更多的工作,优点是你可以在任何地方发出命令。

当使用读取本地文件机制时,缺点是你必须让线程在循环中检查文件并且它将使用一点资源,优点是给出命令非常简单(只需将一个字符串写入一个文件)并且您不需要编写客户端和套接字侦听服务器。

答案 4 :(得分:0)

rpyc是完成此任务的完美工具。

简而言之,您定义了一个rpyc.Service类,它公开了您要公开的命令,并启动rpyc.Server线程。

然后,您的客户端将连接到您的进程,并调用映射到您的服务公开的命令的方法。

它就像那样简单干净。无需担心套接字,信号,对象序列化。

它还有其他很酷的功能,例如协议是对称的。