Python functions naming: 10 tips
Here are some tips I use to provide better naming for functions I write.
Don’t use save/load without to/from
save_user_to_mongo
is a nice name; save_user
is not. The same thing goes with load_categories_from_cache
and load_categories
.
If you have multiple data storages, a function call doesn’t tell you which one you are using. You have to ctrl-click it. Every. Damn. Time.
Use test_<entity>_<behavior> template for tests names
Say we are writing a unit test to a function called validate_user_form
. Here are some bad test names: test_validation_ok
, test_user_can_submit_profile_data
, test_test_test
, test_asdfqerfjeghmnsa
. And these names look nice and informative: test_validate_user_form_raises_no_errors_on_correct_data
, test_validate_user_form_raises_an_error_on_empty_email
, test_validate_user_form_raises_an_error_on_existing_phone
.
Important note on the behaviour part: it should say what exactly we are testing. I used to use “works_fine” and “is_ok” as a behaviour part, and it sucks: when a test fails, I had to go to the test and check what exactly is going on. Now when a test suite tells me if I broke some test, I go straight to the function it tests with all info I need to fix a problem.
These names can be quite long some times, but the use case for the names is quite different: we are not using the names in our code, but we see them in broken CI reports. It’s okay to have logs names there if they allow us to skip the “wtf is going on in the test” part.
Beware of using “and”
process_and_save_profile_info
is quite readable but still quite bad. Not because of its name, but because of its nature: it should be clean_profile_info
and save_profile_info_to_db
.
This rule is related to the single-responsibility principle and command-query separation pattern. Don’t make Bertrand Meyer angry.
Make a function name readable with its arguments
list_item_to_marketplace(marketplace, item)
is a nice name, but list_item_to(marketplace, item)
is better.
Here is an example call of a first option: list_item_to_marketplace(Marketplace.eBay, user_item)
. Word “marketplace” is repeated! Even if it looks like list_item_to_marketplace(eBay, user_item)
it will be clumsy: we all know that eBay is a marketplace. In contrast, list_item_to(eBay, user_item)
is great.
This rule allows us to make a function name a little shorter. Such names often end up with a preposition: _at
, _to
, _in
and so on.
Think of repeating entity type in a function name
Sometimes you should duplicate data from the module path, and sometimes you should not.
Say you have parsers.py
with a lot of parsers in it. Should you call parsers parse_integer
and parse_string
or integer
and string
? It depends on how you will use it.
You could use them in one place: parsers=[integer, string]
, then short names are okay.
You can use them directly inside business logic: age = integer(raw_data[‘user’][‘age’])
, then you might want to add the “parse” part, so the line looks more readable. But even in this case, you could stick with integer
if you change a rule for imports of the parsers. Look at this: age = parsers.integer(raw_data[‘user’][‘age’])
. This gives us a lot of information, but it is not very English-like in my taste.
So, think of how and when you will use the function. Sometimes it is worth repeating a verb (or some other detail) from the module name.
Keep function names as short as possible (without hurt to a context)
save_full_user_profile_to_postgresql_storage_sync
can be easily shorted to save_full_profile_data_to_postgresql_sync
since we all know that PostgreSQL is storage. If all functions in your project are synchronous, you probably should remove _sync
part. If you don’t have “short” or “partial” user profiles, you should make it save_user_profile_to_postgresql
. And finally, you might want to replace postgresql
with db
(if Postgres is the only database storage you’re using in the project). So we made a long path from save_full_user_profile_to_postgresql_storage_sync
to save_user_profile_to_db
. The new name is much shorter, but it keeps the same context. Magic!
The transformation depends on your project. If you have sync and async fetchers, use Postgresql and Mysql as storages, use Postgres as data storage and as cache storage, you should stick with the initial name. But you most likely don’t.
So, give the names of your functions a meaningful context and think of length optimization.
Use subject area vocabulary
Say you’re writing a website about books publishing. You most likely use these words in your names: book, publisher, edition, typography, revision, impression, remainders and so on.
Make sure this vocabulary is shared between team members, and everyone is using it. If you’re using the term “typography”, then using “printing gallery” is an error.
A good thing to do is write the vocabulary down with meanings of all area-specifics one can find in the project.
Forbid typos
I hate typos. Hate them with me.
Set up an automation that will check for typos in names inside your code. If a typo is found, it breaks the build.
I use scspell for that thing. It takes some work to set the thing up and collect initial vocabulary, but now it works like a charm.
Another profit of tools like scspell is that they make you collect all specific words you use in a single place. Check the previous item of the article. It plays well together with vocabulary files.
Don’t forget about grammar
are_user_active
— looks weird, right? It looks fine once you’ve changed it to is_user_active
.
Usually, function names use basic English grammar: is/are, have/have, present/past, simple/perfect. It won’t take you long to fix errors like that, but it increases readability.
Check if a function call is readable
When writing a function, think about how and where the function will be called. Will the name fits there? Will it be readable?
I was trying to collect tricks to make it readable in any case, but don’t do the Cargo cult around the thing.
A good name for a function should be readable in two places: where the function is defined and called.
You won’t miss the definition since you’re writing it when writing a function name.
All you need is to think about a second case: will the name look readable when the function is called.