Conditional imports are somewhat common in code that supports multiple platforms, or code that supports additional functionality when some extra bonus modules are available. Today this is often written as:
# pylint: disable=import-not-at-top
try:
from monty.python.au import bruce
else:
bruce = None
Four lines of code, plus a potential linter directive asking tooling not to whine about logic before imports or non top level imports.
What if we could simply write any variant of the following:
from monty.python.au import bruce else None
from monty.python.au import bruce else bruce = None
from monty.python.au import michael_baldwin as bruce else None
from monty.python.au import michael_baldwin as bruce else bruce = None
import michael_baldwin else None
import michael_baldwin else michael_baldwin = None
import michael_baldwin as bruce else bruce = None
import michael_baldwin as bruce else None
import monty.python.au else None # confusing...
import monty.python.au else monty = None # yuck
I’m using an else to convey the condition what to do on import failure instead of the try+except. I don’t really like that the name has to be repeated for the most natural reading but I dislike the way it reads without the name = expr to convey the conditional alternate name binding. I’d limit the else to only capturing ImportError though you could argue that it should be a broad except.
I’m also suggesting not supporting else on anything other than a simple single name import. import long.dotted.name could be ineligible for else because it feels awkward, though so long as the else name = expr syntax were chosen it’d still be comprehensible. Want to import multiple names from a package in a single import statement? I suggest not supporting else on that; though that should work fine via tuple assignment. having such things to not support is probably more complicated.
things to avoid? I don’t want anyone to write a conditional expression after the else. Or to allow anything other than simple = assignment if adopting that syntax (no augassign).
thoughts? a quick search didn’t turn this up as being proposed in the past but I didn’t look far.
unresolved thoughts. sometimes the alternate of a conditional import is a different import.
import json else import simplejson as json
Include nested conditional import statements as part of this?
A change like this should have heftier motivation than replacing four lines of code with one.
A great example is PEP 380: yield from usually replaces just two lines, for v in g: yield v. If it was only that, it would have been rejected (at least under the old governance). The reason for adding yield from was that it also solves various more subtle problems. With then new command, the correct way to write it is also the most obvious – and that wasn’t true before.
I fear that’s not the case in your proposal. The import ... else ... doesn’t do anything more than the four lines it replaces. And it’s less flexible – the four lines can be trivially extended to things like:
Handling other kinds of errors than ImportError
Importing a fallback module instead (as you mentioned)
Choosing a module based on a condition (if for some reason you need to look before you leap instead of preparing to ask for forgiveness)
Having two optional modules, and importing either both or neither of them
If it’s the linter that bothers you, look into fixing your linter (and possibly into adjusting PEP 8 before that, to codify the right way to format conditional imports).
I like the idea of this syntactic sugar. Conditional imports are very common, especially when writing multi-version code.
One possibility I thought of a long time ago was to allow any number of module names separated by or, with the name to bind to mandatory:
import json or simplejson as json
Adding an optional else clause would be neat:
import json or simplejson as json else expression
which would be equivalent to:
try:
import json as json
except:
try:
import simplejson as json
except:
json = expression
I expect that if this idea goes anywhere, there will be a huge amount of bike-shedding on the syntax and semantics and a lot of opposition since it doesn’t really add anything new to the language but I think its a nice bit of syntactic sugar which would improve readability a lot.
Obvious bikeshedding: except: or except Exception: or except ImportError:?
The idea seems pretty convenient, and relatively innocuous. But I expect it’ll go nowhere, mainly because there simply isn’t enough benefit to get people enthusiastic about it. One particular issue is that this sort of conditional import is typically needed by library authors rather than end users, and library authors are the least likely to be able to use the syntax (because of the need to support older versions of Python). That’s not to say we shouldn’t be investing in future improvements, just to reiterate that it’ll be hard to get much support for the idea.
Whether this counts as an unhealthy tendency towards stagnation, or a sensible counterbalance to overenthusiastic levels of change in a mature and widely used language, I honestly don’t know…
I don’t know. Since optional imports are important to notice, I prefer to have the try..except block spelled out explicitly. Readability trumps concision here.
(but of course, now that we have the := operator, that argument will be harder and harder to make :-/)
Personally, I’m quite fond of this syntax and the conditional approach to imports. Perhaps another alternative could be the ternary conditional syntax that Python already uses, but applied to the context of imports:
import json if json else simplejson as json
I’m not certainly if this is intuitively clear, but in my opinion it looks quite clean and fits well with the general design style of Python.
While this argument makes perfect sense from a purely functional perspective, there is an argument to be made from a design perspective. This is a contested area within the programing community in general, but exceptions usually are best suited to situations which are actually exceptional. If the developer wants to assess the existence of multiple modules and use them in an order or preference, this seems to be more suited to control flow in a conditional rather than an exception. It does seem to be somewhat counter-intuitive to rely on exceptions for determining import alternatives.
This ultimately comes down to design preference, but in general it is my opinion that exceptions should actually be exceptional. When control flow with conditionals can be utilized instead, they should be a preferable alternative. Especially in Python, language styling and design are incredibly important components to consider, even if there is no functional difference. When implemented correctly, syntactic sugar can significantly improve readability.