Home > Enterprise >  Redefine Method of an Object
Redefine Method of an Object

Time:10-18

I've got a class, where a method should only run once. Of course, it could easily be done with artificial has_executed = True/False flag, but why use it, if you can just delete the method itself? python's a duck-typed language, everything is a reference, bla-bla-bla, what can go wrong?

At least it was the thought. I couldn't actually do it:

class A:    
    def b(self):
        print("empty")
        self.__delattr__('b')

a = A()
a.b()

raises AttributeError: b. However, executing self.__getattribute__('b') returns <bound method A.b of <__main__.A object at 0x000001CDC6742FD0>>, which sounds stupid to me: why is a method any different from an attribute, since everything in python is just a reference to an object? And why can I __getattribute__, but not __delattr__?

The same goes to redefinition. I can easily set any attribute, but methods are a no-no?

class A:
    def b(self):
        print("first")
        self.__setattr__('b', lambda self: print(f"second"))

a = A()
a.b()
a.b()

results into TypeError: <lambda>() missing 1 required positional argument: 'self'. Which, of course, means, that now python isn't using dot-notation as intended. Of course, we could ditch the self attribute in the lambda altogether, considering we've got the reference to it already in b. But isn't it incorrect by design?

The further I'm trying to take python to the limit, the more frustrated I become. Some imposed limitations (or seemingly imposed?) seem so unnatural, considering the way the language is marketed. Shouldn't it allow this? Why doesn't it work?

CodePudding user response:

It doesn't work because b isn't an attribute belonging to the instance, it belongs to the class.

>>> a = A()
>>> list(a.__dict__)
[]
>>> list(A.__dict__)
['__module__', 'b', '__dict__', '__weakref__', '__doc__']

When a.b is evaluated, Python will see that a has no instance attribute named b and fall back to the class. (It's a little more complicated because when falling back to the class, it will not simply return the method itself, but a version of the method which is bound to the instance a.)

Since you don't want to delete the method on the class, the way to go is to replace the method on the instance. I don't know why you tried to do this with __setattr__ - there is no need for that, simply assign self.b = ... as normal. The reason your attempt failed is because your lambda requires a positional parameter named self, but this parameter will not be automatically bound to the instance when you look it up, because it is an instance attribute, not a class attribute.

class A:
    def b(self):
        print('first')
        self.b = lambda: print('second')

Usage:

>>> a = A()
>>> a.b()
first
>>> a.b()
second

CodePudding user response:

I'm not too sure what the problem is, but I guess you can try using a class method:

class A:

    @classmethod
    def b(cls):
        print("empty")
        delattr(cls, 'b')


a = A()
a.b()

try:
    a.b()
except AttributeError:
    print('delete is successful')
else:
    print('we were unable to delete method b')

Edit: I actually figured it out. You need to call delete on the class itself (not the instance)

class A:

    def b(self):
        print("empty")
        # delete method from class (A) instead of instance (a)
        delattr(self.__class__, 'b')


a = A()
a.b()

try:
    a.b()
except AttributeError:
    print('delete is successful')
else:
    print('we were unable to delete method b')

CodePudding user response:

Well in python you have 2 types of attributes

  • A class attribute is a variable that belongs to a certain class, and not a particular object. Every instance of this class shares the same variable. These attributes are usually defined outside the init constructor
  • An instance/object attribute is a variable that belongs to one (and only one) object. Every instance of a class points to its own attributes variables. These attributes are defined within the init constructor.

In case of a class attribute its part of the class descriptor, so you cannot delete it from the object attributes like self.__deleteattr__ or add new one with __setattr__ as it alters the class descriptor and reflects on all objects. Such an operation can have devastating effects.

Its very similar to a class variable as well. You can however change the behavior with overriding or reassigning like below

class A:    
    def b(self):
        print("empty")
        A.b =  lambda self: print(f"second")

a = A()
a.b()
a.b()
  • Related