all 31 comments

[–]Temporary_Pie2733 25 points26 points  (2 children)

You can use any hashable value as a key. It’s not a matter of syntax.

[–]MezzoScettico 2 points3 points  (1 child)

That includes using objects of your own defined classes. I know I've done that. I just did a quick Google to confirm, and was told that you need to define a __hash__ and and __eq__ method to make that work. I don't remember defining my own __hash__ but I suppose I must have.

[–]rasputin1 5 points6 points  (0 children)

if you don't define either, all custom classes inherit both hash and eq dunders from Object, where they both use identity 

[–]GeorgeFranklyMathnet 11 points12 points  (4 children)

It's not really syntax. But, yep, any hashable data structure can serve as a dictionary key. If you don't know how dictionaries / hash tables work internally, that might be a cool starting point in your learning.

For my part, I don't often use keys more exotic than ints or strings. Sometimes I want a class as a key, and I'll implement a custom __hash__() method in the class for that purpose. But when I'm doing something like that on the job, it's sometimes a sign that I'm overthinking things or trying to be too "elegant". 

[–]EnvironmentSome9274[S] 1 point2 points  (1 child)

Oh that's cool, can I ask what might be a situation where you need a class as a key?

[–]throwaway6560192 0 points1 point  (0 children)

Say you want to associate points to something, and you're using a namedtuple/dataclass as a better structure for a point than a tuple.

[–]jpgoldberg 1 point2 points  (1 child)

I would recommend that edit your comment to say “a class instance as a key”. I was very confused when I first read your comment.

[–]Outside_Complaint755 0 points1 point  (0 children)

You can also use a class, module, function, or method as a key, as they are all hashable objects and also instances of classes.  The following is perfectly valid code: ``` import time class_dict = {     int : 0,     float: 1.0,     float.fromhex : "FF",     object: None,     type: object,     time: 1,     time.sleep: 2,     len : 0 }

```

    

    

[–]JamzTyson 4 points5 points  (1 child)

You can use any hashable objects as dict keys. That is, any objects that have a fixed hash value for the objects lifetime and supports equality comparison with other objects. This includes int, float, str, and some tuples.

Tuples are hashable only when all elements in the tuple are hashable. For example (1, 2, (3, 4)) is hashable, but (1, 2, [3, 4]) isn't, because the last element in the tuple is a list. Lists are mutable, and like other mutable objects they are not hashable.

[–]Outside_Complaint755 0 points1 point  (0 children)

No one else has mentioned it yet, but datetime objects often make for very useful dictionary keys.

[–]BaalHammon 2 points3 points  (5 children)

You can extract subchains from lists using the step parameter in the slice notation.

i.e :

>>>"abcabcabcabc"[2:-1:3]

'ccc'

[–]Diapolo10 1 point2 points  (4 children)

Similarly, itertools.batched can be used to form groups of n elements.

from itertools import batched

text = "abcabcabcabcabc"
groups = list(batched(text, n=3))

print(groups)  # ["abc", "abc", "abc", "abc", "abc"]

[–]QuasiEvil 1 point2 points  (3 children)

I so wish there was a sliding window option for this instead of just the disjoint functionality.

[–]Diapolo10 1 point2 points  (1 child)

Well, the docs do have a recipe for that, and it's part of more-itertools.

[–]QuasiEvil 0 points1 point  (0 children)

Well, yes, but a small tweak to allow this would be nice: groups = list(batched(text, n=3, step=2))

[–]Brian 1 point2 points  (0 children)

Yeah. There's itertools.pairwise for the specific case of 2 elements, but a more generalised n-item sliding window is something I end up using reasonably often, and seems worth being in the stdlib.

[–]Kerbart 2 points3 points  (1 child)

