Plain text dictionary keys?

Hi all!

I work with Python and JavaScript pretty often and the thing that trips me up when switching between the two is the use of key-value pairs. JavaScript objects allows plain text keys (which are strings under the hood), and I think that’s a reasonable assumption which saves a lot of key strokes. I believe Python could benefit from something similar:

regular_dict = {
    'name': 'Python',
    'version': 3,
    'icon': '🐍'
}

plain_text_dict = {
    name: 'Python',
    version: 3,
    icon: '🐍'
}

I am aware of this use of dict() that has a similar behavior, but I think it’s still more keystrokes than necessary. Plus, you have to switch from : to =, and from {} to ():

plain_text_dict = dict(
    name='Python',
    version=3,
    icon='🐍'
)

I would like to know what are your thoughts on this?

A non-starter, I’m afraid. Would break existing code that relies on being able to set dictionary keys from variables:

key1 = "name"
mydict = {key1: "bob"}
7 Likes

Given:

{name: value}

how is the interpreter supposed to distinguish between the string "name" and the named variable name? What if there is a conflict between them?

More importantly, how is the human reader supposed to tell which is meant?

Code is written once, but read many times. That 0.1 second that you save by not having to type the quotation marks, will almost certainly cost the reader multiple seconds trying to work out whether name refers to the string or the variable (which may or may not even exist) every time they read it.

3 Likes

A syntax that could work is

regular_dict = {
    'name': 'Python',
    'version': 3,
    'icon': '🐍'
}

plain_text_dict = {
    name='Python',
    version= 3,
    icon='🐍',
}
2 Likes

In addition to the issues people have already mentioned, there’s a broader principle here which is worth mentioning. While Python happily borrows ideas and constructs from other languages, simply copying another language’s syntax for something that Python can already do, is much less likely to happen.

In particular, “it’s easier for people coming from language X” isn’t a very compelling argument. There are a lot of differences between Python and whatever other language you care to consider. Making one specific construct similar doesn’t really help in any significant sense - you still need to learn all of the rest of Python. And often the difference is superficial anyway (Python’s dictionaries are semantically very different from Javascript’s objects, or Lua’s tables, or any other similar data structure).

So I’m afraid I’m -1 on this. Just use dict(x=1, y=2) if you prefer the keyword approach.

12 Likes

That makes sense. I forgot that Python allows defined variables to be used as keys. I haven’t encountered it as much, so I didn’t consider it when I wrote this post.

I was thinking that since it was inside {}, the interpreter would interpret the plain text within those brackets as strings:

x = 5

some_dict = {
    x: 'Hello'   # 'x': 'Hello'
}

print(x) # 5
print(some_dict.x) # Hello

But, as it was pointed out in another comment: pre-defined variables can be used as keys, so my initial thought wouldn’t work.

I think this is a good middle ground!

As someone whose recently had to learn groovy for Jenkins pipelines, I can say with absolute certainty that this feature, while it may seem appealing at first, is so darn confusing and I freaking hate it.

Im about 4 days into this but trying to make sense of that syntax is non intuitive

1 Like

It is already possible to achieve this behaviour in Python without having to change the syntax of the language.

Is it? How so?

dict(a=1, b=2)

The “I am aware” part of the first post already covered and dismissed that, so that can’t be it.

Thanks, we did discuss that method of defining key-value pairs in the initial post.
I was hoping the previous post was referring to another way to create dictionaries that hasn’t
been mentioned yet.

Brief introduction/apology

Hello there! I’m sorry for the very late response, I don’t check this website/account very often. I hope you’re not too upset about the wait.

Note the following caveats

– Any vagaries of your encoding/locale may change the results (because the raw string in the text will be interpreted differently).

– The following conditions must be true for this to work:

  • The keystring is not the name of a defined variable that is reachable from that scope (otherwise it’ll just use the value of that variable instead of the literal string that you used).
  • The keystring is a valid variable name (no invalid syntax here, please).
  • You must be using cPython, or some 100% cPython compatible implementation.

Now as promised, here you go!

##
# Imports
##

# Used for checking if Py_TRACE_REFS is on.  If Python was configured with
# `./configure --with-trace-refs' (the proper way to enable Py_TRACE_REFS) then
# `sys.getobjects' will be defined.
# <https://docs.python.org/3/using/configure.html#cmdoption-with-trace-refs>
import sys

# Used for shenanigans :-)
from ctypes import (
    py_object as PyObject_p,
    c_ssize_t as Py_ssize_t,
    c_uint32 as Py_uint32_t,
    sizeof as sizeof,
    Union as union,
)


##
# Hook definition.
##

class __globals__(dict):
    # New definition for `globals().__getitem__'
    def __getitem__(self, item):
        if item in self:
            return {}.__class__.__getitem__(self, item)
        _b = self["__builtins__"]
        if _b.__class__ is not {}.__class__:
            _b = _b.__dict__
        if item in _b:
            return _b[item]
        return item


## 
# Find the offset to the `ob_type' member of the `PyObject' struct.
# <https://github.com/python/cpython/blob/main/Include/object.h#L166-L175>
##

# `Py_TRACE_REFS' stuff.
_ob_next = PyObject_p * hasattr(sys, "getobjects")
_ob_prev = PyObject_p * hasattr(sys, "getobjects")
# `ob_refcnt' union stuff.
class ob_refcnt_union(union):
    _fields_ = (
        ("ob_refcnt", Py_ssize_t),
        ("ob_refcnt_split", Py_uint32_t * 2),
    )
# `ob_type' is right after the refcounting stuff.
ob_type_offset = sizeof(_ob_next) + sizeof(_ob_prev) + sizeof(ob_refcnt_union)


##
# Hook the object type.
##
PyObject_p.from_address(
    id(globals()) + ob_type_offset
).value = __globals__


##
# Now it's hooked :-)
##
print(hello, world)
print({
    this_is: a_dictionary,
})

Bye

Hope you enjoy playing around with this, and have a nice day!

1 Like

That is very cool and very horrifying! Thanks for sharing

2 Likes

This has been discussed before, I forget where, I commented then about this being a feature in Groovy (some java bastardization for scripting Jenkins software). This is a horrible feature and will do nothing but add a bunch of overhead and slowness to dicts.

Edit: ha, didn’t realize this was the same thread, clicking in on mobile made this seem like I was replying to OP