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