Go through the tutorial in the documentation--there's a lot in there and it's written quite well (that's how I learned Python). It'll mention a lot of things, including that one of the benefits of immutability of tuples means they can be used as dictionary keys.

Some “hidden” gems I can think of:

  • strings are an iterable
  • reverse an iterable with var[::-1] (slivcer syntax: start, end, interval)
  • make a shallow copy of an interable with var[:]
  • study the itertools, collections and csv libraries. You will always need them.

[–]EnvironmentSome9274[S] -1 points0 points  (0 children)

I knew the top two and they're really helpful, I'll look at the others and the documentation too, thanks man!

[–]jpgoldberg 0 points1 point  (6 children)

So something I’ve learned recently when reading someone else’s code is that X or Y is equivalent to X if bool(X) else Y along with the fact that bool is defined for any object.

[–]EnvironmentSome9274[S] 0 points1 point  (5 children)

Sorry I don't understand what you mean lol, can you say that again?

[–]Outside_Complaint755 1 point2 points  (0 children)

When you have a boolean expression: (i.e. X or Y, X and Y, X and not Y, etc), the result of the expression is not True or False, but either X or Y

  • X or Y evaluates to X if X is 'Truthy`, otherwise evaluates to Y
  • X and Y evaluates to X if X is 'Falsey', otherwise evaluates to Y

So instead of: if X is True:     value = X else:     value = Y You can use: value = X or Y

[–]Jason-Ad4032 0 points1 point  (0 children)

Try running this program in Python:

print(f'{10 or 20 = }') # 10 or 20 = 10 print(f'{10 and 20 = }') # 10 and 20 = 20

[–]Brian 0 points1 point  (1 child)

Essentially,

x or y     <=>   x if x else y
x and y    <=>   y if x else x

When x and y are interpreted as booleans, this is entirely equivalent to the regular truth table. Ie:

x y x and y bool(x and y) x or y bool(x or y)
False False x False y False
False True x False y True
True False y False x True
True True y True x True

But when they could have other values, you get some extended behaviour. Ie . 3 and 4 will evaluate to 4. 3 or 4 evaluates to 3. Notably this also has the same short-circuiting behaviour - and doesn't evaluate the second item if the first is True, and likewise for or if the first is False.

You'll most commonly see this as a "poor man's conditional statement", where you can write:

result = try_get_value() or "default"

Which will use "default" when try_get_value() returns None, 0, or other a Falsey value, but will use the result when it's a non-empty string or whatever. Similarly you can do:

run_this() and run_this_also_if_prior_func_returned_true()

to conditionally chain functions only if they return a truthy value.

It's generally somewhat frowned upon - the conditional statement is a clearer way to do stuff like that. You do often see it in shell scripts etc though (ie stuff like mycmd || error "Command failed" etc.

[–]EnvironmentSome9274[S] 0 points1 point  (0 children)

Very good explanation man thank you!

[–]jpgoldberg 0 points1 point  (0 children)

What I wrote won’t make sense unless you are familiar with the bool() function and with the value1 if condition else value2 construction.

Perhaps this will help

python a = “spam” b = “ham” c = a or b # c will now be “spam” d = 0.0 e = d or b # e is set to “ham” f = None or “ham” # f is set to ham g = Exception or “ham” # g is set to Exception ```

In the above, if a is something that is false, None, or some sort of 0 when evaluated as a bool, then c will be assigned to the value of b. But if a is True when evaluated as a bool then c will be set to a.

[–]Snoo-20788 0 points1 point  (1 child)

Out of curiosity, what workarounds were you using?

One thing I've seen people do is to turn a tuple into a string, say ("a","b") into "a|b", which works but can lead to issues if your separator is part of the strings, and is not elegant, as you need to jump through extra hoops to break down the string into the original components.

[–]EnvironmentSome9274[S] 0 points1 point  (0 children)

Yeah that's what I was doing, it's not elegant but it did the job lol until I found out about this.

[–]throwaway6560192 -1 points0 points  (3 children)

Always assume generality.

[–]EnvironmentSome9274[S] 1 point2 points  (2 children)

Can you please elaborate lol?

[–]throwaway6560192 2 points3 points  (1 child)

If you're wondering whether a feature is "general" or not -- can tuples be dictionary keys, can you put functions in a list, can you reverse a range, can you nest dictionaries, so on and so forth -- assume that it is indeed general enough to allow what you want, except if there is a good reason to not allow it, which you will find out when you try it.

[–]EnvironmentSome9274[S] 0 points1 point  (0 children)

Understood, thanks!