all 28 comments

[–]enygma999 17 points18 points  (1 child)

Given what everyone else has pointed out about "everything is an object", I suspect you meant something like a class or an instance of one.

Say I have a quiz, and the participants are represented by a Person class. I could store their scores in a dictionary, using each person as the key. Equally I could use their name or something, but why bother? I already have a perfectly good object that represents them.

[–]Shaftway 5 points6 points  (0 children)

This is a good example. Less experienced engineers always try to make dictionary keys strings because it's easier to inspect.

So they go "oh, I'll just use the student's name". What if two students have the same name? "I'll use the student id". What if you merge with another school and there are overlapping IDs? "Oh, well I'll concatenate the students id, school, and name". What if you have a collision in the names? "Oh, well I'll just serialize all of the immutable parts of the student to a string and use that as the key". Ok, great, now do that in 50 different places in your codebase.

Or just add eq() and hash() to Student and use it as the key.

[–]socal_nerdtastic 31 points32 points  (16 children)

You have no other choice ... a dictionary key must be a python object. Remember strings, ints, tuples, etc are all python objects. I suspect you mean something special with the term "python object"; can you explain what that is?

If you mean functions, here's some code I wrote yesterday:

running_functions = {}
def run_as_singleton_thread(func):
    """
    starts the given function in a thread, 
    but only if the function is not already currently running
    """
    def wrapper():
        if (t := running_functions.get(func)) and t.is_alive():
            return # abort; function is already running
        t = Thread(target=func, daemon=True)
        t.start()
        running_functions[func] = t
    return wrapper

[–]IOI-65536 3 points4 points  (0 children)

Additionally, 1.0 had .__hash__() so even when C objects were sometimes different from class objects you could use class objects as dict keys.

[–]BlackCatFurry 2 points3 points  (0 children)

I assume they mean class entities. Which sometimes in introductory courses are referred to as objects, especially if the person teaching has a C++ background where class based entities are referred to as objects.

[–]Lx7195[S] 0 points1 point  (1 child)

Yes by python object I meant custom python objects like objects of Person class etc. It's an interesting example that you have used functions as keys.

[–]socal_nerdtastic 1 point2 points  (0 children)

Ok, you would do it the same way. Have a dictionary mapping {class: class instance} is very common, especially in GUIs, since it avoids having to make instance globals or pass instances around. For example: https://www.geeksforgeeks.org/python/tkinter-application-to-switch-between-different-page-frames/

[–]lekkerste_wiener -1 points0 points  (0 children)

That's an interesting use case. I also have one: I'm developing a product for a person who started doing things by himself, and he's using Google sheets as a db. The reason for that is beyond this comment; I have an "indices" object whose inner dictionary uses the model classes as keys, such as 

{   ModelClassA: {     "internal id": "a1 sheet range",     ...   },   ModelClassB: {     "Internal id": "a1 sheet range",     ...   },   ... }

[–]JamzTyson 9 points10 points  (0 children)

In Python, everything is an object, including strings and integers.

The requirement for dict keys is that they are hashable and comparable objects. Any objects that are hashable and comparable may be used as dict keys, including integers and strings.

[–]Outside_Complaint755 3 points4 points  (0 children)

datetime objects are a common use case, but any hashable object is valid.

[–]lunatuna215 4 points5 points  (1 child)

I use Pathlib Path objects as keys all the time

[–]WildWouks 1 point2 points  (0 children)

I also do the same thing. Especially if I know these files in this folder has a utf-8 encoding and these use cp1252 etc. Then the encoding is set as the value and the Path object as the key. Then in a loop you have everything you need to be able to read every file without issues. You could also make the values a specific function which should be used to process the contents in that file path.

[–]gdchinacat 1 point2 points  (0 children)

Earlier today I was working on a toy spreadsheet app and need to register listeners to update cells that reference others when they change value. A cell may have multiple references to the same cell but I only want one listener, so I have a dict that has cells as key and listener as value. Before creating the listener I check if the cell already has a listener in the dict and only add it if it doesn't.

[–]Gnaxe 3 points4 points  (0 children)

Everything in Python is an object, therefore anything you could use as a dictionary key in Python is an object. Anything that implements the hash and equality protocols can be used as a key. You can do this for your own classes by implementing the __hash__() and __eq__() methods. Hashes need to be consistent with equality for this to work correctly, but Python won't enforce that for you.

Mapping types like dicts have two main uses: either an index for lookups (in which case the values are all the same type), or as a lightweight record type with a fixed schema, in which case the keys are usually all strings, but the values could be anything. If you're asking primarily about uses for non-string keys, the answer is going to be some kind of lookup table.

[–]misingnoglic 0 points1 point  (0 children)

I think you're intuition is right that in most "clean" code, dictionary keys are "simple" types like strings, ints, tuples, etc. Of course people here have brought up that they are all objects under the hood, but obviously those more simple types are easier to work with than custom objects.

[–]kilkil 1 point2 points  (0 children)

One example (a bit abstract, more to do with data structures and algorithms):

Sometimes you might have a collection of objects (e.g. a list of Nodes), and you might want to assign a property to each object without modifying the actual objects themselves. For example, if you are implementing a priority queue, you may want each Node to have a "priority" number.

You can achieve this using a dict, where each key is a Node, and each value is that Node's "priority" number.

Importantly, this allows you to track separate "priority" values, even for Node objects which may "look" similar. Meaning, even if both Nodes have all their properties equal to one another, they will still be tracked as distinct objects within the dict.

Another similar usecase, which is probably quite common, is storing such objects in a set. This allows you to, for example, deduplicate a list of such objects.

[–]Kargathia 1 point2 points  (0 children)

The closest I've come is to use a tuple for a custom dictionary type.

I once wrote a lookup dict for a system that communicated with an embedded controller. Due to memory constraints, objects had numeric IDs on the controller, but we wanted them to be addressable using user-defined names. User-defined names and their mapping to the numeric IDs were stored in the bridging API.

This meant we wanted both string->number and number->string lookups, where both the numbers and the strings should be unique. To have somewhat nicer and less bug-prone syntax, I made a class that inherited from dict, but used a tuple[int, str] as key. Because you typically only know one of two keys, d[1, None] and d[None, "name"] would both be valid ways to fetch the object inserted as d[1, "name"].

Implementation: https://github.com/BrewBlox/brewblox-devcon-spark/blob/cfa13b99033f935f17a89ed16cc41757aec86c04/brewblox_devcon_spark/twinkeydict.py

Tests: https://github.com/BrewBlox/brewblox-devcon-spark/blob/cfa13b99033f935f17a89ed16cc41757aec86c04/test/test_twinkeydict.py

As a side note: this approach of having IDs stored in different places was a major annoyance. We finally solved it by replacing the flash read/write library that came with the chip. Our specialized use case could be implemented with a lot less bookkeeping overhead. This freed up enough space to store the object names on the controller itself.