Reputation: 1089
Often times I find myself in a situation in which I want to inject a sort of 'identity' into an instance of a custom class and depending on the kind, the instance should exhibit differing behaviour.
class Foo:
def __init__(self, identity):
self.identity = identity
def bar(self):
if self.identity = 'A':
do_something
elif self.identity = 'B':
sth_else
So right now I am using string comparison for this task but coming from C++ and its notorious striving for type-safety this seems odd to me. What is the most pythonic way of achieving what I want?
Upvotes: 0
Views: 400
Reputation: 95712
Use inheritance:
class Foo:
...
class Foo_A(Foo):
def bar(self):
do_something
class Foo_B(Foo):
def bar(self):
sth_else
Then just construct a Foo_A
or Foo_B
instance as appropriate.
If you want to change the 'identity' of the object during its lifecycle then identity
would be the wrong name for it.
Usually if you want an object with behaviour 'A' you create a Foo_A
and then later when you want an object with behaviour 'B' you throw away the original object and create Foo_B
instance. Make objects immutable if at all reasonable, you will thank yourself later if you do that now.
As suggested in the comments you can mutate the type of the class to change the behaviour of overridden methods. That works in Python and for some problems is the cleanest solution, but it is rare. Usually it is better to aggregate the variable objects (but better still just to not mutate the original objects). Here's a possible solution using aggregation:
class Foo:
def __init__(self, identity):
self.identity = identity
@property
def identity(self):
return self._processor.identity
@identity.setter
def identity(self, identity):
self._processor = FooProcessor(identity, self)
def bar(self):
self._processor.bar()
class FooProcessor:
def __new__(cls, identity, foo):
for subclass in cls.__subclasses__():
if identity == subclass.identity:
return object.__new__(subclass)
raise RuntimeError(f'unknown identity {identity}')
def __init__(self, identity, foo):
assert identity == self.identity
self.foo = foo
class Foo_A(FooProcessor):
identity = 'A'
def bar(self):
print(f'do_something with {self.foo}')
class Foo_B(FooProcessor):
identity = 'B'
def bar(self):
print(f'something else with {self.foo}')
foo = Foo('A')
foo.bar()
foo.identity = 'B'
foo.bar()
assert foo.identity == 'B'
And here's a version that mutates __class__
:
class Foo:
def __init__(self, identity):
self.identity = identity
@property
def identity(self):
return self._identity
@identity.setter
def identity(self, identity):
for subclass in Foo.__subclasses__():
if identity == subclass._identity:
self.__class__ = subclass
return
raise RuntimeError(f'unknown identity {identity}')
class Foo_A(Foo):
_identity = 'A'
def bar(self):
print(f'do_something with {self} {self.identity}')
class Foo_B(Foo):
_identity = 'B'
def bar(self):
print(f'something else with {self} {self.identity}')
foo = Foo('A')
foo.bar()
foo.identity = 'B'
foo.bar()
assert foo.identity == 'B'
Output is something like:
do_something with <__main__.Foo_A object at 0x10c022af0> A
something else with <__main__.Foo_B object at 0x10c022af0> B
Upvotes: 1
Reputation: 169268
With the
I cant, because I need to dynamically switch between those at runtime
going against inheritance, maybe you could split that dynamic behavior into behavior classes such as
class BaseFooBehavior:
@classmethod
def bar(cls, foo):
raise NotImplementedError('...')
class FooBehavior1(BaseFooBehavior):
@classmethod
def bar(cls, foo):
return foo.a * foo.b
class FooBehavior2(BaseFooBehavior):
@classmethod
def bar(cls, foo):
return str(foo.a) * foo.b
class Foo:
behavior_cls = FooBehavior1 # default behavior
def __init__(self, a, b):
self.a = a
self.b = b
def bar(self):
return self.behavior_cls.bar(self)
foo = Foo(a=42, b=12)
print(foo.bar())
foo.behavior_cls = FooBehavior2 # change the instance's behavior
print(foo.bar())
printing out
504
424242424242424242424242
Upvotes: 0