Movatterモバイル変換


[0]ホーム

URL:


insert (other file), via genhtml tag or manual(AFTER top banner or del initial

, else Firefox alone increases top spacing)5) Add at end, so space for Top without covering last content-->Python Changes 2014-2024 by Mark Lutz

Frankenthon! 

Python
Changes
2014-2024

Latest substantial revision: July 2025

This page documents and critiques changes made to Python from 2014 to 2024 and between the 5th and6th Editions of the bookLearning Python. Because that book's5th Editionwas updated for Pythons 3.3 and 2.7 and the 2.X line is effectively frozen, this page coversPythons 3.4 through 3.12, the latter of which is integrated into the6th Edition. Earlier changes are documented in the book's 5th Edition, but for brief summaries,see its Appendix C or this site's pages for Python3.3,3.2, and2.7. A separate page for Python 3.13+ changes may appear in time.

There's a wealth of content here for Python readers, but if your time is tight and you'relooking for suggested highlights, be sure to catch theintro,the coverage of new formatting tools in3.6 and3.5,recent newshere andhere,and the essays on 3.5+type hints andcoroutines. For the full story, browse this page's contents:

 

Introduction: Why This Page?

Changes in Python 3.10+(Jan-2022+)

Changes in Python 3.9(Oct-2020)

Changes in Python 3.8(Oct-2019)

Changes in Python 3.7(Jun-2018)

  1. General Rehashings and Breakages
  2. StopIteration Busted in Generator Functions

Changes in Python 3.6(Dec-2016)

  1. Yet Another String Formatting Scheme: f'...'
  2. Yet Another String Formatting Scheme: i'...'?
  3. Windows Launcher Hokey Pokey: Defaults
  4. Tk 8.6 Comes to Mac OS Python—DOA
  5. Coding Underscores in Numbers
  6. Etcetera: the Parade Marches On

Changes in Python 3.5(Sep-2015)

  1. Matrix Multiplication: "@"
  2. Bytes String Formatting: "%"
  3. Unpacking "*" Generalizations
  4. Type Hints Standardization [essay]
  5. Coroutines: "async" and "await" [essay]
  6. Faster Directory Scans with "os.scandir()"?
  7. Dropping ".pyo" Bytecode Files
  8. Windows Install-Path Changes
  9. Tk 8.6 and tkinter: PNGs, Dialogs, Colors
  10. Tk 8.6 Regression: PyEdit Thread Crashes
  11. File Loads Seem Faster (TBD)
  12. Docs Broken on Windows, Incomplete
  13. Socket sendall() and smtplib: Timeouts
  14. Windows Installer Drops Support for XP

Changes in Python 3.4(Mar-2014)

  1. New Package Installation Model: pip
  2. Unpacking "*" Generalizations? (to 3.5)
  3. Enumerated Type as a Library Module
  4. Import-Related Library Module Changes
  5. More Windows Launcher Changes, Caveats
  6. Etc: statistics, File Descriptors, asyncio,...

 

Introduction: Why This Page?

The 5th Edition ofLearning Python published in mid-2013 has been updated to be current with Pythons 3.3 and 2.7. Especiallygiven its language-foundations tutorial role, this book should address the needs of all Python 3.X and 2.X newcomers for many years to come.

Nevertheless, the inevitable parade of changes that seems inherent in open source projects continues unabated in each new Python release. Many such changes are trivial—and often optional—extensions which will likely see limited use and may be safely ignored by newcomers until they become familiar with fundamentals that span all Pythons.

The Downside of Change

But not all changes are so benign; in fact, parades can be downright annoyingwhen they disrupt your day. Those downstream from developer cabals have legitimate concerns. To some, many recent Python extensions seemfeatures in search of use cases—new features considered clever by their advocates but which have little clear relevance to real-world Python programsand complicate the language unnecessarily. To others, recent Python changes arejust plain rude—mutations that break working code with no more justification than personal preference or ego.

This is a substantial downside of Python's dynamic, community-driven development model, which is most glaring to those on the leading edge of new releases, and which the book addresses head-on, especially in its introduction and conclusion(Chapters 1 and 41).As told in the book, apart from the lucky few who are able to stick with a single version for all time, Python extensions and changes have amassive impact on the language's users and ecosystem. Language mods must be:

