Syntax Changes¶
Python 3 cleaned up some warts of the language’s syntax.
The changes needed to accommodate this are mostly mechanical, with little chance of breaking code, so they work well as the first patches to send to a project when intending to port it.
Tabs and Spaces¶
- Fixer: see below
- Prevalence: Very common (unless the code uses a style linter)
In Python 2, a tab character in indentation was considered equal to 8 spaces or less. In Python 3, a tab is only equal to another tab, so the following code is rejected (whitespace highlighted):
def f(cond):
····if cond:
→ do_something()
····else:
→ do_something_else()
If your code mixes tabs and spaces, the easiest way to fix this is converting all tabs to spaces. You can use the following Bash command for this:
find . -name '*.py' -type f -exec bash -c 'T=$(mktemp); expand -i -t 8 "$0" > "$T" && mv "$T" "$0"' {} \;
Tuple Unpacking in Parameter Lists¶
- Fixer:
python-modernize -wnf fissix.fixes.fix_tuple_params
(fixup needed) - Prevalence: Common
Python 3 requires that each argument of a def
function has a name.
This simplifies code that uses introspection (such as help systems,
documentation generation, and automatic dispatchers), but it does
have a drawback: tuples are no longer allowed in formal parameter lists.
For example, functions like these are no longer allowed in Python 3:
def line((x1, y1), (x2, y2)):
connect_points(Point(x1, y1), Point(x2, y2))
lambda (key, item): (item, key)
The recommended fixer does a good job in finding places that need fixing, but it does need some manual cleanup. The above example would be rewritten to:
def line(xxx_todo_changeme, xxx_todo_changeme1):
(x1, y1) = xxx_todo_changeme
(x2, y2) = xxx_todo_changeme1
connect_points(Point(x1, y1), Point(x2, y2))
lambda key_item: (key_item[1], key_item[0])
For def
, each of the newly introduced variables should be renamed to
something more appropriate.
As for lambda
, this transformation can leave the code less readable than
before.
For each such lambda
, you should consider if replacing it with a regular
named function would be an improvement.
Backticks¶
- Fixer:
python-modernize -wnf fissix.fixes.fix_repr
(with caveat) - Prevalence: Common
The backtick (`
) operator was removed in Python 3.
It is confusingly similar to a single quote, and hard to type on some
keyboards.
Instead of the backtick, use the equivalent built-in function repr()
.
The recommended fixer does a good job, though it doesn’t catch the case where
the name repr
is redefined, as in:
repr = None
print(`1+2`)
which becomes:
repr = None
print(repr(1+2))
Re-defining built-in functions is usually considered bad style, but it never hurts to check if the code does it.
The Inequality Operator¶
- Fixer:
python-modernize -wnf fissix.fixes.fix_ne
- Prevalence: Rare
In the spirit of “There’s only one way to do it”, Python 3 removes the
little-known alternate spelling for inequality: the <>
operator.
The recommended fixer will replace all occurrences with !=
.
New Reserved Words¶
- Fixer: None
- Prevalence: Rare
Constants¶
In Python 3, None
, True
and False
are syntactically keywords,
not variable names, and cannot be assigned to.
This was partially the case with None
even in Python 2.6.
Hopefully, production code does not assign to True
or False
.
If yours does, figure a way to do it differently.
async
and await
¶
Since Python 3.7, async
and await
are also keywords.
If your code uses these names, rename it.
If other code depends on the names, keep the old name available for
old Python versions.
The way to do this will be different in each case, but generally
you’ll need to take advantage of the fact that in Python’s various namespaces
the strings 'async'
and 'await'
are still valid keys, even if they
are not accesible usual with the syntax.
For module-level functions, classes and constants, also assign the original
name using globals()
.
For example, a function previously named async
could look like this:
def asynchronous():
"""...
This function used to be called `async`.
It is still available under old name.
"""
globals()['async'] = asynchronous
For methods, and class-level constants, assign the original name using
setattr
:
class MyClass:
def asynchronous(self):
"""...
This method used to be called `async`.
It is still available under old name.
"""
setattr(MyClass, 'async', MyClass.asynchronous)
For function parameters, more work is required. The result will depend on
whether the argument is optional and whether None
is a valid value for it.
Here is a general starting point:
def process_something(asynchronous=None, **kwargs):
if asynchronous is None:
asynchronous = kwargs.get('async', None)
else:
if 'async' in kwargs:
raise TypeError('Both `asynchronous` and `async` specified')
if asynchronous is None:
raise TypeError('The argument `asynchronous` is required')
For function arguments, if the parameter cannot be renamed as above, use “double star” syntax that allows you to pass arbitrary argument names:
process_something(**{'async': True})
Other Syntax Changes¶
For convenience and completeness, this section lists syntax changes covered in other chapters: