4

I'm not very familiar with the use cases for Python's __future__ module, only that I've seen it in demo codes on some online articles. After reading around, I guess it has something to do with future-proofing existing codes? I admit I don't fully understand what I'm reading. According to the official documentation, its functions are:

  • To avoid confusing existing tools that analyze import statements and expect to find the modules they’re importing.
  • To ensure that future statements run under releases prior to 2.1 at least yield runtime exceptions (the import of future will fail, because there was no module of that name prior to 2.1).
  • To document when incompatible changes were introduced, and when they will be — or were — made mandatory. This is a form of executable documentation, and can be inspected programmatically via importing future and examining its contents.

What does point 1 mean? Is it saying that if an interpreter reads an import statement, and doesn't find the module it is importing, having import __future__ will prevent it from throwing an error?

With Python 2.7 officially end-of-life on 1 Jan 2020, if I have no intent to support Python 2 environments in my Python 3 code, then I assume point 2 is not relevant to me.

Point 3 is for...debugging's sake, I guess? Is that what it's trying to say?

I noticed that all of the articles I've read so far are dated at least half a decade ago. All of the examples focus on Python 3 codes handling Python 2 backward compatibility, specifically print and //. But like I said, I've no intent to support Python 2.

What is __future__ in Python used for and how/when to use it, and how it works

So, when do I need to use import __future in Python 3 development if there's no Python 2 backward-compatibility intended? Do I even need it anymore in such a case?

