Reputation: 2914
I have a class with a few methods, some of which are only valid when the object is in a particular state. I would like to have the methods simply not be bound to the objects when they're not in a suitable state, so that I get something like:
>>> wiz=Wizard()
>>> dir(wiz)
['__doc__', '__module__', 'addmana']
>>> wiz.addmana()
>>> dir(wiz)
['__doc__', '__module__', 'addmana', 'domagic']
>>> wiz.domagic()
>>> dir(wiz)
['__doc__', '__module__', 'addmana']
>>> wiz.domagic()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Wizard instance has no attribute 'domagic'
I can see how to add methods (types.MethodType(method, object)), but I can't see any way to delete a method for just a single object:
>>> wiz.domagic
<bound method Wizard.domagic of <__main__.Wizard instance at 0x7f0390d06950>>
>>> del wiz.domagic
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: Wizard instance has no attribute 'domagic'
Overriding __dir__ (and getting an InvalidState or NotEnoughMana exception on invocation instead of AttributeError on reference) might be okay, but I can't see how to mimic the built-in behaviour of dir() accurately. (Ideally I'd prefer a way that works in Python 2.5, too)
Ideas?
Upvotes: 15
Views: 10975
Reputation: 831
I have a class with a few methods, some of which are only valid when the object is in a particular state. have the methods simply not be bound to the objects
By relaxing this a bit, there is another possible technique. Don't delete the functions you don't want, but simply redirect them.
In "magic" mode we should only be to run domagic. In "reality" mode we should only be able to run doreality.
Pros:
class Wizard:
def __init__(self):
self._mode = 'both'
def setmode(self, mode):
self._mode = mode
print(f'set mode to {self._mode}')
if self._mode == 'magic':
setattr(self, 'doreality', self._bad_func)
else:
setattr(self, 'domagic', self._bad_func)
def domagic(self):
print('in domagic')
def doreality(self):
print('in doreality')
def _bad_func(self):
print(f'in _bad_func: {self._mode}')
print("===")
wiz = Wizard()
wiz.setmode('magic')
wiz.domagic()
wiz.doreality()
print("===")
wiz = Wizard()
wiz.setmode('reality')
wiz.domagic()
wiz.doreality()
Output:
===
set mode to magic
domagic works!
in _bad_func: magic
===
set mode to reality
in _bad_func: reality
doreality works!
Upvotes: 0
Reputation: 28768
While I agree that this is the wrong solution, my way to "remove" a method would be to overwrite the function with a property that raises an AttributeError
. This way hasattr
gives the correct result, but it is not as much hassle as using dir
.
Upvotes: 0
Reputation: 18720
As an additional possibility (twisting the question a little bit), if it makes more sense to only have certain methods on certain instances, you can always add those methods in the __init__
of the class to those instances for which it makes sense. Ex: say we have your Wizard
class, and the only time a Wizard
instance should have the domagic()
method is if a magic
parameter passed to __init__()
is True
. We could then do something like:
class Wizard(object):
def __init__(self, magic = False):
if magic:
def _domagic():
print "DOING MAGIC!"
self.domagic = _domagic
def main():
mage = Wizard(magic = True)
nomage = Wizard(magic = False)
mage.domagic() # prints "DOING MAGIC!"
nomage.domagic() # throws an AttributeError
Having said that, this code does have a bit of smell to it -- now before you call domagic() on a Wizard, you need to know if the method is defined or not. I wonder if the inheritance heirarchy could be refined a bit to make this a bit more elegant.
Upvotes: 2
Reputation: 27986
There seem to be some pretty good ideas to address the explicit question of how to hide/remove a method from a python object, so I'd like to address a higher level question.
Specifically, the problem statement from the original question:
I have a class with a few methods, some of which are only valid when the object is in a particular state.
This situation is a classic example of the problem solved via the State Design Pattern:
This pattern is used in computer programming to represent the state of an object. This is a clean way for an object to partially change its type at runtime.
I would consider using this pattern if your Wizard has the following properties:
Upvotes: 1
Reputation: 2914
Looks like the way to dir() works by default is:
dir(obj) == sorted(obj.__dict__.keys() + dir(obj.__class__))
(well, removing duplicates anyway)
So an approach would be:
class Wizard(object):
def __init__(self):
self.mana = 0
def __dir__(self):
natdir = set(self.__dict__.keys() + dir(self.__class__))
if self.mana <= 0:
natdir.remove("domagic")
return list(natdir)
def addmana(self):
self.mana += 1
def domagic(self):
if self.mana <= 0:
raise NotEnoughMana()
print "Abracadabra!"
self.mana -= 1
With the behaviour in Py2.6 being:
>>> wiz = Wizard()
>>> [x for x in dir(wiz) if not x.startswith("_")]
['addmana', 'mana']
>>> wiz.addmana()
>>> [x for x in dir(wiz) if not x.startswith("_")]
['addmana', 'domagic', 'mana']
>>> wiz.domagic()
Abracadabra!
>>> [x for x in dir(wiz) if not x.startswith("_")]
['addmana', 'mana']
>>> wiz.domagic()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 13, in domagic
__main__.NotEnoughMana
Upvotes: 4
Reputation: 96830
You can't delete a class method from an instance of that class because the instance doesn't have that method. The protocol is: if o
is an instance of class Foo
, and I call o.bar()
, first o
is examined to see if it has a method named bar
. If it doesn't, then Foo
is examined. The methods aren't bound to the instance unless they override its class.
I don't see any good that can come from the road that you're going down here, either.
Upvotes: 15
Reputation: 28617
This isn't directly answering your question, but I think a better approach to solving this problem would be to not add/remove the member method domagic
on your wiz
object:
What I would do instead is within the domagic
method, add a condition that checks for the relevant state of the wiz
object, and then only perform the rest of the domagic
method for a valid state, otherwise outputting an error message of your choosing.
def domagic():
if (state != desired_state):
print "You cannot do any magic now!"
return
print "Doing some magic"
[some more commands]
Upvotes: 9
Reputation: 1535
You can use a hack like that:
>>> class A(object):
... def test(self):
... print 'test'
...
>>> a = A()
>>> def noattr(name):
... raise AttributeError('no attribute %s' % name)
...
>>> a.test = lambda *a, **k: noattr('test')
>>> a.test()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
/Users/piranha/<ipython console> in <module>()
/Users/piranha/<ipython console> in <lambda>(*a, **k)
/Users/piranha/<ipython console> in noattr(name)
AttributeError: no attribute test
Of course, this gives you wrong traceback, but exception is the same. ;-)
Another (IMHO - better) way is to override __getattr__
and then put dispatch logic there, then you can use it like that:
>>> class A(object):
... def __getattr__(self, name):
... def inner(*args, **kwargs):
... print args, kwargs
... return inner
...
>>> a = A()
>>> a.test
<function inner at 0x1006c36e0>
>>> a.test('q')
('q',) {}
Upvotes: 2
Reputation: 9725
If an object is in a state where calling a given method on that object doesn't make sense, having it disappear from the object's interface seems like the wrong way to handle the problem. More explicit error handling gives you the ability to explain the problem more precisely to your callers, throwing NotEnoughMana instead of presenting them with a method-not-found error.
If you're worried about having a bunch of function prefixes that look like this:
if self.StateNotValid():
raise NotEnoughMana()
...then you could use a decorator on each function. It's shorter, and gives you an easy way to grep for all special-state-requiring functions.
Upvotes: 6