Below you can find the code that we will be reviewing today. Before you scroll down to section where I fix it, feel free to check how many mistakes you can find.
def CountOccurrences(input):
result = {}
for i in range(len(input)):
item = input[i]
if item in result.keys():
result[item] = result.get(item, 0) + 1
else:
result[item] = 1
keys = []
for key in result.keys():
keys.append(key)
keys.sort()
output = ''
for key in keys:
output += str(key) + ': ' + str(result[key]) + ', '
return output[:-2]This function is meant to take a list of strings, count the occurrences of each individual string and return these counters in a form of a dictionary serialized to a string. Let’s begin with the function signature:
def CountOccurrences(input):First line and already 2 bad practices:
- name of the function
- missing type annotations
Name of the functions in Python as suggested by PEP-8 should be snake case, not camel case, so here it should be:
def count_occurrences(input):The type annotations are not required in Python (after all it’s a dynamically typed language), but providing them is a good practice because they directly inform the user what types are expected on the input and on the output:
from typing import List
def count_occurrences(input: List[str]) -> str:If you want be even more expressive, you can create aliases for the actual types:
from typing import List
StringContainer = List[str]
SerializedDictionary = str
def count_occurrences(input: StringContainer) -> SerializedDictionary:This may an overkill for the simple types like in this case, but it’s very useful for more complex types. You gain not only more information about what such complex type represents, but what’s most important, if you ever change the underlying types, but preserve their API, you must introduce a change only in one place (in the type alias) instead of everywhere, where the type has been used so far.
Note for beginners: remember, that type annotations don’t enforce the specified types, so you can’t prevent this way calling your function with an argument of invalid type. These are information for the developers (so that they know what types to use) and for IDEs (so that they know what lines to underline). If you want to enforce type correctness, your function must perform an explicit type validation using e.g.
isinstancebefore using the received argument.

AI is powerful. Snippets are instant.
Stop prompting for the same patterns repeatedly. Get almost 100 free VS Code snippets for C++, Python, CMake and Bazel from piko::snippets GitHub repository.
Let’s move to the the next part of the function:
for i in range(len(input)):
item = input[i]
if item in result.keys():
result[item] = result.get(item, 0) + 1
else:
result[item] = 1First of all, there is no need here to iterate the list with an index, so to avoid overcomplicating loop, we should just iterate directly over its elements. This way we also avoid the redundant assignment item = input[i]:
for item in input:Secondly, there’s no need to check for the item explicitly in result.keys() – the following line does the same work in less code:
if item in result:Finally, an if statement in this case is redundant anyway because using get(item, 0) already does the checking work for us, so the entire if statement and incrementation may be replaced with just one line:
result[item] = result.get(item, 0) + 1Next in line is part responsible for sorting the items, so that in the output we have the in an alphabetical order.
keys = []
for key in result.keys():
keys.append(key)
keys.sort()All of these 4 lines can be replaced just by one:
sorted_keys: List[str] = sorted(result)The last challenge is finding a way how to get rid of this ugly dictionary serialization:
output = ''
for key in keys:
output += str(key) + ': ' + str(result[key]) + ', '
return output[:-2]It’s ugly because string concatenation like above is inefficient and imposes on us some additional things to do, like removal of the trailing coma. This can be improved by using join function which (as the name suggests) will join our sorted_keys list, separate its elements with coma and return it in a form of a string:
output = ', '.join(f'{key}: {result[key]}' for key in sorted_keys)
return outputEventually, our refactored function looks like this:
from typing import List, Dict
StringContainer = List[str]
SerializedDictionary = str
def count_occurrences(input: StringContainer) -> SerializedDictionary:
result: Dict[str, int] = {}
for item in input:
result[item] = result.get(item, 0) + 1
sorted_keys: List[str] = sorted(result)
output = ', '.join(f'{key}: {result[key]}' for key in sorted_keys)
return outputShorter, simpler and easier to maintain. If we now call it like this:
print(count_occurrences(['apple', 'banana', 'apple', 'orange', 'banana', 'apple']))The output will be as follows:
apple: 3, banana: 2, orange: 1Note for advanced: if you have already some experience with Python, you can most probably see that almost entire function above can be replaced using
Counterobject fromcollections. I didn’t want to use it because it would hide many important points in the “bad” function and that’s just not the idea of this article, but to make the article complete, below you can find the function’s implementation usingCounter:
from typing import List
from collections import Counter
def count_occurrences(input_list: List[str]) -> str:
counter = Counter(input_list)
return ', '.join(f'{key}: {value}' for key, value in sorted(counter.items()))Read also:
- Sharing variable between bash scripts
- A 40-line LLM-based bash command executor in Python
- GTest and short-circuit evaluation in C++
- AI is powerful. Snippets are instant.
- From AUTOSAR to S-Core: the first C++ pub/sub implementation
- How to write Arduino Uno code with Python?
- Combining Bazel with Docker
- Running commands with timeout on Linux
- Running Python unit tests with CMake
- Thirdparty dependencies with FetchContent
- Bug of the week #11
- Combining CMake with Docker
- How to search the internet from Linux terminal?
- Folding expressions in C++
- How to derive from an enum in Python?
- Bug of the week #10
- Trying ROS2: client/server within a single container
- Make C++ a better place #4: Go as an alternative
- How to convert hex to dec in Linux terminal?
- Setting up a Python project with CMake
- Separating builds for different configs with Bazel
- Trying ROS2: pub/sub within a single container
- Bug of the week #9
- UDP multicasting with Python
- Destruction order vs thread safety in C++
- Let’s review some code: C++ #2
- Make C++ a better place #3: D as an alternative
- Registering callback using std::function in C++
- Bug of the week #8
- TCP client/server with Python
- Simple menus in Bash scripts with select
- Calling member function on a nullptr in C++
- Bug of the week #7
- Python lru_cache explained
- How to dockerize a Python application?
- Make C++ a better place #2: CppFront as an alternative
- Parameters combinations in GoogleTest
- Data transfer with curl
- Python reduce explained
- Bug of the week #6
- Custom literals in C++
- Linux and hash command
- 5 Python good practices which make life easier
- Let’s review some code: Python #1
- Make C++ a better place #1: What does better mean
- Enums vs enum class in C++
- Bug of the week #5
- UDP client/server with Python
- Hard links in Linux
- Functions calling order in unit tests in C++
- Bug of the week #4
- Yield in Python – state machines, coroutines and more
- Copy files from another branch with Git
- Make C++ a better place #0: Introduction
- 5 misconceptions about std::move in C++
- How to use xargs on Linux?
- How to test method call order with unittest in Python?
- Bug of the week #3
- Build & run C++ unit tests with CMake
- Arrange text with sort on Linux