While the language is still usable for a multitude of tasks, Python's rapid evolutionadds additional management work to programmers' already-full plates and oftenwithout clear cause.

Perhaps worst of all,newcomers face the full force of accumulated flux and growth in the latest and greatest release at the time of their induction. Today, the syllabus for new learners includestwo disparate lines, with incompatibilities even among the releases of a single line;multiple programming paradigms, with tools advanced enough to challenge experts;and a torrent of feature redundancy, with 4 or 5 ways to achieve some goals—all fruits of Python's shifting story thus far.

In short, Python's constant change has created a softwareTower of Babel, in which the very meaning of the language varies per release. This leaves its users with an ongoing task: even after you've mastered the language, new Python mutations become required reading for you if they show up in code you encounter or use and can become a factor whenever you upgrade to Python versions in which they appear.

Consequently, this page briefly chronicles changes that appeared in Python after the 5th Edition's June 2013 release, as a sort of virtual appendix to the book.Hopefully, this and other resources named here will help readers follow the route of Python change—wherever the parade may march next.

The Value of Criticism

An editorial note up front: because changing a tool used by many comes with accountability,this page alsocritiques while documenting. Though subjective, its assessments are both fair and grounded in technical merit, and they reflect the perspective of someone who has watched Python evolve and been one of its foremostproponents since1992and still wishes only the best for its future. Despite what you may have heard,informed criticism is both okay and crucial when its goal is improvement.

Programming language design is innately controversial, and you should weigh for yourself the potential benefits of each change noted here against itsimpacts on knowledge requirements and usability. At the end of the day, though, we can probably all agree thatcritical thinking on this front is in Python's best interest. The line between thrashing and evolution may be subjective, but drawing it carefully is as vital to the language's future as any shiny new feature can be.

Wherever you may stand on a given item below, this much seems certain:a bloated system that is in a perpetual state of change may eventually be of more interest to its changers than its prospective users.If this page encourages its readers to think more deeply about such thingswhile learning more about Python, it will have discharged its role in full.

 

Changes in Python 3.10+ (Jan-2022+)

The latest from the kitchen-sink design school: Python 3.10 isout,with the usual set of opinion-based changes and deprecations—including the usual set of painfully academic tweaks to 3.X's constantly morphing typehintsandcoroutines. Most 3.10 changes aren't worth covering here, but one meritsa callout.

match Python: case Bloat:

Python 3.10 has sprouted a multiple-choicematch/casestatement—a sort of "switch" on steroids—after getting by quite well without one for three decades. As usual, the new statement convolutes the language and adds to its learning requirements for no better reason than the mood of a handful of developers. And as usual, this addition was justified by the non-sequitur argument that other languages do it too; per the PEP:

Motivation(Structural) pattern matching syntax is found in many languages, from Haskell,Erlang and Scala to Elixir and Ruby. (A proposal for JavaScript is also under consideration.) [...]

More fundamentally, this is yet another redundant extension sans user need.Structural pattern matching is a wildly ad-hoc answer to a question nobody seems to have asked, and there have always been simple ways to code multiple-choicelogic in Python—all of which will certainly continue to see more widespread use than the new and curiously complex alternative.Python, after all, managed to rise to the top of the programming-languagesheapand become the go-to tool for everything under thesun,without the new statement.This is surely not the hallmark of a utility deficit.

All of which has been said here before (see the 3.9reviewfor an expanded cut). In the interest of brevity, this page isn't going to legitimize the latest superfluous add-on by covering it further; read about ithere andhereif you wish.But please remember: usingmatch/case means your program can be run only on Python 3.10 or later. Being divisive and exclusionary may bea norm in Python 3.X's culture, but it doesn't have to be one in your own code.

The real problem with Python, of course, is that its evolution is largely driven by narcissism, not user feedback.That inevitably makes your programs beholden to ever-shifting whimsand ever-hungry egos. Such dependencies might have been laughable in the past. In the age of Facebook, unfortunately, this paradigm permeates Python, open source, and the computer field. In fact, it extends well beyond all three; narcissism is a sign of our times.

That said, fixing bugs in human nature also goes well beyond this page's mission, so we'll have to leave this thread here.As always, dear reader, the future of Python, like that of the human story at large, must rest in part with you.

