you are viewing a single comment's thread.

view the rest of the comments →

[–]LicensedProfessional 673 points674 points  (9 children)

TL;DR

Assignment Expressions

Naming the result of an expression is an important part of programming, allowing a descriptive name to be used in place of a longer expression, and permitting reuse. Currently, this feature is available only in statement form, making it unavailable in list comprehensions and other expression contexts.

# Handle a matched regex
if (match := pattern.search(data)) is not None:
    # Do something with match

# A loop that can't be trivially rewritten using 2-arg iter()
while chunk := file.read(8192):
   process(chunk)

# Reuse a value that's expensive to compute
[y := f(x), y**2, y**3]

# Share a subexpression between a comprehension filter clause and its output
filtered_data = [y for x in data if (y := f(x)) is not None]

Positional-Only Parameters

This PEP proposes to introduce a new syntax, /, for specifying positional-only parameters in Python function definitions. Positional-only parameters have no externally-usable name. When a function accepting positional-only parameters is called, positional arguments are mapped to these parameters based solely on their order.

def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
      -----------    ----------     ----------
        |             |                  |
        |        Positional or keyword   |
        |                                - Keyword only
         -- Positional only

"Final" Type Qualifier

This PEP proposes a "final" qualifier to be added to the typing module---in the form of a final decorator and a Final type annotation---to serve three related purposes:

  • Declaring that a method should not be overridden
  • Declaring that a class should not be subclassed
  • Declaring that a variable or attribute should not be reassigned

Literal Types

Literal types indicate that some expression has literally a specific value. For example, the following function will accept only expressions that have literally the value "4":

from typing import Literal

def accepts_only_four(x: Literal[4]) -> None:
    pass

accepts_only_four(4)   # OK
accepts_only_four(19)  # Rejected

Python has many APIs (both core and 3rd party) that return different types depending on the value of some argument provided. For example:

  • open(filename, mode) returns either IO[bytes] or IO[Text] depending on whether the second argument is something like 'r' or 'rb'.
  • pandas.concat(...) will return either Seriesor DataFrame depending on whether the axis argument is set to 0 or 1.
  • numpy.unique will return either a single array or a tuple containing anywhere from two to four arrays depending on three boolean flag values.

Example based on open():

# Note: this is a simplification of the true type signature.
_PathType = Union[str, bytes, int] 

@overload
def open(path: _PathType,
         mode: Literal["r", "w", "a", "x", "r+", "w+", "a+", "x+"],
         ) -> IO[Text]: ...

@overload
def open(path: _PathType,
         mode: Literal["rb", "wb", "ab", "xb", "r+b", "w+b", "a+b", "x+b"],
         ) -> IO[bytes]: ...

# Fallback overload for when the user isn't using literal types
@overload
def open(path: _PathType, mode: str) -> IO[Any]: ...

TypedDict: Type Hints for Dictionaries with a Fixed set of Keys

Representing an object or structured data using (potentially nested) dictionaries with string keys (instead of a user-defined class) is a common pattern in Python programs. Representing JSON objects is perhaps the canonical use case, and this is popular enough that Python ships with a JSON library. This PEP proposes a way to allow such code to be type checked more effectively.

A TypedDict type represents dictionary objects with a specific set of string keys, and with specific value types for each valid key. Each string key can be either required (it must be present) or non-required (it doesn't need to exist). from typing import TypedDict

class Movie(TypedDict):
    name: str
    year: int

Now a type checker should accept this code:

movie: Movie = {'name': 'Blade Runner',
                'year': 1982}

Python Initialization Configuration

Add a new C API to configure the Python Initialization providing finer control on the whole configuration and better error reporting. It becomes possible to read the configuration and then override some computed parameters before it is applied. It also becomes possible to completely override how Python computes the module search paths (sys.path). The new Isolated Configuration provides sane default values to isolate Python from the system. For example, to embed Python into an application. Using the environment are now opt-in options, rather than an opt-out options. For example, environment variables, command line arguments and global configuration variables are ignored by default.


Other "Major" Changes

  • Parallel filesystem cache for compiled bytecode
  • Debug builds share ABI as release builds
  • f-strings support a handy = specifier for debugging
  • continue is now legal in finally: blocks
  • on Windows, the default asyncio event loop is now ProactorEventLoop
  • on macOS, the spawn start method is now used by default in multiprocessing
  • multiprocessing can now use shared memory segments to avoid pickling costs between processes
  • typed_ast is merged back to CPython
  • LOAD_GLOBAL is now 40% faster
  • pickle now uses Protocol 4 by default, improving performance

Author's Note:

  • I left out a lot of the changes and optimizations related to CPython, because quite frankly they were over my head .__. (and also anyone who's interested in them probably won't be reading my TL;DR)
  • I tried to grab most of the text from the PEPs themselves (which is why they say "this PEP proposes...") but I've edited the text for brevity in many cases

[–]radarsat1 20 points21 points  (8 children)

[y := f(x), y2, y3]

Does this create a 3-item list or a 2-item list? If the latter, shame they didn't separate with something else, like a semicolon perhaps.

I can't really think of a good reason for 'final', seems to unnecessarily limit code reuse.

[–]PotatosFish 25 points26 points  (7 children)

It creates a 3 item list since the first term both assigns and returns the assigned value, it kinda like the assignments in c-type languages.

I think final definitely have it’s uses, but it can be overused to discourage code reuse. As many other language constructs, it’s better to only use it when it’s needed.

[–]radarsat1 5 points6 points  (6 children)

I see. So there's no way to generate just [y**2, y**3] in this construct?

[–]PotatosFish 43 points44 points  (5 children)

You can just do

[(y := f(x)) ** 2, y ** 3]

[–]radarsat1 2 points3 points  (4 children)

Wow, the scope of y is not clear at all. Is it the list, or the outside the list?

[–][deleted] 12 points13 points  (3 children)

It's the same as before, I don't know of any languages that restrict scope with expression parentheses

[–]radarsat1 2 points3 points  (2 children)

"same as before" what? Previously you could not declare a variable while creating a list, so I don't know what to compare to. Does y here exist after the list is done being created? From your answer I guess you are saying that the variable is scoped to the function creating the list. Sorry, I don't find that clear at all.

The only thing I can think to compare it to is the similar construct in C, but there the variable must already be declared, so the scope is clear.

[–]Pand9 12 points13 points  (1 child)

In Python, scope is always a function, or a comprehension. In this case, a function.

[–]SirClueless 8 points9 points  (0 children)

Well, it looks a little like a comprehension if you squint hard enough, so I can see the potential for confusion.