[Tutor] Class decorator on a derived class not initialising the base classes using super - TypeError

Peter Otten __peter__ at web.de
Mon Feb 24 21:09:06 CET 2014


Sangeeth Saravanaraj wrote:

> On Mon, Feb 24, 2014 at 10:53 PM, Peter Otten <__peter__ at web.de> wrote:
> 
>> Sangeeth Saravanaraj wrote:
>>
>> > I am trying to capture an object initiation and deletion events using
>> > the __call__() and __del__() methods with the following approach.
>>
>> Note that there is no guarantee that __dell__ will ever be called.
>> Usually it is better to introduce a weakref with callback.
>>
>> > class A(object):
>> >     def __init__(self, klass):
>> >         print "A::__init__()"
>> >         self._klass = klass
>> >
>> >     def __call__(self):
>> >         print "A::__call__()"
>> >         return self._klass()
>> >
>> >     def __del__(self):
>> >         print "A::__del__()"
>> >
>> > class Parent1(object):
>> >     def __init__(self):
>> >         print "Parent1:: __init__()"
>> >         super(Parent1, self).__init__()
>> >
>> > class Parent2(object):
>> >     def __init__(self):
>> >         print "Parent2:: __init__()"
>> >         super(Parent2, self).__init__()
>> >
>> > @A
>> > class B(Parent1, Parent2):
>> >     def __init__(self):
>> >         print "B::__init__()"
>> >         super(B, self).__init__()
>> >
>> > def main():
>> >     b = B()
>> >
>> > if __name__ == "__main__":
>> >     main()
>> >
>> >
>> > I decorate a class, say class B (whose object initiation and deletion I
>> > wanted to capture) with a decorator class A. Please note that the class
>> > B is derived from two classes - Parent1 & Parent2 and I want to use
>> > super() method to initialise the parent classes.
>> >
>> > When I executed the above code snippet, I ran into the following issue:
>> >
>> >
>> > A::__init__()
>> > A::__call__()
>> > B::__init__()
>> > Traceback (most recent call last):
>> >   File "so.py", line 40, in <module>
>> >     main()
>> >   File "so.py", line 36, in main
>> >     b = B()
>> >   File "so.py", line 10, in __call__
>> >     return self._klass()
>> >   File "so.py", line 32, in __init__
>> >     super(B, self).__init__()
>> > TypeError: must be type, not A
>> > A::__del__()
>> >
>> >
>> > When I commented "super(B, self).__init__()" in the class B ::
>> > __init__() method, it returned an object of type B and I was able to
>> > see the prints in the __call__ and __del__ methods but the __init__()
>> > methods of the
>> base
>> > classes (Parent1 & Parent2) are not called!
>> >
>> > From the error message, what I could understand is - the object
>> > returned by A::__call__() is not of type B but of type A. But when I
>> > put a print
>> in
>> > the A::__call__() I could see it returns an object of type B and not A.
>> >
>> > Now the question is - With this approach to capture the initiation and
>> > deletion events of an object, how do I initialise the base classes
>> > using super()?
>>
>> You'd have to introduce a naming convention or rewrite your class to be
>> aware of the wrapping in some way:
>>
>> @A
>> class B(Parent1, Parent2):
>>     def __init__(self):
>>         print "B::__init__()"
>>         super(B._klass, self).__init__()
>>
>> Not pretty.
>>
>> > Or, is there any other better way to capture the __call__ and __del__
>> >  events for an object of a certain class - if so, how?!
>>
>> Most certainly, but you have to give some details about what you are up
>> to first.
>>
> 
> Sorry, I should have described what I was trying!
> 
> I want to create a decorator which should do the following things:
> 
>    - When an object of the decorated class is created, the objects name
>    (say the value of the incoming "id" argument) should be stored as a
>    record in a table in a database.
>    - When an object of the decorated class is deleted, the record with
>    this deleted objects name (i.e. object.id) should be removed from the
>    table.
> 
> You can safely assume that all the database operations are working fine!
> 
> Now, for example - consider the following snippet:
> 
> @saveme
> class A(object):
>     def __init__(self, id):
>         self.id = id
> 
> @saveme
> class B(object):
>     def __init__(self, id):
>         self.id = id
> 
> "saveme" should do what I have explained earlier.
> 
> a1 = A("A1")
> a2 = A("A2")
> a3 = A("A3")
> b1 = B("B1")
> b2 = B("B2")
> 
> At this point if I query and print all the records in a table, I should
> get the following:
> output: ["A1", "A2", "A3", "B1", "B2"]
> 
> del a1
> del a2
> del a3
> del b1
> del b2
> 
> At this point, all entries in the table should be deleted; query should
> return an empty list!
> 
> And, I want to highlight that the classes that are being decorated with
> "saveme" can de derived classes too!
> 
> What is the best way to do this?!

I'm sorry, after a bit of try-and-error I could not come up with a good way 
to write such a decorator. My best effort so far uses inheritance:

import itertools
import weakref

_registry = weakref.WeakValueDictionary()
_next_id = lambda count=itertools.count(): next(count)

def show():
    print(list(_registry.values()))

class Registered(object):
    def __init__(self, id=None):
        if id is None:
            id = _next_id()
        self.id = id
        _registry[id] = self
    def __repr__(self):
        return "{}(id={!r})".format(self.__class__.__name__, self.id)

class A(Registered): 
    pass

class B(Registered): 
    pass

class C(B): 
    pass

a1 = A()
b1 = B()
c1 = C()
show()
del a1
show()
b2 = B()
show()
del b1
del c1
del b2
show()




More information about the Tutor mailing list