I have a five or six resources that have nice 'with' handlers, and normally I'd do this:
with res1, res2, res3, res4, res5, res6:
do1
do2
However, sometimes one or more of these resources should not be activated. Which leads to very ugly repetitive code:
with res1, res3, res4, res6: # these always acquired
if res2_enabled:
with res2:
if res5_enabled:
with res5:
do1
do2
else:
do1
do2
else if res5_enabled:
with res5:
...
There must be clean easy ways to do this surely?
You could create a wrapper object that supports the with statement, and do the checking in there. Something like:
with wrapper(res1), wrapper(res2), wrapper(res3):
...
or a wrapper than handles all of them:
with wrapper(res1, res2, res3):
...
The definition for you wrapper would be:
class wrapper(object):
def __init__(self, *objs):
...
def __enter__(self):
initialize objs here
def __exit__(self):
release objects here
If I understand you correctly you can do this:
from contextlib import contextmanager, nested
def enabled_resources(*resources):
return nested(*(res for res,enabled in resources if enabled))
# just for testing
#contextmanager
def test(n):
print n, "entered"
yield
resources = [(test(n), n%2) for n in range(10)]
# you want
# resources = [(res1, res1_enabled), ... ]
with enabled_resources(*resources):
# do1, do2
pass
Original Poster here; here is my approach refined so far:
I can add (or monkey-patch) the bool operator __nonzero__ onto the with objects, returning whether they are enabled. Then, when objects are mutually exclusive, I can have:
with res1 or res2 or res3 or res4:
...
When an resource is togglable, I can create an empty withable that is a nop; wither seems a nice name for it:
class sither:
#classmethod
def __enter__(cls): pass
#classmethod
def __exit__(cls,*args): pass
...
with res1 or wither, res2 or wither:
...
I can also use this keeping the toggling out of the withable objects:
with res1 if res1enabled else wither, res2 if res2enabled else wither:
..
Finally, those I have most control over, I can integrate the enabled checking into the class itself such that when used and not enabled, they are nop:
with res1, res2, res3:
...
The with statement is absolutely adorable, it just seems a bit unentrenched yet. It will be interesting to see what finesse others come up with in this regard...
Related
Is there a way to begin a block of code with a with statement, but conditionally?
Something like:
if needs_with():
with get_stuff() as gs:
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
To clarify, one scenario would have a block encased in the with statement, while another possibility would be the same block, but not encased (i.e., as if it wasn't indented)
Initial experiments of course give indentation errors..
Python 3.3 and above
Python 3.3 introduced contextlib.ExitStack for just this kind of situation. It gives you a "stack", to which you add context managers as necessary. In your case, you would do this:
from contextlib import ExitStack
with ExitStack() as stack:
if needs_with():
gs = stack.enter_context(get_stuff())
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
Anything that is entered to stack is automatically exited at the end of the with statement as usual. (If nothing is entered, that's not a problem.) In this example, whatever is returned by get_stuff() is exited automatically.
If you have to use an earlier version of python, you might be able to use the contextlib2 module, although this is not standard. It backports this and other features to earlier versions of python. You could even do a conditional import, if you like this approach.
Python 3.7 and above
Python 3.7 further introduced contextlib.nullcontext (a couple years after this answer was originally posted, and since that time mentioned in several other answers). In the comments, #Kache points out the most elegant usage of this option:
from contextlib import nullcontext
with get_stuff() if needs_with() else nullcontext() as gs:
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
Note that if needs_with() is False, then gs will be None inside the context block. If you want gs to be something_else in that case, you just replace nullcontext() with nullcontext(something_else).
This approach is obviously not as flexible as ExitStack, because this is just a binary choice, whereas ExitStack allows you to add as many exiting things as you want, with complicated logic and so on. But this certainly answers the OP's simple requirements.
If you want to avoid duplicating code and are using a version of Python prior to 3.7 (when contextlib.nullcontext was introduced) or even 3.3 (when contextlib.ExitStack was introduced), you could do something like:
class dummy_context_mgr():
def __enter__(self):
return None
def __exit__(self, exc_type, exc_value, traceback):
return False
or:
import contextlib
#contextlib.contextmanager
def dummy_context_mgr():
yield None
and then use it as:
with get_stuff() if needs_with() else dummy_context_mgr() as gs:
# do stuff involving gs or not
You alternatively could make get_stuff() return different things based on needs_with().
(See Mike's answer or Daniel's answer for what you can do in later versions.)
As of Python 3.7 you can use contextlib.nullcontext:
from contextlib import nullcontext
if needs_with():
cm = get_stuff()
else:
cm = nullcontext()
with cm as gs:
# Do stuff
contextlib.nullcontext is pretty much just a no-op context manager. You can pass it an argument that it will yield, if you depend on something existing after the as:
>>> with nullcontext(5) as value:
... print(value)
...
5
Otherwise it'll just return None:
>>> with nullcontext() as value:
... print(value)
...
None
It's super neat, check out the docs for it here: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext
A third-party option to achieve exactly this:
https://pypi.python.org/pypi/conditional
from conditional import conditional
with conditional(needs_with(), get_stuff()):
# do stuff
import contextlib
my_context = None # your context
my_condition = False # your condition
# Option 1 (Recommended)
with my_context if my_condition else contextlib.nullcontext():
print('hello 1')
# Option 2
with my_context if my_condition else contextlib.ExitStack():
print('hello 2')
You can use contextlib.nested to put 0 or more context managers into a single with statement.
>>> import contextlib
>>> managers = []
>>> test_me = True
>>> if test_me:
... managers.append(open('x.txt','w'))
...
>>> with contextlib.nested(*managers):
... pass
...
>>> # see if it closed
... managers[0].write('hello')
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: I/O operation on closed file
This solution has its quirks and I just noticed that as of 2.7 its been deprecated. I wrote my own context manager to handle juggling multiple context managers. Its worked for me so far, but I haven't really considered edge conditons
class ContextGroup(object):
"""A group of context managers that all exit when the group exits."""
def __init__(self):
"""Create a context group"""
self._exits = []
def add(self, ctx_obj, name=None):
"""Open a context manager on ctx_obj and add to this group. If
name, the context manager will be available as self.name. name
will still reference the context object after this context
closes.
"""
if name and hasattr(self, name):
raise AttributeError("ContextGroup already has context %s" % name)
self._exits.append(ctx_obj.__exit__)
var = ctx_obj.__enter__()
if name:
self.__dict__[name] = var
def exit_early(self, name):
"""Call __exit__ on named context manager and remove from group"""
ctx_obj = getattr(self, name)
delattr(self, name)
del self._exits[self._exits.index(ctx_obj)]
ctx_obj.__exit__(None, None, None)
def __enter__(self):
return self
def __exit__(self, _type, value, tb):
inner_exeptions = []
for _exit in self._exits:
try:
_exit(_type, value, tb )
except Exception, e:
inner_exceptions.append(e)
if inner_exceptions:
r = RuntimeError("Errors while exiting context: %s"
% (','.join(str(e)) for e in inner_exceptions))
def __setattr__(self, name, val):
if hasattr(val, '__exit__'):
self.add(val, name)
else:
self.__dict__[name] = val
It was hard to find #farsil's nifty Python 3.3 one-liner, so here it is in its own answer:
with ExitStack() if not needs_with() else get_stuff() as gs:
# do stuff
Note that ExitStack should come first, otherwise get_stuff() will be evaluated.
So I made this code;
It is invoked like so:
with c_with(needs_with(), lambda: get_stuff()) as gs:
##DOESN't call get_stuff() unless needs_with is called.
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
Properties:
it does not call get_stuff() unless condition is true
if condition is false, it provides a dummy contextmanager. (could probably be replaced with contextlib.nullcontext for python >= 3.7)
Optionally you can send in an alternative contextmanager in case the condition is false:
with c_with(needs_with(), lambda: get_stuff(), lambda: dont_get_stuff()) as gs:
Hope this will help someone!
-- Here is the code:
def call_if_lambda(f):
"""
Calls f if f is a lambda function.
From https://stackoverflow.com/a/3655857/997253
"""
LMBD = lambda:0
islambda=isinstance(f, type(LMBD)) and f.__name__ == LMBD.__name__
return f() if islambda else f
import types
class _DummyClass(object):
"""
A class that doesn't do anything when methods are called, items are set and get etc.
I suspect this does not cover _all_ cases, but many.
"""
def _returnself(self, *args, **kwargs):
return self
__getattr__=__enter__=__exit__=__call__=__getitem__=_returnself
def __str__(self):
return ""
__repr__=__str__
def __setitem__(*args,**kwargs):
pass
def __setattr__(*args,**kwargs):
pass
class c_with(object):
"""
Wrap another context manager and enter it only if condition is true.
Parameters
----------
condition: bool
Condition to enter contextmanager or possibly else_contextmanager
contextmanager: contextmanager, lambda or None
Contextmanager for entering if condition is true. A lambda function
can be given, which will not be called unless entering the contextmanager.
else_contextmanager: contextmanager, lambda or None
Contextmanager for entering if condition is true. A lambda function
can be given, which will not be called unless entering the contextmanager.
If None is given, then a dummy contextmanager is returned.
"""
def __init__(self, condition, contextmanager, else_contextmanager=None):
self.condition = condition
self.contextmanager = contextmanager
self.else_contextmanager = _DummyClass() if else_contextmanager is None else else_contextmanager
def __enter__(self):
if self.condition:
self.contextmanager=call_if_lambda(self.contextmanager)
return self.contextmanager.__enter__()
elif self.else_contextmanager is not None:
self.else_contextmanager=call_if_lambda(self.else_contextmanager)
return self.else_contextmanager.__enter__()
def __exit__(self, *args):
if self.condition:
return self.contextmanager.__exit__(*args)
elif self.else_contextmanager is not None:
self.else_contextmanager.__exit__(*args)
#### EXAMPLE BELOW ####
from contextlib import contextmanager
def needs_with():
return False
#contextmanager
def get_stuff():
yield {"hello":"world"}
with c_with(needs_with(), lambda: get_stuff()) as gs:
## DOESN't call get_stuff() unless needs_with() returns True.
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
print("Hello",gs['hello'])
I have found that the #Anentropic answer is incomplete.
from conditional import conditional
a = 1 # can be None
if not a is None:
b = 1
class WithNone:
def __enter__(self):
return self
def __exit__(self, type, value, tb):
pass
def foo(x):
print(x)
return WithNone()
with conditional(not a is None, foo(b) if not a is None else None):
print(123)
The complete conditional usage required 3 conditions instead of 1 because of:
NameError: name 'b' is not defined in case if not defined a
the function foo still must return enterable object, otherwise: AttributeError: 'NoneType' object has no attribute '__enter__'
Is there a better way of doing this than:
def create_expired_weakref():
class Tmp: pass
ref = weakref.ref(Tmp())
assert ref() is None
return ref
Context: I want a default state for my weakref, so that my class can do:
def __init__(self):
self._ref = create_expired_weakref()
def get_thing(self):
r = self._ref() # I need an empty weakref for this to work the first time
if r is None:
r = SomethingExpensive()
self._ref = weakref.ref(r)
return r
Another approach is to use duck typing here. If all you care about is that it behaves like a dead weakref with respect to the self._ref() call, then you can do
self._ref = lambda : None
This is what I ended up using when I had a similar desire to have a property that would return a cached value if it was available, but None otherwise. I initialized it with this lambda function. Then the property was
#property
def ref(self):
return self._ref()
Update: Credit to #Aran-Fey, who I see posted this idea as a comment to the question, rather than as an answer.
Use you a weakref.finalize for great good:
import weakref
def create_expired_weakref(type_=type("", (object,), {'__slots__':
('__weakref__',)})):
obj = type_()
ref = weakref.ref(obj)
collected = False
def on_collect():
nonlocal collected
collected = True
final = weakref.finalize(obj, on_collect)
del obj
while not collected:
pass
return ref
This might block the thread for a while if you're debugging, and might even deadlock in some obscure situations, but it's guaranteed to return an expired weakref.
I have a class with code that fits into the following template:
class aClass:
def __init__(self, switch = False):
self.switch = switch
def f(self):
done = False
while not done:
# a dozen lines of code
if self.switch:
# a single line of code
# another dozen lines of code
So the single line of code in the if statement will either never be executed, or it will be executed in all iterations. And this is actually known as soon as the object is initialized.
When self.switch is True, I would like the single line of code to be executed without having to check for self.switch at every single iteration. And when self.switch is False, I would like the single line of code to be ignored, again without having to repeatedly check for self.switch.
I have of course considered writing two versions of f and selecting the appropriate one in __init__ according to the value of the switch, but duplicating all this code except for a single line doesn't feel right.
Can anyone suggest an elegant way to solve this problem? Perhaps a way to generate the appropriate version of the f method at initialization?
That's a completely valid ask. If not for performance then for readability.
Extract the three pieces of logic (before, inside, and after your condition) in three separate methods and in f() just write two implementations of the big loop:
def first(self):
pass
def second(self):
pass
def third(self):
pass
def f(self):
if self.switch:
while ...:
self.first()
self.third()
else:
while ...:
self.first()
self.second()
self.third()
If you want it more elegant (although it depends on taste), you express the two branches of my f() into two methods first_loop and second_loop and then in __init__ assign self.f = self.first_loop or self.f = self.second_loop depending on the switch:
class SuperUnderperformingAccordingToManyYetReadable(object):
def __init__(self, switch):
if self.switch:
self.f = self._first_loop
else:
self.f = self._second_loop
def _first(self):
pass
def _second(self):
pass
def _third(self):
pass
def _first_loop(self):
while ...:
self.first()
self.third()
def _second_loop(self):
while ...:
self.first()
self.second()
self.third()
You may need to do some extra work to manage breaking out of the while loop.
If the .switch attribute is not supposed to change, try to select the loop body dynamicly in the __init__() method:
def __init__(self, switch=False):
self.switch = switch
self.__fBody = self.__fSwitchTrue if switch else self.__fSwitchFalse
def f(self):
self.__done = False
while not self.__done:
self.__fBody()
def __fSwitchTrue(self):
self.__fBodyStart()
... # a single line of code
self.__fBodyEnd()
def __fSwitchFalse(self):
self.__fBodyStart()
self.__fBodyEnd()
def __fBodyStart(self):
... # a dozen lines of code
def __fBodyEnd(self):
... # another dozen lines of code
Remember to change values used by more than one of the defined methods to attributes (like done is changed to .__done).
In a comment to my original question, JohnColeman suggested using exec and provided a link to another relevant question.
That was an excellent suggestion and the solution I was lead to is:
_template_pre = """\
def f(self):
for i in range(5):
print("Executing code before the optional segment.")
"""
_template_opt = """\
print("Executing the optional segment")
"""
_template_post = """\
print("Executing code after the optional segment.")
"""
class aClass:
def __init__(self, switch = False):
if switch:
fdef = _template_pre + _template_opt + _template_post
else:
fdef = _template_pre + _template_post
exec(fdef, globals(), self.__dict__)
# bind the function
self.f = self.f.__get__(self)
You can verify this actually works:
aClass(switch = False).f()
aClass(switch = True).f()
Before jumping to conclusions as to how "pythonic" this is, let me point out that such an approach is employed in a couple of metaclass recipes I have encountered and even in the Python Standard Library (check the implementation of namedtuple, to name one example).
Is there a way to begin a block of code with a with statement, but conditionally?
Something like:
if needs_with():
with get_stuff() as gs:
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
To clarify, one scenario would have a block encased in the with statement, while another possibility would be the same block, but not encased (i.e., as if it wasn't indented)
Initial experiments of course give indentation errors..
Python 3.3 and above
Python 3.3 introduced contextlib.ExitStack for just this kind of situation. It gives you a "stack", to which you add context managers as necessary. In your case, you would do this:
from contextlib import ExitStack
with ExitStack() as stack:
if needs_with():
gs = stack.enter_context(get_stuff())
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
Anything that is entered to stack is automatically exited at the end of the with statement as usual. (If nothing is entered, that's not a problem.) In this example, whatever is returned by get_stuff() is exited automatically.
If you have to use an earlier version of python, you might be able to use the contextlib2 module, although this is not standard. It backports this and other features to earlier versions of python. You could even do a conditional import, if you like this approach.
Python 3.7 and above
Python 3.7 further introduced contextlib.nullcontext (a couple years after this answer was originally posted, and since that time mentioned in several other answers). In the comments, #Kache points out the most elegant usage of this option:
from contextlib import nullcontext
with get_stuff() if needs_with() else nullcontext() as gs:
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
Note that if needs_with() is False, then gs will be None inside the context block. If you want gs to be something_else in that case, you just replace nullcontext() with nullcontext(something_else).
This approach is obviously not as flexible as ExitStack, because this is just a binary choice, whereas ExitStack allows you to add as many exiting things as you want, with complicated logic and so on. But this certainly answers the OP's simple requirements.
If you want to avoid duplicating code and are using a version of Python prior to 3.7 (when contextlib.nullcontext was introduced) or even 3.3 (when contextlib.ExitStack was introduced), you could do something like:
class dummy_context_mgr():
def __enter__(self):
return None
def __exit__(self, exc_type, exc_value, traceback):
return False
or:
import contextlib
#contextlib.contextmanager
def dummy_context_mgr():
yield None
and then use it as:
with get_stuff() if needs_with() else dummy_context_mgr() as gs:
# do stuff involving gs or not
You alternatively could make get_stuff() return different things based on needs_with().
(See Mike's answer or Daniel's answer for what you can do in later versions.)
As of Python 3.7 you can use contextlib.nullcontext:
from contextlib import nullcontext
if needs_with():
cm = get_stuff()
else:
cm = nullcontext()
with cm as gs:
# Do stuff
contextlib.nullcontext is pretty much just a no-op context manager. You can pass it an argument that it will yield, if you depend on something existing after the as:
>>> with nullcontext(5) as value:
... print(value)
...
5
Otherwise it'll just return None:
>>> with nullcontext() as value:
... print(value)
...
None
It's super neat, check out the docs for it here: https://docs.python.org/3/library/contextlib.html#contextlib.nullcontext
A third-party option to achieve exactly this:
https://pypi.python.org/pypi/conditional
from conditional import conditional
with conditional(needs_with(), get_stuff()):
# do stuff
import contextlib
my_context = None # your context
my_condition = False # your condition
# Option 1 (Recommended)
with my_context if my_condition else contextlib.nullcontext():
print('hello 1')
# Option 2
with my_context if my_condition else contextlib.ExitStack():
print('hello 2')
You can use contextlib.nested to put 0 or more context managers into a single with statement.
>>> import contextlib
>>> managers = []
>>> test_me = True
>>> if test_me:
... managers.append(open('x.txt','w'))
...
>>> with contextlib.nested(*managers):
... pass
...
>>> # see if it closed
... managers[0].write('hello')
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
ValueError: I/O operation on closed file
This solution has its quirks and I just noticed that as of 2.7 its been deprecated. I wrote my own context manager to handle juggling multiple context managers. Its worked for me so far, but I haven't really considered edge conditons
class ContextGroup(object):
"""A group of context managers that all exit when the group exits."""
def __init__(self):
"""Create a context group"""
self._exits = []
def add(self, ctx_obj, name=None):
"""Open a context manager on ctx_obj and add to this group. If
name, the context manager will be available as self.name. name
will still reference the context object after this context
closes.
"""
if name and hasattr(self, name):
raise AttributeError("ContextGroup already has context %s" % name)
self._exits.append(ctx_obj.__exit__)
var = ctx_obj.__enter__()
if name:
self.__dict__[name] = var
def exit_early(self, name):
"""Call __exit__ on named context manager and remove from group"""
ctx_obj = getattr(self, name)
delattr(self, name)
del self._exits[self._exits.index(ctx_obj)]
ctx_obj.__exit__(None, None, None)
def __enter__(self):
return self
def __exit__(self, _type, value, tb):
inner_exeptions = []
for _exit in self._exits:
try:
_exit(_type, value, tb )
except Exception, e:
inner_exceptions.append(e)
if inner_exceptions:
r = RuntimeError("Errors while exiting context: %s"
% (','.join(str(e)) for e in inner_exceptions))
def __setattr__(self, name, val):
if hasattr(val, '__exit__'):
self.add(val, name)
else:
self.__dict__[name] = val
It was hard to find #farsil's nifty Python 3.3 one-liner, so here it is in its own answer:
with ExitStack() if not needs_with() else get_stuff() as gs:
# do stuff
Note that ExitStack should come first, otherwise get_stuff() will be evaluated.
So I made this code;
It is invoked like so:
with c_with(needs_with(), lambda: get_stuff()) as gs:
##DOESN't call get_stuff() unless needs_with is called.
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
Properties:
it does not call get_stuff() unless condition is true
if condition is false, it provides a dummy contextmanager. (could probably be replaced with contextlib.nullcontext for python >= 3.7)
Optionally you can send in an alternative contextmanager in case the condition is false:
with c_with(needs_with(), lambda: get_stuff(), lambda: dont_get_stuff()) as gs:
Hope this will help someone!
-- Here is the code:
def call_if_lambda(f):
"""
Calls f if f is a lambda function.
From https://stackoverflow.com/a/3655857/997253
"""
LMBD = lambda:0
islambda=isinstance(f, type(LMBD)) and f.__name__ == LMBD.__name__
return f() if islambda else f
import types
class _DummyClass(object):
"""
A class that doesn't do anything when methods are called, items are set and get etc.
I suspect this does not cover _all_ cases, but many.
"""
def _returnself(self, *args, **kwargs):
return self
__getattr__=__enter__=__exit__=__call__=__getitem__=_returnself
def __str__(self):
return ""
__repr__=__str__
def __setitem__(*args,**kwargs):
pass
def __setattr__(*args,**kwargs):
pass
class c_with(object):
"""
Wrap another context manager and enter it only if condition is true.
Parameters
----------
condition: bool
Condition to enter contextmanager or possibly else_contextmanager
contextmanager: contextmanager, lambda or None
Contextmanager for entering if condition is true. A lambda function
can be given, which will not be called unless entering the contextmanager.
else_contextmanager: contextmanager, lambda or None
Contextmanager for entering if condition is true. A lambda function
can be given, which will not be called unless entering the contextmanager.
If None is given, then a dummy contextmanager is returned.
"""
def __init__(self, condition, contextmanager, else_contextmanager=None):
self.condition = condition
self.contextmanager = contextmanager
self.else_contextmanager = _DummyClass() if else_contextmanager is None else else_contextmanager
def __enter__(self):
if self.condition:
self.contextmanager=call_if_lambda(self.contextmanager)
return self.contextmanager.__enter__()
elif self.else_contextmanager is not None:
self.else_contextmanager=call_if_lambda(self.else_contextmanager)
return self.else_contextmanager.__enter__()
def __exit__(self, *args):
if self.condition:
return self.contextmanager.__exit__(*args)
elif self.else_contextmanager is not None:
self.else_contextmanager.__exit__(*args)
#### EXAMPLE BELOW ####
from contextlib import contextmanager
def needs_with():
return False
#contextmanager
def get_stuff():
yield {"hello":"world"}
with c_with(needs_with(), lambda: get_stuff()) as gs:
## DOESN't call get_stuff() unless needs_with() returns True.
# do nearly the same large block of stuff,
# involving gs or not, depending on needs_with()
print("Hello",gs['hello'])
I have found that the #Anentropic answer is incomplete.
from conditional import conditional
a = 1 # can be None
if not a is None:
b = 1
class WithNone:
def __enter__(self):
return self
def __exit__(self, type, value, tb):
pass
def foo(x):
print(x)
return WithNone()
with conditional(not a is None, foo(b) if not a is None else None):
print(123)
The complete conditional usage required 3 conditions instead of 1 because of:
NameError: name 'b' is not defined in case if not defined a
the function foo still must return enterable object, otherwise: AttributeError: 'NoneType' object has no attribute '__enter__'
In a class method I have a set of possible options for a single keyword argument, each with a different algorithm to calculate something. To check which option has been added to the keyword I made a chain of if, elif, else too find the keyword option provided.
class MyClass:
def my_method(self, my_parameter, my_keyword='spacial'):
if my_keyword == 'spacial':
print('Cool stuf')
elif my_keyword == 'discoidal':
print('OTHER cool stuff')
elif my_keyword == 'temporal':
print('You get the gist')
else:
print('not in options list')
In my opinion this is not a very elegant way to code this. Especially if the options list keeps growing. Is there a way to omit the list of if, elif, elif, else statements?
Use a dictionary:
def cool_stuff(param):
...
def other_cool_stuff(param):
...
def you_get_the_gist(param):
....
dispatch_mapping = {
'spacial': cool_stuff,
'discoidal': other_cool_stuff,
'temporal': you_get_the_gist
}
Somewhere else:
def my_method(self, param, keyword='spacial'):
handler = dispatch_mapping.get(keyword)
if handler is None:
raise Exception("No handler for %s" % keyword)
return handler(param)
There exists always at least one place where you have to divide the cases.
In your case you set a string and then compare it again.
A "way" around that would be replace setting your string by different function/method calls directly instead of routing it inside a function. Or use a dictionary to map strings to a function call.
Using dictionary is a good idea. However, another option is reflection which function be called by string.
class MyClass:
def handle_spacial(self):
print('Cool stuf')
def handle_discoidal(self):
print('OTHER cool stuff')
def handle_temporal(self):
print('You get the gist')
def default(self):
print('not in options list')
def my_method(self, my_parameter, my_keyword='spacial'):
function_name = "handle_"+my_keyword
if hasattr(self, function_name):
getattr(self, function_name)()
else:
self.default()
The best way to to create a dictionary of keywords and options to be displayed and use in the following way:
>>> class MyClass:
... global mykey_option
... mykey_option={'spacial':'Cool stuf','discoidal':'OTHER cool stuff','temporal':'You get the gist'}
... def my_method(self, my_parameter, my_keyword='spacial'):
... try:
... print(mykey_option[my_keyword])
... except:
... print('not in options list')
...
>>> x = MyClass()
>>> x.my_method(1,'discoidal')
OTHER cool stuff
>>> x.my_method(1,'spacial')
Cool stuf
>>> x.my_method(1,'temporal')
You get the gist
>>> x.my_method(1,'newstring')
not in options list