Supahupe
Supahupe

Reputation: 515

Python decorate function with class and access class instance

i have a class in Python which shall perform a remote connection to a server. Because there are several protocols (TCP/IP, HTTP) to access the same data which can all be used depending on the situation (performance issues,...), I would like to use the class as a decorator for different functions which encapsulate the implementation.

My class looks like this:

class RemoteConnection(object):
      def __init__(self, function):
         self.function = function
         self.results = None

      def __call__(self, *args):
         self.function(self, *args)

      def setResults(*args):
         self.results = args


 @RemoteConnection
 def performTCPConnection(instance, username, password):
       #...perform connection
       instance.setResults(results) #set the results of the connection (a query)
       return instance

 @RemoteConnection
 def performHTTPConnection(instance, username, password):
       #...perform connection
       #and so on

  if __name__ == '__main__':
        performTCPConnection("admin", "admin") #OR
        #performHTTPConnection("admin, "admin")

The parameters (more than username and passwords, these are only for example) later shall be given to the programm by using argparse or something else.

If I call the programm like this, the __init__()-method is called two times because of the decorator. Also, the performTCPConnection-function is called and before the __call__-method is called. What I want now is that the instance of the RemoteConnection class is returned by the performTCPConnection-function to a variable in the main-function.

If i do something like the following:

foo = perfprmTCPConnection("admin","admin")
print foo

None is printed to StdOut.

If i try to access the instance in the performTCPConnection-function like that:

   @RemoteConnection
   def performTCPConnection(instance, username, password):
   #...perform connection
   instance.setResults(results) #set the results of the connection (a query)
    print instance
   return instance

something like <__main__.ServerConnection object at 0x7f4803516fd0> is written to StdOut.

My idea is a bit driven from the Java-world like implementing a Strategy-Pattern.

Unfortunately, I didn't find a good explanation of the explained behaviour until now. Is this also a good design approach?

Upvotes: 1

Views: 85

Answers (3)

alexis
alexis

Reputation: 50220

I'd definitely say this is not good design. It's a clever way to effectively get inheritance without using the inheritance mechanism, but it's non-standard, unintuitive, and unnecessary since there are more direct ways to do all this.

The answer of @zmo shows how to properly set up your derived classes. But I wouldn't even do this: Consider separating the connector objects from the class itself, and setting up your class signature like this:

class RemoteConnection(object):
    def __init__(self, connector):
        self.connection = connector

Then you simply instantiate it with a reference to the right connector:

conn = RemoteConnection(TCPConnector(user, password))

This way RemoteConnection encapsulates the protocol-independent functions, and the protocol-dependent parts are independent functions or objects (which could be derived from a common class if you wish). If the connector will be needed during the life of the connection, you're saving the connector object so you can call it later. If not, then TCPConnection could just return the results value you have now.

Upvotes: 2

zmo
zmo

Reputation: 24802

If I'm following what you wrote, I believe you're over-engineering this. Basically, your strategy using the decorator would be strictly equivalent to:

def performTCPConnection(username, password):
   instance = RemoteConnection()
   #...perform connection
   instance.setResults(results) #set the results of the connection (a query)
   return instance

but reading what you want, you're still not there yet. Because if I understand the purpose of the RemoteConnection class in your pattern, it's to defer the connection at a time when you'll call the .function() method.

So what you'd want is actually:

def prepareTCPConnection(username, password):
    rc = RemoteConnection()
    def connection_handler():
        # perform the connection, and setup results
        rc.setResults(results)
    rc.function = connection_handler
    return rc

you'd have to adapt RemoteConnection appropriately, obviously.

Here, what you do is take advantage of inner function (and closure) to define a function to be called at a later convenient point in your program flow.

But to be honest, you can achieve the same in a way simpler fashion using simple subclassing:

class PerformConnection():
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def perform(self):
        raise NotImplementedError

class PerformConnectionTCP(PerformConnection):
    def perform(self):
        # do what you got to do here...
        return results

then in your main you can do:

if __name__ == "__main__":
    instance = PerformConnectionTCP('admin', 'admin')
    instance.perform()

Upvotes: 1

Supahupe
Supahupe

Reputation: 515

I found the error, i forgot to return the result of the decorated function in the call()-method.

This solved the problem:

class RemoteConnection(object):
  def __init__(self, function):
     self.function = function
     self.results = None

  def __call__(self, *args):
     return self.function(self, *args) #here was the problem!

  def setResults(*args):
     self.results = args

Upvotes: 0

Related Questions