[Python-ideas] Syntax idea: escaping names to avoid keyword ambiguity

Daniel Moisset dmoisset at machinalis.com
Mon May 14 06:47:55 EDT 2018

Following up some of the discussions about the problems of adding keywords
and Guido's proposal of making tokenization context-dependent, I wanted to
propose an alternate way to go around the problem.

My proposal essentially boils down to:

   1. The character "$" can be used as a prefix of identifiers. formally,

   *identifier  * ::= ["$"] xid_start
xid_continue <https://docs.python.org/3/reference/lexical_analysis.html#grammar-token-xid_continue>*

   2. The "$" character is not part of the name. So the program
   "foo=3;print($foo)" prints 3. So does the program "$foo=3; print(foo)".
   Both set an entry to globals["foo"] and keep globals["$foo"] unset.
   3. if "$" appears in a token, it's always an identifier. So "$with",
   "$if", "$return" are all identifiers.

If you overcome the "yikes, that looks like awk/bash/perl/php, and I don't
like those", and consider it as an escape for "unusual"/"deprecation"
situations, I think it's not a bad chose, and allows to a simple solutions
to many problems that have been in discussion recently and not so recently.
[examples below]

For me the benefits of this approach are:

   - It's very simple to explain how to use and its semantics
   - It (seems to me it) should be easy to explain to a python apprentice
   what a "$" means in code they read on a book/blogpost/manual
   - It's very easy to implement, minimal changes in the tokenizer
   - It's also easy to implement/integrate in other tools (editors with
   syntax highlighters, code formatters, etc)
   - It is easy to see that it's 100% backwards compatible (I understand
   that "$" has never been used in python before)
   - It is relatively unsurprising in the sense that other languages are
   already using $ to label names (there may be some point of confusion to
   people coming from javascript where "$" is a valid character in names and
   is not ignored).
   - It gives python devs and users a clear, easy and universal upgrade
   path when keywords are added (language designers: Add a __future__ import
   to enable keyword in python N+1, add warnings to change kw --> $kw in
   python N+2, and then turn it on by default in python N+3... ; developers:
   add the import when they want to upgrade , and fix their code with a
   search&replace when adding the import or after getting a warning).
   - It allows you to use new features even if some libraries were written
   for older python versions, depending the deprecation period (this could be
   improved with sth I'll write in another email, but that's the topic for
   another proposal)
   - When clashes occur, which they always do, there's one obvious way to
   disambiguate (see today the "class_" argument for gettext.translation, the
   "lambd" argument for random.expovariate, the "class_" filter in libraries
   like pyquery for CSS class, functions like
   pyquery, sqlalchemy.sql.operators.as_ , etc. Not counting all the "cls"
   argument to every classmethod ever)
   - If we're worried about over proliferation of "$" in code, I'm quite
   sure given past experience that just a notice in PEP 8 of "only with $ in
   names to prevent ambiguity" should be more than enough for the community

What are the drawbacks that you find in this?

[The rest of this post is just examples]

Example 1:

Python 3.92 has just added a future import that makes "given" a keyword.
Then you can do:

# This works because we have no future import
*from* hypothesis *import* given, strategies *as* st

*def* foo(i):
    x = f(i)**2 + f(i)**3

if you want to use the new feature (or upgraded to python 3.93 and started
receiving warnings) you can then change it to:

*from* __future__ *import* given_expression

*from* hypothesis *import* $given, strategies *as* st

@$given(st.integers()) *# If you forget the $ you get a SyntaxError*
*def* foo(i):
    x = z**2 + z**3 *given* z = f(i)

And also you could do:

*from* __future__ *import* given_expression

*import* hypothesis

*def* foo(i):
    x = z**2 + z**3 *given* z = f(i)

Or even, if you want to prevent the "$" all over your code:

*from __future__ import given_expressionfrom* hypothesis *import* $given
*as* hgiven, strategies *as* st

*def* foo(i):
    x = z**2 + z**3 *given* z = f(i)


If you have some library which uses a new keyword as a method name (you
can't rename those with import ... as ...), it still works perfectly:

*from* mylib *import* SomeClass

instance = SomeClass()

This is also helpful as a universal way to solve name clashes between
python keywords and libraries that use some external concept that overlaps
with python

(from https://pythonhosted.org/pyquery/attributes.html ):

>>> *import* pyquery *as* pq
>>> p = pq(*'<p id="hello" class="hello"></p>'*)(*'p'*)
>>> p.attr(id=*'hello'*, $class=*'hello2'*)

Or even nameclashes within python itself

*def *new_with_color($class, color): *# Instead of the usual cls or class_*

    result = $class()


    return result

Daniel Moisset

A:   1 Fore Street, EC2Y 9DT London <https://goo.gl/maps/pH9BBLgE8dG2>
P:   +44 7398 827139 <+44+7398+827139>
M:   dmoisset at machinalis.com <dmoisset at machinalis.com>  |   S:   dmoisset
<http://www.twitter.com/machinalis>  <http://www.facebook.com/machinalis>
Machinalis Limited is a company registered in England and Wales. Registered
number: 10574987.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-ideas/attachments/20180514/97238f4f/attachment-0001.html>

More information about the Python-ideas mailing list