5

I'd like to be able to use __delitem__ with a class-level variable. My use case can be found here (the answer that uses _reg_funcs) but it basically involves a decorator class keeping a list of all the functions it has decorated. Is there a way I can get the class object to support __delitem__? I know I could keep an instance around specially for this purpose but I'd rather not have to do that.

class Foo(object):
    _instances = {}

    def __init__(self, my_str):
        n = len(self._instances) + 1
        self._instances[my_str] = n
        print "Now up to {} instances".format(n)

    @classmethod
    def __delitem__(cls, my_str):
        del cls._instances[my_str]


abcd = Foo('abcd')
defg = Foo('defg')

print "Deleting via instance..."
del abcd['abcd']
print "Done!\n"

print "Deleting via class object..."
del Foo['defg']
print "You'll never get here because of a TypeError: 'type' object does not support item deletion"
Community
  • 1
  • 1
kuzzooroo
  • 6,788
  • 11
  • 46
  • 84

1 Answers1

11

When you write del obj[key], Python calls the __delitem__ method of the class of obj, not of obj. So del obj[key] results in type(obj).__delitem__(obj, key).

In your case, that means type(Foo).__delitem__(Foo, 'abcd'). type(Foo) is type, and type.__delitem__ is not defined. You can't modify type itself, you'll need to change the type of Foo itself to something that does.

You do that by defining a new metaclass, which is simply a subclass of type, then instructing Python to use your new metaclass to create the Foo class (not instances of Foo, but Foo itself).

class ClassMapping(type):
    def __new__(cls, name, bases, dct):
        t = type.__new__(cls, name, bases, dct)
        t._instances = {}
        return t
    def __delitem__(cls, my_str):
        del cls._instances[my_str]

class Foo(object):
    __metaclass__ = ClassMapping
    def __init__(self, my_str):
        n = len(Foo._instances) + 1
        Foo._instances[my_str] = n
        print "Now up to {} instances".format(n)

Changing the metaclass of Foo from type to ClassMapping provides Foo with

  1. a class variable _instances that refers to a dictionary
  2. a __delitem__ method that removes arguments from _instances.
chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    Works, thanks. But I'm surprised that "when you write del obj[key], Python calls the __delitem__ method of the class of obj, not of obj." Why is this? – kuzzooroo Nov 28 '13 at 05:48
  • 3
    Like any method, the function is tied to the class, not the object, and the object is the implicit first argument to the function. Trying to make `__delitem__` a class method instead of a regular instance method would be a special case which Python simply does not implement. – chepner Nov 28 '13 at 15:55