0

I need to check if the python script is already running then calling a method from the same running python script. But it must be on same process(pid), no new process. Is this possible?

I tried some codes but not worked.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import Tkinter as tk
from Tkinter import *
import socket


class Main():
    def mainFunc(self):
        self.root = tk.Tk()
        self.root.title("Main Window")

        self.lbl = Label(self.root, text = "First Text")
        self.lbl.pack()

        openStngs = Button(self.root, text = "Open Settings", command=self.settingsFunc)
        openStngs.pack()

    def settingsFunc(self):
        stngsRoot = Toplevel()
        stngsRoot.title("Settings Window")

        changeTextOfLabel = Button(stngsRoot, text = "Change Main Window Text", command=self.change_text)
        changeTextOfLabel.pack()

    def change_text(self):
        self.lbl.config(text="Text changed")

# the get_lock from http://stackoverflow.com/a/7758075/3254912
def get_lock(process_name):
    lock_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
    try:
        print lock_socket
        lock_socket.bind('\0' + process_name)
        print 'I got the lock'

        m.mainFunc()
        mainloop()

    except socket.error:
        print 'lock exists'
        m.settingsFunc()
        mainloop()
        # sys.exit()

if __name__ == '__main__':
    m=Main()
    get_lock('myPython.py')
tshepang
  • 12,111
  • 21
  • 91
  • 136
empax
  • 80
  • 2
  • 8
  • I don't think this isn't possible. Python isn't a compiled language like C/C++ where you can call a function from its memory address (also this would be very complicated if you're not at the same memory space). I think you can achieve whatever you want to do using another method. – cdonts Mar 16 '14 at 02:22

4 Answers4

4

You either need:

  • A proactive check in your running process to look at the environment (for instance, the contents of a file or data coming through a socket) to know when to fire the function,
  • or for your running process to receive unix signals or some other IPC (possibly one of the user-defined signals) and perform a function when one is received.

Either way you can't just reach into a running process and fire a function inside that process (it MIGHT not be literally impossible if you hook the running process up to a debugger, but I wouldn't recommend it).

Tkinter necessarily has its own event loop system, so I recommend reading up on how that works and how to either run something on a timer in that event loop system, or set up a callback that responds to a signal. You could also wrap a non-event loop based system in a try/except block that will catch an exception generated by a UNIX signal, but it may not be straightforward to resume the operation of the rest of the program after that signal is caught, in that case.

Andrew Gorcester
  • 19,595
  • 7
  • 57
  • 73
2

Sockets are a good solution to this kind of interprocess communication problem.

One possible approach would be to set up a socket server in a thread in your original process, this can be used as an entry point for external input. A (rather stupid) example might be:

# main.py

import socket
import SocketServer # socketserver in Python 3+
import time
from Queue import Queue
from threading import Thread

# class for handling requests
class QueueHandler(SocketServer.BaseRequestHandler):
    def __init__(self, request, client_address, server):
        self.server = server
        server.client_address = client_address
        SocketServer.BaseRequestHandler.__init__(self,request, client_address, server)

    # receive a block of data
    # put it in a Queue instance
    # send back the block of data (redundant)  
    def handle(self):
        data = self.request.recv(4096)
        self.server.recv_q.put(data)
        self.request.send(data)


class TCPServer(SocketServer.TCPServer):
    def __init__(self, ip, port, handler_class=QueueHandler):
        SocketServer.TCPServer.__init__(self, (ip, port), handler_class, bind_and_activate=False)
        self.recv_q = Queue() # a Queue for data received over the socket
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.server_bind()
        self.server_activate()

    def shutdown(self):
        SocketServer.TCPServer.shutdown(self)

    def __del__(self):
        self.server_close()


# This is the equivalent of the main body of your original code
class TheClassThatLovesToAdd(object):
    def __init__(self):
        self.value = 1

        # create and instance of the server attached to some port
        self.server = TCPServer("localhost",9999)

        # start it listening in a separate control thread
        self.server_thread = Thread(target=self.server.serve_forever)
        self.server_thread.start()
        self.stop = False

    def add_one_to_value(self):
        self.value += 1

    def run(self):
        while not self.stop:
            print "Value =",self.value

            # if there is stuff in the queue...
            while not self.server.recv_q.empty():

                # read and parse the message from the queue
                msg = self.server.recv_q.get()

                # perform some action based on the message
                if msg == "add":
                    self.add_one_to_value()
                elif msg == "shutdown":
                    self.server.shutdown()
                    self.stop = True

            time.sleep(1)

if __name__ == "__main__":
    x = TheClassThatLovesToAdd()
    x.run()

When you start this running, it should just loop over and over printing to the screen. Output:

Value = 1
Value = 1
Value = 1
...

However the TCPServer instance attached to the TheClassThatLovesToAdd instance now gives us a control path. The simplest looking snippet of control code would be:

# control.py

import socket
import sys

sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.settimeout(2)
sock.connect(('localhost',9999))

# send some command line argument through the socket
sock.send(sys.argv[1])
sock.close()

So if I run main.py in one terminal window and call python control.py add from another, the output of main.py will change:

Value = 1
Value = 1
Value = 1
Value = 2
Value = 2
...

Finally to kill it all we can run python control.py shutdown, which will gently bring main.py to a halt.

This is by no means the only solution to your problem, but it is likely to be one of the simplest.

ebarr
  • 7,704
  • 1
  • 29
  • 40
  • Thanks but i think this will be very big effort for my little python script. :) – empax Mar 17 '14 at 18:36
  • No worries. What you are asking to do is quite complicated so its not going to be that easy to implement in a few lines of code. – ebarr Mar 17 '14 at 23:04
0

One can try GDB, but not sure how to call a function from within [an idle thread].

Perhaps someone very versed with gdb and debugging/calling Python functions from within GDB can improve this answer.

Roel Van de Paar
  • 2,111
  • 1
  • 24
  • 38
0

One solution would be to use a messaging service (such as ActiveMQ or RabbitMQ). Your application subscribes to a queue/topic and whenever you want to send it a command, you write a message to it's queue. I'm not going to go into details because there are thousands of examples on-line. Queues/messaging/MQTT etc. are very simple to implement and are how most business systems (and modern control systems) communicate. Do a search for paho-mqtt.

Simon Dreyer
  • 43
  • 1
  • 8