Potentially powerful features for Python
Python is a living language – under constant development to keep up with the times. The Python Software Foundation is not just making additions to the standard library and to the reference implementation CPython, but also introducing new features and refinements to the language itself.
For instance, Python 3.8 introduced a new syntax for in-line assignments (the “walrus operator”) that makes certain operations more concise. Another newly approved syntax improvement, pattern matching, will make it easier to write code that evaluates for one of many possible cases. Both of these features were inspired by their presence and utility in other languages.
And they’re only two of a slew of useful features that could be added to Python to make the language more expressive, more powerful, more suited to the modern programming world. What else might we wish for? Here are four more language features that could add something of real value to Python – two we might actually get, and two we probably won’t.
Python does not really have the concept of a constant value. Today, constants in Python are mostly a matter of convention. Using a name that is in all-caps and snake case – e.g.,
DO_NOT_RESTART – is a hint that the variable is intended to be a constant. Similarly, the
typing.Final type annotation provides a hint to linters that an object should not be modified, but it doesn’t enforce that at runtime.
Why? Because mutability is deeply ingrained in Python’s behaviours. When you assign a value to a variable – e.g.,
x=3 – you are creating a name in the local namespace,
x, and pointing it at an object in the system that has the integer value
3. Python assumes at all times that names are mutable – that any name could point to any object. That means that every time a name is used, Python goes to the trouble of looking up what object it is pointing at. This dynamism is one of the chief reasons Python runs more slowly than some other languages. Python’s dynamism offers great flexibility and convenience, but it comes at the cost of runtime performance.
One advantage of having true constant declarations in Python would be some reduction in the frequency of object lookups that take place during runtime, and thus better performance. If the runtime knows ahead of time that a given value never changes, it doesn’t have to look up its bindings. This could also provide an avenue for further third-party optimisations, like systems that generate machine-native code from Python apps (Cython, Nuitka).
However, true constants would be a major change, and most likely a backward incompatible change. It would also be up for debate if constants would come by way of new syntax – for instance, the as-yet-unused
$ symbol – or as an extension of Python’s existing way to declare names. Finally, there is the larger, philosophical question of whether or not true constants make sense in a language where dynamism has been a big part of the appeal.
In short, it’s possible we’ll see true constants in Python, but it would be a major breaking change.
True overloading and generics
In many languages, multiple versions of the same function can be written to work with different kinds of input. For instance, a
to_string() function could have different implementations for converting from integers, floating-point numbers, or other objects – but they would share the same name for the sake of convenience. “Overloading,” or “generics,” make it easier to write robust software, since you can write generic methods for common processes rather than use a method specifically for a given type.
Python does let you use one function name do the work of many, but not by defining multiple instances of a function. You can define a name only once in a given scope and bind it to only a single object at a time, so you can’t have multiple versions of a single function under the same name.
What Python developers typically do to work around this is use built-ins like
type() to determine the type of variable submitted to a function, then take action based on the type. Sometimes this involves dispatching to a type-specific version of a function under the hood. But this approach makes it hard for other developers to extend your function unless you go out of your way to make it extensible – for instance, by dispatching to methods within a class, which could be sub-classed.
PEP 3124, advanced in April 2007, proposed a mechanism for decorating functions to indicate they could be overloaded. The proposal was deferred rather than being rejected outright – meaning the idea was fundamentally sound, but the time was not right to implement it. One factor that might speed the adoption of overloading in Python – or cause the idea to be ditched entirely – is the implementation of the newly proposed pattern matching system.
In theory, pattern matching could be used under the hood to handle overload dispatch. However, pattern matching could also be given as a rationale for not implementing generics in Python, since it already provides an elegant way to dispatch operations based on type signatures.
So we might get true overloading in Python one day, or its advantages might be superseded by other mechanisms.
Tail recursion optimisations
Many language compilers employ tail recursion optimisations, where functions that call themselves don’t create new stack frames in the application, and thus risk blowing up the stack if they run for too long. Python doesn’t do this, and in fact its creators have consistently come out against doing so.
One reason is that much of Python, from the inside out, uses iteration rather than recursion – generators, coroutines, and so on. In this case, it means using a function with a loop and a stack structure instead of a recursive mechanism. Each call of the loop can be saved into a stack to create a new recursion, and popped off the stack when the recursion finishes.
Python developers are encouraged to use these patterns instead of recursion, so there seems little hope for recursion optimisations. The chances here are not likely at all, as Python’s idioms support other solutions.
Lambdas, or anonymous functions, made it into Python only after some resistance on the part of language creator Guido van Rossum. As Python lambdas exist now, they’re highly constrained: They only allow you to use a single expression (essentially, anything to the right of an equals sign in an assignment operation) as the function body. If you want a full block of statements, just break them out and make an actual function from them.
The reason comes down to the design of the language as van Rossum sees it. As van Rossum wrote in 2006, “I find any solution unacceptable that embeds an indentation-based block in the middle of an expression. Since I find alternative syntax for statement grouping (e.g. braces or begin/end keywords) equally unacceptable, this pretty much makes a multi-line lambda an unsolvable puzzle.”
In other words, the problem is not technical, but the lack of a syntax for multiline lambdas that complements the existing aesthetics of Python syntax. There is probably no way to do it that does not involve creating a special case, and a language that accrues special cases tends to become unpleasant to use. Until such a unicorn appears, we’ll just have to make do with separately defined functions.
Multiline lambdas are probably not happening in Python.
IDG News Service