简体   繁体   中英

Redefine Method of an Object

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 'sa 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 Ab 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?

UPD

Ok, consider this:

class A:
    def __init__(self):
        self.variable = 1

    def b(self):
        print("old")
        self.variable += 1
        def new_b():
            print("new")
            self.variable += 15
        self.__setattr__('b', new_b)

It will work and do what we want: none of other objects will have their Ab method redefined once one object kind of overlays its b definition. ( overlays , since everyone so far says that you cannot redefine a method for an object, but instead only kind of hide it from the caller behind another attribute with the same name, as far as I understand).

Is this good?

It doesn't work because b isn't an attribute belonging to the instance, it belongs to the class. So you can't delete it on the instance because it isn't there to be deleted.

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

When ab 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

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()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM