Skip to content

Commit ea1cebf

Browse files
authored
Merge pull request #26 from tmck-code/20250506_dictionary_slice_in_python
20250506 dictionary slice in python
2 parents 202cefa + f4125da commit ea1cebf

2 files changed

Lines changed: 141 additions & 0 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ my blog
44

55
---
66

7+
### [20250506 Dictionary slice in Python](articles/20250506_dictionary_slice_in_python/20250506_dictionary_slice_in_python.md)
8+
9+
> _Creating an equivalent of Ruby's 'Hash.slice' in python_
10+
711
### [20250102 OSX dark mode shortcut](articles/20250102_osx_dark_mode_shortcut/20250102_osx_dark_mode_shortcut.md)
812

913
> _How to toggle between Light & Dark Appearance with a keyboard shortcut_
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
# 20250506 Dictionary slice in Python
2+
3+
- [20250506 Dictionary slice in Python](#20250506-dictionary-slice-in-python)
4+
- [Recreating `slice` in Python](#recreating-slice-in-python)
5+
- [A better way?](#a-better-way)
6+
- [Using `operator.itemgetter`](#using-operatoritemgetter)
7+
- [Benchmarking](#benchmarking)
8+
- [Results](#results)
9+
10+
---
11+
12+
One of my favourite methods in ruby is [Hash.slice](https://ruby-doc.org/core-3.1.0/Hash.html). It returns a new hash containing only the specified keys from the original hash - very handy!
13+
14+
*It works like this:*
15+
16+
```ruby
17+
h = {a: 1, b: 2, c: 3, d: 4}
18+
19+
h.slice(:a, :b)
20+
# => {a: 1, b: 2}
21+
```
22+
23+
***TL;DR, this is my python equivalent:***
24+
25+
```python
26+
import operator as op
27+
28+
def slice_dict(d: dict, keys: list) -> dict:
29+
return dict(zip(keys, op.itemgetter(*keys)(d)))
30+
```
31+
32+
---
33+
34+
## Recreating `slice` in Python
35+
36+
We can definitely use a dictionary comprehension to achieve the same result:
37+
38+
```python
39+
def slice_dict(d: dict, keys: list) -> dict:
40+
return {k: d[k] for k in keys if k in d}
41+
```
42+
43+
```python
44+
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
45+
46+
slice_dict(d, ('a', 'b'))
47+
# {'a': 1, 'b': 2}
48+
```
49+
50+
## A better way?
51+
52+
We can use the `dict` constructor to create a new dictionary from a list of tuples, which is a more efficient way to slice a dictionary:
53+
54+
```python
55+
def slice_dict(d: dict, keys: list) -> dict:
56+
return dict((k, d[k]) for k in keys if k in d)
57+
```
58+
59+
```python
60+
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
61+
slice_dict(d, ('a', 'b'))
62+
# {'a': 1, 'b': 2}
63+
```
64+
65+
## Using `operator.itemgetter`
66+
67+
We can use the `[operator.itemgetter](https://docs.python.org/3/library/operator.html#operator.itemgetter)` function to create a callable that retrieves the specified keys from the dictionary.
68+
69+
First, a demonstration of how `itemgetter` works:
70+
71+
```python
72+
import operator as op
73+
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
74+
75+
getter = op.itemgetter('a', 'b')
76+
77+
# now we can use the getter to retrieve the values from a dictionary
78+
getter(d)
79+
# (1, 2)
80+
```
81+
82+
We can then create a new dictionary by zipping the returned values with the keys:
83+
84+
```python
85+
keys = getter(d)
86+
# (1, 2)
87+
88+
dict(zip(('a', 'b'), keys))
89+
# {'a': 1, 'b': 2}
90+
```
91+
92+
Combining these two steps, we can create a function that slices a dictionary:
93+
94+
```python
95+
import operator as op
96+
97+
def slice_dict(d: dict, keys: list) -> dict:
98+
return dict(zip(keys, op.itemgetter(*keys)(d)))
99+
```
100+
101+
```python
102+
d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
103+
104+
slice_dict(d, ('a', 'b'))
105+
# {'a': 1, 'b': 2}
106+
```
107+
108+
## Benchmarking
109+
110+
Here are some quick benchmarks to compare the three methods:
111+
112+
*I ran this in ipython so that I can use the `%timeit` magic function*
113+
114+
```python
115+
import operator as op
116+
117+
def slice_dict_comp(d: dict, keys: list) -> dict:
118+
return {k: d[k] for k in keys if k in d}
119+
120+
def slice_dict_gen(d: dict, keys: list) -> dict:
121+
return dict((k, d[k]) for k in keys if k in d)
122+
123+
def slice_dict_op(d: dict, keys: list) -> dict:
124+
return dict(zip(keys, op.itemgetter(*keys)(d)))
125+
126+
for fn in [slice_dict_comp, slice_dict_gen, slice_dict_op]:
127+
print(fn.__name__, fn(d, keys))
128+
%timeit fn(d, keys)
129+
```
130+
131+
### Results
132+
133+
| function | time |
134+
| --------------- | ------- |
135+
| slice_dict_comp | 746 ns |
136+
| slice_dict_gen | 1.33 μs |
137+
| slice_dict_op | 696 ns |

0 commit comments

Comments
 (0)