Etcetera: this page stopped receiving updates at this point, but the parade marched on.Pythons 3.11 and 3.12 added exceptiongroups (a tangled extensionwith narrow roles), as well as atype statement that obviates simple assignments (and yes, there are now optional, convoluted, and wholly unused typedeclarations in a dynamically typed language!).

These Pythons also come with opinionated deprecations and removals of a host of longstanding tools. Among these are:

Later Pythons continued the ascent of Mt. Convolution, including3.14's arguably comicalt'...' string-formatting twist that's officially out of scope here (but see the blurbahead).

In the end, Python remains a constantly morphing sandbox of ideas, which is great if you just want to play in the sandbox but lousy if you're trying to engineerreliable and durable software. Vetter and downstream user beware.

 

Changes in Python 3.9 (Oct-2020)

Per itsWhat's New, Python 3.9 is a minor release with a relatively small set of changes, which isabout what you'd expect from a project that now issues a new releaseeach year.It does, however, manage to both inject the usual batch of superfluous language convolutions and extend the paradigm of thrashing-based—if not troll-driven—engineering. Among the morph:

Erasing the past
A new large-scale effort is afoot to remove the last vestigesof backward compatibility with decades ofPython practice. The blurb at that link includes the odd suggestion that you should test in developmentmode, in order to accommodate incompatible changes in thenext version of 3.X.It's official: breaking code in the present is no longer enough to satisfy Python 3.X's developers.
Type-declaration foo
There's additional support fortype-declaration syntaxin 3.X. You can now write Python code that looks just like that in your favorite statically typed language—and negate most of thereasons for using a dynamically typed language like Python in the first place.(Oh, snap?)
Dictionary "union"
Dictionaries have sprouted| and|= operators purportedly meant to add set union todictionaries... though the second is just an in-place version of the first; this is just one of many set operations and isn't the same as union at all; and the mod just adds redundant and obscure functionality that is imagined to be better than other redundant and obscure functionality added to 3.X in the past.

Wisdom Deprecated

Perhaps most elaboration worthy among 3.9's changes is the addition of union-like| operators for dictionaries.In truth, this is really just a trivial key-based merge, with an operator syntax borrowed from sets. It's also about as irregular and redundant as it gets; it:

Takeaway: Python 3.X now thrashes so much that it eventually contradicts itself.In this case, there was pushback onmultiple grounds,all of which were well reasoned and need no elaboration here—and as usual, were outshouted by the agenda of a few convoluters. The rejection of calls to avoidredundancywith appeals to tenets of the PythonZen, however, warrants an underscore. Quoting from the change's PEP:

More Than One Way To Do It  Okay, the Zen doesn't say that there should be Only One Way To Do It.   But it does have a prohibition against allowing "more than one way to do it".Response  There is no such prohibition.  The "Zen of Python" merely expresses a preference   for "only one obvious way":There should be one-- and preferably only one --obvious way to do it. [...]  In practice, this preference for "only one way" is frequently violated in Python. [...]  We should not be too strict about rejecting useful functionality because it   violates "only one way".

And somewhere, angels surely must weep (and Perl folk surely mustsmirk). Feature bloat kills usability, and past mistakes do not justify new ones.More to the point: needlessly changing a tool used by millions may be rude, but knowingly rejecting past wisdom is juvenile.

Closing Words?

This page hopes to continue sparking discourse on the perils of thrashing in engineering projects. But this will likely be its final entry. There will, of course, be a Python 3.10, and a 3.11, and a 3.12. And they will, of course, add redundant functionality, break existing code, and impose the constantly morphing whims of the few on the many.Popularity implies neither quality nor courtesy, and candidly, there are better things to do in the last quadrant of life than document this stuff.

In the end, the convolution of Python was not a technical story. It was a sociological story and a human story. If you create a work with qualitiespositive enough to make it popular but also encourage it to be changed fora reward paid entirely in the dark currency of ego points, time will inevitably erode the very qualities which made the work popular originally. There's no known name for this law, but it happens nonetheless and not just in Python. In fact, it's the very definition of open source, whose paradigm of reckless change now permeates the computing world.