thegreatjedi
  • 2,788
  • 4
  • 28
  • 49
  • 2
    Have you checked the [documentation](https://docs.python.org/3/library/__future__.html)? – Thierry Lathuille Oct 10 '20 at 06:54
  • 2
    If you’re starting with a modern Python version, today you’re only possibly going to need the `annotations` future. That doesn’t preclude Python from introducing new futures in the… future. – deceze Oct 10 '20 at 06:57
  • It can be used to use features which will appear in newer versions while having an older release of Python. – Sivaram Rasathurai Oct 10 '20 at 07:21
  • @ThierryLathuille Yeah I did, and from my understanding if I don't care about supporting Python 2, then I won't need it, right? Or is there still other uses for it? – thegreatjedi Oct 10 '20 at 10:46
  • @rcvaram Are you saying that if I have, for example, a Python 3.3 interpreter installed, and it reads `import __future__` in the script, it'll be able to use Python 3.6 f-strings without needing Python 3.6 installed? – thegreatjedi Oct 10 '20 at 10:48
  • @deceze How does `__future__` for the current interpreter know what's in the future? Hypothetically, if Python 3.12 introduces functionality for quantum computing, and a Python 3.3 interpreter reads the code written by a Python 3.12 developer that contains `__future__`, what happens? – thegreatjedi Oct 10 '20 at 11:02
  • `__future__` is a compatibility thing. When the Python developers decide they want to change an existing behavior, they can’t necessarily do so right away if it would break existing code. So they plan to change that behavior in some future version of Python, and you can already opt in to that change today through `__future__`. So your code will continue to behave the same when that switch actually occurs. This gives developers enough time to update their code between announcing the change and implementing the change, which can sometimes be years. – deceze Oct 10 '20 at 11:47
  • @deceze So, as an example, if I use `import __future__` in Python 3.5, it allows me to use f-strings that's introduced in Python 3.6 without needing to install the latter? – thegreatjedi Oct 10 '20 at 16:24
  • No, it’s not arbitrary. f-strings where a completely new feature without backward compatibility concerns. In 3.5 they didn’t exist, in 3.6 they exist. Backporting them to earlier versions makes little sense. The current example is that the behavior of annotations is scheduled to change in 3.10. That is planned in advance. You can update your code today and use `__future__.annotations` to already opt into the new behavior in advance and future proof your code. This `__future__.annotations` was introduced in Python version 3.7 as a new feature as well. – deceze Oct 10 '20 at 17:24
  • @deceze So what you're saying is that `annotations` is unofficially available for use since Python 3.7 via `import __future__.annotations` and it'll be officially available since 3.10 via `import annotations` is that how it goes? – thegreatjedi Oct 11 '20 at 06:08
  • No! The behavior of how annotations work (a syntax feature, e.g. `foo: bar = 'baz'`) *will* change subtly in 3.10. If you write code today where that change is meaningful, you can opt into the new behavior today by importing the future. Importing the future after 3.10 simply does nothing and your code continues to behave with the then default new behavior. Every developer whose code might break due to this change has until 3.10 to update their code to work with the new behavior. – deceze Oct 11 '20 at 06:18
  • @deceze What I mean is: `import __future__.annotations` in Python 3.7 will allow me to use Python 3.10's version of `import annotations`? And if `__future__` is meant for a developer to opt-in to changes, then what happens if a user with Python 3.5 runs this 3.7 code that contains `import __future__.annotations`? – thegreatjedi Oct 11 '20 at 07:14
  • It won’t work because that future doesn’t exist in 3.5. – deceze Oct 11 '20 at 07:25
  • @deceze So a user is still confined to requiring Python 3.7 and above to run the scripts, but a developer can use `__future__.annotations` to run Python 3.10's functionality without actually upgrading his development environment to Python 3.10? – thegreatjedi Oct 11 '20 at 07:29
  • It’s not about “upgrading”. 3.10 doesn’t exist yet, you couldn’t “upgrade to it” if you wanted to. It’s about this: you write code today which expects behavior A. You keep that code running for many years, on different computers, on newer and newer Python versions over time. Eventually that behavior is changed to behavior B in a Python version and your code breaks. To avoid that, the Python developers are announcing such changes way in advance and give you a way to prepare your code for that change by opting into behavior B early. – deceze Oct 11 '20 at 07:39
  • This way you have code which works all the way from 3.7 to 3.10+. Otherwise you’d need to produce two different versions of your code, one which works on 3.9- and one for 3.10+. – deceze Oct 11 '20 at 07:41
  • @deceze So if I use `import __future__.annotations` when developing in Python 3.7, I'll be using the 3.10 version of `annotations`, and any user with Python 3.7 and higher installed will be able to run this code correctly? Or do I still need to upgrade my development environment to Python 3.10 and change my code from `import __future__.annotations__` to `import annotations` in order for users with 3.10 and higher to use the script? – thegreatjedi Oct 11 '20 at 08:00

3 Answers3

3

What's the point of __future__?

__future__ allows introducing new behavior into the language that would be backwards-incompatible. For example, in Python 2.2 - 2.7 you could do:

>>> 3/2
1
>>> from __future__ import division
>>> 3/2
1.5

__future__ means "we added this feature into the language, but we can't make it the default because it would break other code".

Why was __future__ introduced originally?

Let's say you have a Python 2 project, and want to transition it to Python 3. Rather than converting it in one "big bang" and hoping it still behaves the same under Python 3, developers can upgrade their project piece-by-piece, by first importing division for example and testing for regressions, then importing print_function and so on.

What is the use of __future__ in Python 3?

A "Python 4" release is not on the horizon. But two features have already been added to Python 3 this way:

+----------------|---------------|----------------+
|    feature     | __future__ in | implemented in |
|----------------|---------------|----------------|
|----------------|---------------|----------------|
| generator_stop |      3.5      |      3.7       |
|----------------|---------------|----------------|
|  annotations   |      3.7      |      3.10      |
+----------------|---------------+----------------+

Both of these changes were backwards-incompatible. This means the language maintainers made a big change to how generators worked that was ready for release in 3.5. However they "hid" it in the __future__ module, because they were worried a bunch of packages would break upon 3.5's release if they put it out directly.

There are always smaller backwards-incompatible changes in Python, for example the introduction of the async and await keywords in 3.7, which makes code that used them as variable names (async = 1) break. It is really a judgment call whether to introduce a feature directly, or as a __future__, depending on how much effort a migration is.

Thus putting it a feature in a __future__ with a DeprecationWarning allows for a transition period for maintainers, which allows opting-in to the feature early.

xjcl
  • 12,848
  • 6
  • 67
  • 89
1

Software changes

This is the fundamental problem this all deals with. Software is always changing. It's soft after all. When you write some code today and run it, it behaves a certain way. Next week you'll decide that you don't like what it does and that it needs some changes. You change it, and run it, and it behaves differently now. If you'd be following a proper release workflow, you'd tag your software with version numbers and have now produced two different versions of your software.

Python is also just software, written by people which are just programmers. The impact of changes in the Python language are obviously a bit different than you changing your little application which you only run yourself. If the Python developers decided tomorrow that the next version of Python required semicolons at the end of every line to be more like other languages, it would require thousands and thousands of software developers to rewrite millions upon millions of lines of code to make it compatible with that new version of Python. Obviously that would be insane, so the Python developers aren't going to make such drastic changes and will ensure a reasonable amount of backwards compatibility with existing code.

Why changes?

So, why change anything at all? Why can't we just keep the current version of Python and the current version of our code and keep running that forever as is? Well, you could. That's a perfectly fine strategy, within limits. There are three main categories of change, which are reflected in the Python versioning scheme:

  • X.Y.Z version changes: when Python version 3.7.1 is updated to 3.7.2, that's because bugs have been found in 3.7.1 that required fixing. Often these are security patches. Existing code should keep working as is, unless it has been directly affected by the bug in question. You should always keep your Python version in production updated with the latest patch version, to ensure you're not falling pray to security vulnerabilities.
  • X.Y version changes: when Python releases a new version like 3.8, this is to introduce new features. You'll want to upgrade to that version if you're interested in using those new features, because they make your work easier or your code nicer. Those versions also sometimes introduce breaking changes, e.g. removing deprecated functions or changing existing functions in minor ways. While you're consciously updating to that new version because of new features anyway, you can also double check whether you might be affected by breaking changes and fix your code accordingly.
  • X version changes. This is typically almost akin to an entirely new language, which happens very rarely. Python 3 introduced many changes over Python 2 which made a lot of code simply incompatible and required sometimes substantial rewriting of existing code. As a benefit, Python 3 contained significant improvements which made the switch worthwhile.

There are entire pages in the documentation devoted to those changes, and you should peruse them occasionally, and especially before any major upgrades: https://docs.python.org/3/whatsnew/index.html

So there are various incentives for keeping up with changes in the Python language, though they're all optional. If you want to stay on one specific Python version forever and ever, you can. Until you need to replace the hardware it's running on and you find that that version isn't compatible with your new replacement hardware…

Compatibility

There are different compatibility concerns. When new features are introduced, that introduces a minimum required version for any code using that feature. E.g., this code:

print(f'foo {bar} baz')

This uses f-strings, a syntactic feature introduced in Python 3.6. If you write this code, you require at least Python 3.6 to run it. Python 3.5 refuses to run this code, as it has no idea what f-strings are. However, this kind of change has no backwards compatibility concerns. The f'' syntax is simply a syntax error in older Python versions and simply doesn't work.

Changes which work on older versions of Python, but behave differently, are more of a concern. The current issue is with annotations:

foo: bar = 'baz'

The behaviour of this will change subtly in Python 3.10. That is a change that has been decided upon and that's coming. Starting in Python 3.10, the above code will behave like:

foo: 'bar' = 'baz'

In other words, annotations will be evaluated as strings, not as objects. For most developers that will make little difference, and will in fact solve certain issues with annotations as they work today. Hence why this change is being made. However, depending on what you do with annotations, this change can also break your code.

Futures

And thus we finally come to futures: you can opt into the new behaviour of annotations today if you're running at least Python 3.7 by importing the appropriate future:

from __future__ import annotations

If you do this in your code, your code will already evaluate annotations as strings today. This behaviour will become on-by-default in Python 3.10. In other words, this change has been implemented in Python 3.7, but is optional and opt-in. It will become mandatory in 3.10. At that point, from __future__ import annotations will simply do nothing; whether or not you specify it, your code will behave one way and one way only.

This gives developers three major versions, or about three full years time, to update their code if they need to. That way nobody will be surprised by their code breaking if and when they try to run their existing code on Python 3.10 one day. (Unless they've been living under a rock those past years, and there will be people like that, unfortunately. But the impact will be greatly minimised.)

So, that's what futures are for. They implement breaking changes, but hide those changes behind an explicit opt-in import, in order to allow a sliding window within which developers can upgrade their code so as not require thousands of developers to have to rewrite their code in a panic when they're suddenly confronted with breaking code in production because their production Python version got upgraded for some reason or another.

And it's not just a direct relationship between the Python developers and coders like yourself; you'll often depend on 3rd party libraries, and those will need to be updated too. Every change to the Python language has a slow trickle-down effect from Python to library developers to repository maintainers to coders like you. This simply requires time, and futures buy this kind of time.

There have been a ton of those kinds of changes in the years when Python was transitioning from Python 2 to Python 3, and many of the breaking changes introduced in Python 3 could slowly be transitioned into with the use of futures. At the time of writing, there's only the aforementioned annotations change which is currently of active concern. But that doesn't preclude Python from introducing other, new, breaking changes which will be rolled out through futures in… the future.


Those points you cite from the documentation that you don’t understand aren’t very important and are just internal implementation details. In a nutshell, even though futures are opted into using from ... import ... statements, those aren’t actual module imports. They’re compiler flags that just look like they import an actual module. (An actual module import wouldn’t have the kind of power to make the changes those futures actually effect.) What you cite from the documentation is merely explaining that because those aren’t actual imports, some tools may get confused if they do try to treat those like actual module imports. For that purpose alone, there does exist a valid “__future__” module, and that documentation you cite is just about that and explains why it exists even when it has no actual effect.

deceze
  • 510,633
  • 85
  • 743
  • 889
  • "*Software is always changing. It's soft after all.*" Stability is a good quality, particularly in programming languages and tools. The "soft" in software is not about its rate of change (which ideally would be zero) but about how easy is to perform such a change. – Acorn Oct 11 '20 at 09:22
  • Sure, it doesn’t change *by itself*, that would be… worrisome. But it’s *malleable* and is being changed little by little over time. The code that is written once and never changed is often simply not interesting to anyone. – deceze Oct 11 '20 at 09:28
  • Being malleable does not imply changing over time. In fact, code that never changes is *ideal* (and if it was written only once, even better). I don't see how that makes it uninteresting (nor why being interesting or not matters, to be honest). – Acorn Oct 11 '20 at 09:48
  • I’m speaking from the perspective of software project management. A project like Python is interesting to a lot of people, and many people contribute enhancements to it, so it changes over time. Yes, in certain areas write-once stable software is ideal and to some degree required, but that’s certainly not the Python ecosystem. – deceze Oct 11 '20 at 10:02
  • Given the time is took for me to scroll through the answer on my phone, I suspect no one will read it entirely :) I can only imagine the effort that went behind writing it. – Abhijit Sarkar Oct 11 '20 at 10:12
  • @deceze I'm *also* speaking from the perspective of software project management. Python is not interesting because it changes over time, but because it solves a problem. It is stability, and not change, what enables Python projects to exist to begin with. It is also why transitions like Python 2 -> 3 are painful and take too long. – Acorn Oct 11 '20 at 11:59
  • @deceze Claiming that the Python ecosystem has some kind of "always changing" positive quality is misleading and hurts the image of Python. The merits of all its changes are considered against the complexity and liability they introduce, for good reason. – Acorn Oct 11 '20 at 12:12
  • @Acorn The topic of futures is precisely about managing changes. If Python didn’t change, it wouldn’t need futures. So introducing this answer with an explanation of changing software makes sense, no? I’m not sure why you’re arguing against the reality of such changes and transitions. – deceze Oct 11 '20 at 12:18
  • @deceze Yeah, `__future__` is used to ease working around breaking changes, i.e. to *increase* stability. I haven't argued anything about changes happening, but about the reasons behind those changes and about the idea that Python is somehow embracing change for the sake of change. – Acorn Oct 11 '20 at 13:31
  • @deceze Concerning your question, I think it is always useful to give some background on any answer, but this one goes on several tangential topics in length that are not really needed (and trigger off-topic discussions like the one we just had :) By the way, where does the word "futures" come from? The module is called future because it is about importing breaking changes from the future, but I think it is the first time I have seen somebody call the features themselves "futures". – Acorn Oct 11 '20 at 13:41
  • @Acorn After my lengthy back and forth with the OP in the question comments, I was inspired to write this answer, as I believe it would help OP understand the topic. Maybe it won’t, tough luck. Whatever you’re reading into it I have no idea. – deceze Oct 11 '20 at 14:39
1

Use __future__ when you want to be proactive in modernizing your code.

In many cases, you will find yourself using it when you deem a new feature worth it for your project, yet you still need to support older Python versions.

However, you can also use it to reduce your tech debt around future breaking changes as well as avoiding version splits in your project.

Acorn
  • 24,970
  • 5
  • 40
  • 69