views:

52

answers:

3

Hello, I have a method to save data in DB, and a decorator to manage the connection, but I can't figure out how to make it work.

method to save:

class DA_Row(DABase):

    @DABase.connectAndDisconnect
    def save(self):
        """
        Guarda el spin en la base de datos
        """
        self.__cursor.callproc('sp_insert_row', (
                                 "value 1",
                                 "value 2"
                                 )
        )

and I have here the inherited class with a function decorator that does not work.

class DABase():

    def __init__(self):
        self.__cursor = None

    @staticmethod
    def connectAndDisconnect(func):
        def deco(*args):
            returnValue = None
            self.DBconnect()
            try:
                self.__cursor = self.db.cursor()
                returnValue = func(*args)
            finally:
                self.desconectarDB()

            return returnValue
        return deco
....

Shown this...

How can I redefine DABase.__cursor from a decorator?

If is not possible, how to solve this problem in a different way?

Thank you for your time!

+2  A: 

It would help if you showed the error that you're getting. However, I can take a guess...

Decorating a method of a class is hard. How is connectAndDisconnect supposed to know what self should be? connectAndDisconnect is a static method of the base class, which gets called when the derived class gets created, long before any instances of the derived class get created.

There's a trick which lets the decorator figure out what self should be, but it's a complicated hack and fragile in a way that I'll explain at the end. The trick is, use a class as the decorator, and make that class an descriptor (i.e. define __get__) to give you a chance to determine what self should be. In your case it would look something like:

class DABase(object):
  def __init__(self):
    self.__cursor = None

  class connectAndDisconnect(object):
    def __init__(self, method):
      self._method = method # method is the thing being decorated
                            # note that it's an *unbound* method
      self._instance = None # no way to know what the instance is yet

    def __get__(self, instance, owner):
      'This will be called when the decorated method is accessed'
      self._instance = instance
      return self

    def __call__(self, *args):
      'This is where the actual decoration takes place'
      returnValue = None

      # 'self' is the connectAndDisconnect object. 'self._instance' is the decorated object.
      self._instance.DBConnect()
      try:
        self._instance.__cursor = self._instance.db.cursor()
        # Since self._method is unbound, we have to pass the instance explicitly
        returnValue = self._method(self._instance, *args)
      finally:
        self._instance.desconectarDB()
      return returnValue

The derived class is unchanged:

class DA_Row(DABase):
  @DABase.connectAndDisconnect
  def save(self):
    # ...

Now DA_Row.save is actually an instance of the connectAndDisconnect class. If d is a DA_Row object and someone calls d.save(), the first thing that happens is that connectAndDisconnect.__get__ gets called because someone tried to access d.save. This sets the _instance variable to equal d. Then connectAndDisconnect.__call__ gets called and the actual decoration takes place.

This works, most of the time. But it's fragile. It only works if you call save in the "normal" way, i.e. through an instance. If you try to do funny stuff like calling DA_Row.save(d) instead, it won't work because connectAndDisconnect.__get__ won't be able to figure out what the instance should be.

Peter Milley
It seems to work, it helps me understand how to work with decorators. thank you!
dnuske
@dnuske: then you should mark the answer as accepted!
katrielalex
but is too long and confusing, it would be better a short answer for the understanding of people not that professional
dnuske
A: 

I got it working!

@staticmethod
def connectAndDisconnect(func):
    def deco(*args):
        self = args[0]
        returnValue = None
        self.conectarDB()
        try:
            self.cursor = self.db.cursor()
            returnValue = func(*args)
        finally:
            self.desconectarDB()

        return returnValue
    return deco

the first deco param should be self, so I defined it.

=)

dnuske
+2  A: 

self is just a name like everything else, it does not magically appear like Java's this. You need to add it to your decorator. Try this:

    @staticmethod
    def connectAndDisconnect(func):
        # deco will be a method, so it needs self (ie a DA_Row instance)
        def deco(self, *args):
            returnValue = None
            self.DBconnect()
            try:
                self.__cursor = self.db.cursor()
                # func was supposed to be a method to, so it needs self
                returnValue = func(self, *args)
            finally:
                self.desconectarDB()

            return returnValue
        return deco
THC4k