Some of this law's consequences are less abstract than others. While Python developers were busy playing in their sandbox, web browsers mandated JavaScript, Android mandated Java, and iOS became so proprietary and closed that it holds almost no interest to generalist developers. Indeed, many of the open-source movement's original ideals of developer freedom are now perilously close to being dismissed as sacrifice on the altar of ego-based churn.

The sage should know when to stop. Python never did. This page does.

Never say never: almost immediately after the preceding was posted, this site received feedback from readers hoping that this page's updates will continue. It's possible that this page may be back for 3.10 and beyond, though this depends upon both whether there's anything useful to add and how wellthe sedatives work...

 

Changes in Python 3.8 (Oct-2019)

Python 3.8 is now in beta. It's scheduled for release in October 2019,and it's documented in full by itsWhat's New.As is now the norm, it also comes with the latest batch of language changes born of opinion-based thrashing. In fact, this page is starting to sound like a broken record (and the changes are starting to sound like flamebait), but a brief review of this version is in order for readers of the book. Among 3.8's lowlights:

Assignment expression
A new and wholly superfluous assignment expression,(x := y), that millions of Python programmers somehow managed to make do without for nearly three decades.Typingx = y separately was never hard,but code with obscurely nested:=assignments will almost certainly be brutal.
Function kludge
An ad-hoc syntax extension,def f(x, y=None, /), whose odd/ both qualifies askludge and forces preceding function arguments to be passed by position only—a wildly special-case role so important that it went unnoticed since the early 1990s (yes, sarcasm intended).
F-string kludge
Another ad-hoc syntax extension,f'{x*y + 15=}', whose weird= invokes an implicit and unexpectedevaluation and format and adds yet another special case to 3.6's f-strings extension describedahead... which was itself fully redundant 3.X morph that mushroomed Python'sstring-formatting story badly and needlessly. F-strings are now officially ad-hoc stacked!
Bytecode folder
A new environment-variable setting,PYTHONPYCACHEPREFIX,which allows the location of the 3.X__pycache__ bytecodefolder to vary per program, thereby breaking any tools that assume its formerly fixed location and further convoluting the ever-changing3.X module tale.
time.clock() dropped
And the rude removal of thetime.clock function, a tool which has been widely used by Python programmers from day one and whose deletion will break reams of Python code (including some benchmarking examples in thebook and other programs publishedonline).

In short, 3.8 is mostly more of theaccumulation-instead-of-designmodel of language evolution championed by the 3.X line.As usual, some will rush to use the new items in a strange effort to prove that they are somehow smarter than others—thereby sentencing their code to work on 3.8 and later only.As noted repeatedly on this page, you don't have to be one of them; these are optional extensions that most users are better off avoiding.

The Whims of the Few

The pigheaded removal of the widely usedtime.clock, however, just seems sad. Rather than improving a go-to tool that was supported for some 30 years, it's been deleted altogether in favor of something different. As foreshadowed in the book, this means thatvery many users of Python 3.8 and later will have to change their code to use alternative tools in thetime module thatvery few opinionated others have now mandated. Friendly not, that, but typical of the Python 3.X culture.

It's easy to see this culture at work for yourself. Well-reasoned objections to the subjective removal oftime.clock were indeed registeredhere,here, andelsewhere;but they were outshouted by the aesthetic preferences of a stubborn and myopicfew, whose ego investment in the incompatible change clearly outweighed the consequences for other peoples' code. This is how open source does not work.

In this specific case, thebook was able to give work-arounds for futuretime.clock deprecation (seeNew Timer Calls in 3.3 on page 655; in short, you must usetime.perf_counter wherevertime.clock is absent). In general, though, Python 3.X's rate ofchange far outpaces its learning resources (see the similar fate ofimp.reload in 3.7ahead), and neither documentation nor deprecation protocols qualify as justification; warning people that you're going to be rude doesn't make it okay to be rude.

Keeping It Simple

This writer wishes to reiterate that he still uses and enjoys Pythonregularly. It remains a great tool for programming in most or all application domains—evensomethat mutate just as carelessly as Python (per the link, thrashing is both endemic and chronic in today's software world).

But this writer also doesn't use the pointless products of flux that are now cranked outannuallyand doesn't recommend that book readers use these extensions either.Ego-driven churn may be an inherent downside of open-source projects, but it can also be largely ignored. You can't do much about changes that break existing programs, of course (apart from releasing work-aroundsor freezing support levels); optional language extensions, however, are optional.

As always, the best advice is to avoid the extraneous new stuff, and stick to the very large subset of Python that has proved to be so valuable across decades and domains. Your code will be much more widely usable, and your coworkers will be much less inclined to grab the pitchforks.

Postscript: during 3.8's tenure, python.org alsoended its supportfor the still-widely-usedPython 2.X—and inserted a rude denigration banner at the top of every page in 2.X users'docs. You can read expanded coverage of this unfriendly movehere;alas, 2.X users seem no longer invited to the club.

 

Changes in Python 3.7 (Jun-2018)

As of February 2018, Python 3.7 is in beta and scheduled for releaseinJune 2018. Per itsWhat's New document,this looks to be a relatively minor update in terms of language change (e.g.,__getattr__ now works inmodulestoo, but it probably shouldn't, and you'll probably never care).As usual, though, this latest Python continues to rehash its "features," and breaks existing programs in the name of subjective "improvements."This section briefly summarizes both categories and calls out one of their most crass members.

 

1.General Rehashings and Breakages

In therehashings department, Python 3.7 is yet again changing its handling of Unicodelocalesand bytecodefiles—perennial sources of revisions in recentversions. It's also once more modifying or deprecating portions of its implementations of bothtype hintsandcoroutines—convoluted 3.X extensions that have been in a perpetual state of flux since they were introduced a few versions ago (seethis andthis for background).

Dictionaries also suddenly maintain their keys in insertion order—which is not quite sequence-y and invalidates scads of docs which documented the former random order.Andmodules have suddenly sprouted__getattr__ and__dir__ functions called for attribute unknowns and listings—which conflate classes with modules and seem to assume that you have to already know Python to use Python.

In thebreakages column, 3.7's changes are numerous and widespread and require case-by-case analysis. As examples, its standard library changes to thesubprocessmodule's stream defaults and theshuil.rmtree()function's error-handler arguments seem likely to impact programs like those this page's author has written in recentyears. For such programs, revalidation and redistribution costs make a 3.7 upgrade impossible.

Reloads Break... Again

As a specific example of a breakage that's directly relevant to thebook, Python 3.7 also now issues a deprecation warning when code imports theimp.reload module-reloading tool; you'll soon need to change the first of the following to the second everywhere:

$ python3Python 3.7.4 (default, Jul 28 2019, 22:33:35)>>> from imp import reload__main__:1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses>>> from importlib import reload>>>
Sadly, this widely used built-in has now beensenselessly moved twice in Python 3.X—from built-in function, to theimp module, and now to theimportlib module—breaking all code that relies on formerly documented locations on each hop. This is careless in the extreme and motivated by nothing more than the personal preferences of those with time to impose their opinions on others' programs.

It's Called Bleeding for a Reason

From the broader view, Python 3.7 is really just the latest installment of the constant churn that is the norm in the 3.X line. Because this is pointless (and just no fun) to document, this page's 3.7 coverage will stop short here. Readers are encouraged to browse Python'sWhat's Newdocuments for more details on 3.7 and later.

Better still, avoid the bleeding-edge's pitfalls altogether by writing code that does not depend on new releases and their ever-evolving feature sets. In truth, the latest Python version is never required to implement useful programs. After all,every Python program written over the last quarter centuryhas managed to work well without Python 3.7's changes. Yours can too.

In the end, new does not necessarily mean better—despite the software field's obsession with change. Someday, perhaps, more programmers will recognize that volatility born of personal agenda is not a valid path forward in engineering domains.Until then, the path that your programs take is still yours to choose.

 

2.StopIteration Busted in Generator Functions

After the preceding summary was written, a reader'serratapost in autumn 2019 revealedan insidious Python 3.7 change that merits retroactive and special coverage here.In short, 3.7 also included a change that needlessly and intentionally broke the behavior of exceptions in generator functions and requires modifications toexisting 3.X code. That behavior and its dubious alteration in 3.7 are both subtle, but one partial example in thebook fell victim to the thrashing.

Details: In Pythons 3.0 through 3.6, aStopIteration raised anywhere in a generator function suffices to end the generator'svalue production because that's just what an explicit or implicitreturn statement does in a generator. For example,in the following code from a sidebar on page 645 of the book, thenext(i) inside the loop's list comprehension will triggerStopIteration when anyiterator is exhausted, thereby implicitly terminating the generatorfunction altogether—as intended—through Python 3.6:

>>> def myzip(*args):...     iters = list(map(iter, args))# make iters reiterable...     while iters:# guarantee >=1, and force looping...         res = [next(i) for i in iters]# any empty? StopIteration == return...         yield tuple(res)# else return result and suspend state                  ...# exit: implied return => StopIteration>>> list(myzip((1, 2, 3), (4, 5, 6)))[(1, 4), (2, 5), (3, 6)]>>> [x for x in myzip((1, 2, 3), (4, 5, 6))][(1, 4), (2, 5), (3, 6)]

If such code has to run on 3.7 and later, however, you'll need tochange it to catch theStopIteration manually inside the generator, and transform it into an explicitreturn statement—which just implicitly raisesStopIteration again:

>>> def myzip(*args):...     iters = list(map(iter, args))...     while iters:...         try:...             res = [next(i) for i in iters]...         except StopIteration:# StopIteration won't propagate in 3.7+...             return# how generators should exit in 3.7+...         yield tuple(res)# but exit still == return => StopIteration... >>> list(myzip((1, 2, 3), (4, 5, 6)))[(1, 4), (2, 5), (3, 6)]>>> [x for x in myzip((1, 2, 3), (4, 5, 6))][(1, 4), (2, 5), (3, 6)]

If you don't change such code, theStopIteration raised inside the generator function is covertly changed to aRuntimeError as of 3.7, which won't terminate the generator nicely, but will pass as an uncaught exception causing code failures in most use cases. Here's the impact on the bookexample's original code when run in 3.7:

>>> list(myzip((1,2,3), (4,5,6)))Traceback (most recent call last):...StopIterationThe above exception was the direct cause of thefollowing exception:Traceback (most recent call last):...RuntimeError: generator raised StopIteration

Importantly, this change applies to both implicit and explicit uses ofStopIteration: any generator function that internally triggers this exception in any way will now likely fail with aRuntimeError.This is amajor change to the semantics of generator functions. In essence, such functions in 3.7 and later can no longer finish with aStopIteration but must insteadreturn or complete the function's code.The following variant, for instance, fails the same way in 3.7:

>>> def myzip(*args):...     iters = list(map(iter, args))...     while iters:...         try:...             res = [next(i) for i in iters]...         except StopIteration:...             raise StopIteration# this also fails: return required...         yield tuple(res)# even though return raises StopIteration!

And bizarrely, changing the first of the following lines in the code to the second now causes adifferent exception to be raised in 3.7: despite the semantic equivalence, you'll get aStopIterationfor the first but aRuntimeError for the second,and may have to catch both in some contexts to accommodate the new special case:

...             res = [next(i) for i in iters]# StopIteration...             res = list(next(i) for i in iters)# RuntimeError (!)

In other words, 3.7 swaps one subtle and implicit behavior foranother subtle and implicit behavior—which alsomanages to be inconsistent. It'stough to imagine a better example of pointless churn in action.

Worse, the replacement knowingly breaks much existing and formerly valid 3.X codeand as usual reflects the whims of a small handful of people with time to chatabout such things online. In this case, developers lamented the need to maintain backward compatibility—and then went ahead and broke it anyhow. As part of their reckless bargain, even code in Python's own standard library which relied on the former 3.6-and-earlier behaviorhad to be changed to run on 3.7.

Lesson: You can read more about this change at itsPEP,find examples of programs it broke with aweb search,and draw your own conclusions along the way.Regardless of your take, though, users of Python 3.X should clearlyexpect that deep-rooted language semantics may change out from under them arbitrarily; with minimal warning and even less user feedback; and according to the opinions of a few people who have no vested interest in your code's success.

Simply put, in the absence of version freezes, your Python programswill probably break eventually through no fault of your own. Buyer, be warned.

 

Changes in Python 3.6 (Dec-2016)