Lazy Looping: The Next Iteration by Trey Hunner
Published October 25, 2019
This video features Trey Hunner at DjangoCon US 2016 in Philadelphia, Pennsylvania, USA.
DjangoCon US 2016 - Readability Counts by Trey Hunner
Most code is read many more times than it is written. Constructing readable code is important, but that doesn't mean it's easy.
If you've ever found unreadable PEP8-compliant code and wondered how to fix it, this talk is for you.
Long-lived code must be maintainable and readability is a prerequisite of maintainability. It's easier to identify unreadable code than it is to create readable code.
Let's talk about how to shape tricky code into something more readable and more maintainable.
During this talk we'll discuss:
whitespace
self-documenting code
modularity
expectation management
We'll conclude this talk with a checklist of questions you can use to make your own code more readable.
This talk was presented at: https://2016.djangocon.us/schedule/presentation/14/
LINKS:
Follow DjangCon US 👇
https://twitter.com/djangocon
Follow DEFNA 👇
https://twitter.com/defnado
https://www.defna.org/
Readable code is essential because developers spend more time reading code than writing it, and readability underpins maintenance, bug fixes, feature work, and onboarding. Trey Hunner argues that line breaks should reflect logical structure rather than arbitrary width limits, and that projects need explicit style guides beyond PEP 8. He recommends accurate, descriptive names; extracting intent into variables and functions; reading code aloud; and choosing specific Python constructs such as context managers, comprehensions, containers, and classes when they match the problem. The central principle is to make code communicate its intent clearly, while treating readability as an active design decision.
Summarised automatically from the transcript.
Automatically transcribed, so expect mistakes in names and technical terms.
Speaker 1: Come on, yo.
Speaker 2: Hey everyone. Let's talk about readability. So before we talk about readability, let's just kind of make sure that we're all on the same page. So, textbook definition, readability is really just the measure of how easily we can read our code. So I'm assuming you're all here because you care about readability, but why do we actually care about readability? What makes it actually important? So every time you fix a bug, change some functionality, or add a new feature to your code, you probably need to read some code. So you probably read code more often than you write it.
Speaker 2: Also, you do sometimes need to change code. Code doesn't always stagnate immediately after you write it. In order to change something, you need to read it. So readability is really a prerequisite for maintainability. You can't have maintainable code unless you have readable code. Lastly, not all teams are immortal. You do sometimes need to hire developers, and those developers will need to be onboarded into your team. It's a lot easier to onboard people when they can read your code. So before we get started, let's make it clear what we're not going to talk about. We're not going to talk about how easy it is to write code. We're not going to talk about how easy it is for the computer to read your code and to run your code.
Speaker 2: We're only talking about how easy it is for humans to read your code. So we'll talk about how to structure your code, which pretty much boils down to where you put your line breaks. We'll also talk about naming unnamed things and also naming things more descriptively. And finally we'll reconsider some of the programming idioms that we use every day. We'll be looking at a lot of small code examples. So if you can't keep up, don't worry. I'm going to tweet out the code afterwards. There's a lot going on. Alright, so let's talk about the structure of our code first. In the modern age, line length is not a technical limitation anymore.
Speaker 2: Screens are really wide. Line length is not about punch cards, it's about how readable your code is. Long lines are not easy to read. Line length is a little flawed though because when it comes to readability, indentation isn't quite the same as code. So instead of focusing on line length, I'd propose that we focus on text width. This is basically line length where we ignore the indentation. Now I don't know what a good average text width is. I prefer kind of a 60-character maximum. But really most importantly, we should focus on making our code readable when it comes to text width and not worrying about some arbitrary limit Short lines are not our end goal.
Speaker 2: Readability is our end goal. Alright, let's talk about line breaks. So this code has a text width under 60 characters. As you read this code, you're probably trying to figure out what it does. You're not going to figure out what it does until you've figured out what the structure of this code actually is. You'll eventually notice that that first statement there has a generator expression with two loops in it. And that second statement has a generator expression with one loop and a condition in it. This code is hard to read because the line breaks were inserted completely arbitrarily. The author simply wraps their lines whenever they got near some maximum text width or line length. The author was valuing text width or line length
Speaker 2: as the most important thing. They completely forgot about readability. Is this code more readable This is the same code as before, but the line breaks have been moved around to split up the code into logical parts. So these line breaks were not inserted arbitrarily at all. These were inserted with the express goal of readability. Alright, let's take a look at another example. Let's say you're creating a Django model and one of your model fields has a whole bunch of arguments passed into it. So we're passing a lot of arguments into this foreign key field here, and it's feeling a little unwieldy. Is this a good way to wrap our code over multiple lines?
Speaker 2: What about this way? Is this better or is this worse? What about this one? How does this compare Would anything change if we were using exclusively keyword arguments here? Would that affect our choice at all? So personally, I usually prefer that last strategy for wrapping my lines, especially with all keyword arguments. I almost always prefer that last one. The first one's a little difficult to read, and that second one can be kind of problematic when you have really long lines like we do here. Alright, so let's take a look at that last strategy a little more closely. Would it be better to leave off the closing parenthesis, or rather to put that closing parenthesis on its own line?
Speaker 2: Okay, got a lot of nods here. What if we added a trailing comma? Would this be an improvement or is this worse? Better, okay. Alright, so personally I I also prefer this last uh this last one here. Now, I am certain that there are some of you in this room who are not nodding your heads who disagree with this preference of mine. That fact is okay. The fact that we disagree means that we need to document the way that we're wrapping our function calls in our style guide for every project we make You do have a style guide for every project you make, right? Your style guide doesn't just mention PEP 8.
Speaker 2: I'm gonna dramatically drink some water here as you think about this. All right. So consistency lies at the heart of readability. You need to make sure that you are defining a style guide with really explicit conventions in every single Django project that you make. Do poets use a maximum line length to wrap their lines? No Poets break up their line breaks with purpose. In poetry, inserting a line break is an art form. In code, inserting a line break is an art form. So as programmers, we should wrap our lines with great care. And remember, all your projects should have a style guide that goes well beyond PEP
Speaker 2: 8. Your code style convention should be explicitly documented. Alright, let's talk about naming things. If a concept is important, it needs a name. Names give you something to communicate about. Unfortunately, naming things is hard. Naming a thing requires that you describe it, and describing a thing isn't always easy. Not only that, once you've described a thing, you need to shorten that description into a name. And that's not always easy either. If you can't think of a good short name, use a long and descriptive one. That's a lot better than a subpar name that's really short. You can always shorten a name tomorrow. It's hard to make a name a little bit longer. So worry about accuracy, not the length of your names.
Speaker 2: Okay, let's take a look at some code with some poor variable names. I bet that you do not know what SC stands for in this code. Anyone want to guess? It does not stand for South Carolina. So you might know if you had more context that this stands for state capitals. Don't use two-letter variable names in Python code, use descriptive names. Now speaking of descriptive names, what does the i variable here represent? Index? Okay. Um is it a two -tuple? Maybe something else? Is is I sub zero capitals or is it states? Or something else entirely?
Speaker 2: So when you see an index access, this should be a red flag. Index accesses can usually be replaced by variables. We can do this with tuple unpacking. So you can probably tell now that S and C means state and capital, or maybe you could guess that. Avoid using arbitrary indexes in your code. Whenever possible, use tuple unpacking instead. It's often a lot more explicit to have named variables than it is to have indexes in your code. Now, you probably did guess that S and C means state and capital, or maybe you know because I told you. Uh but there's no reason not to use real words here instead. This makes our code a lot easier to read
Speaker 2: and it wasn't that hard to type. Name every variable with care. Optimize for maximum accuracy and optimize for maximum completeness. Make sure you're describing everything as fully as you can. Okay, let's take a look at an example of code that could use some more variable names. This code returns a list of all anagrams of words that are in the candidates list. It's not bad code, but it's also not the most descriptive code. The if statement in particular is pretty loaded. There's a lot going on there. What if we abstracted out that logic into its own function?
Speaker 2: So with this isAnagram function here , I think it's a lot more obvious that we're checking whether two words are in fact anagrams. We've broken down the problem and described the process that we're using and left out the details. The details are inside that function. Let's take a look at that function. So this is pretty much exactly what we had inside our if statement before. It could use a little bit of work. Firstly, word1. upper, word two. upper, those both appear twice. We've got some code duplication there. Let's fix that Okay, that's a lot better. I certainly find that conditional on that last line easier to read. But I think there's still more room for improvement.
Speaker 2: So one strategy that we could employ here is to read our code aloud So I like to read my code aloud to see how descriptive it is. Here we're sorting our words, checking whether sorted versions are equal, and then checking whether the unsorted versions are or sorry, uh checking whether the Words are not in fact the same word. Now, that description took me a little bit to read because it's not the easiest thing to figure out. That description isn't very helpful. It's not very much like how we describe this in English. If we add some comments to describe this in actual English, we can see that we're actually checking whether the words have the same letters and whether they're not the same words.
Speaker 2: Now, this code is formatted a little bit strangely, but it is more descriptive. We've added comments to describe what's actually going on Whenever you add comments though, that might be a hint that you actually need another variable name or a better variable name. In this case, we need some more variable names. So here we've turned those conditional statements into two new variables that describe what they actually do. Are different letters and have same words describes exactly what we're doing. That last line literally says return have same letters and are different words. We've broken down this problem to make it more clear and more readable because we're conveying the intent of our algorithm, not just the details. Okay, so we ended up adding four extra lines to that code, but we broke down our process a bit in doing so.
Speaker 2: It's a little bit more understandable at first glance. You may think this example is silly. I mean what we started with wasn't really that complex, but this process was a worthwhile mental exercise regardless, even if we're going to end up reverting this code afterwards. The exercise of refactoring your code to be more self-documenting is almost always a worthwhile endeavor. It helps you reframe the way that you actually think about your code. Okay, let's take a look at a complex Django model method. I'm going to go through this one a little bit quickly, and there's a lot of code here. I don't want you to read this code. I'd like you to unfocus your eyes and focus on just the shape of this code. There, did you do it? Okay, so the first thing you'll notice is that this code is broken up into three sections.
Speaker 2: There's three sections because there are three logical parts to this code. If we add very or if we add um comments to each of those sections, we can better see what's actually going on. Now remember I said comments are maybe a step in the direction of making variables that don't exist or making variables with better names. We're missing variables here. We could name these sections by making methods for them. So even when you see the code here, you're tempted to read the comment because the comments what's actually saying what's going on. Now, second step, make methods for these. If we split these out into helper functions Those names actually describe what the code is doing. I left in the doc
Speaker 2: strings because you know there's no reason not to leave in documentation. Documentation is not quite the same as comments. It's always good to document your things If we call those methods in our original function, this now describes the actual process. This is almost like English here. If we wanted to see what each of these is doing, we can jump into that function. We don't have to be um distracted. by the fact that it's doing some really detailed stuff under the hood. Okay, so brief recap. Read your code aloud to ensure that you're describing the intent of your algorithm in detail. Remember that comments are great for describing things, but sometimes a comment is just the first step toward a better variable name or a variable name that didn't exist before.
Speaker 2: Also, make sure you're giving a name to everything. And in general, describe for descriptive and self-documenting code. Okay, so this section is a little long. I may end up skipping over one or two of these little subsections here. Basically, let's talk about the code constructs that we actually use. And make sure that our code constructs are as specific as they should be. When given the opportunity, I prefer to use a more special purpose tool than a more general purpose tool if it makes sense Specific problems call for specific solutions. So let's take a look at exception handling. Here we're opening a database connection, reading from it, and closing the connection.
Speaker 2: We need to make sure that we're closing the connection every time that we exit this code, even if an exception occurs. So we have this try-finally block here. Now whenever you see any code that has kind of a cleanup step, think about whether or not you could use a context manager. It 's not that hard to actually write your own context managers. You just need a dunder enter and a dunder exit method. By the way, dunder stands for double underscore for anyone who's not familiar with that nomenclature Let's look at how this context manager is actually used. Okay, so this is a little bit more clear than what we had before in the sense that we're not distracted by the fact that we have to close our database connection When we're done with it every time. The code does that work for us.
Speaker 2: Now we just implemented our own context manager, but the standard library already had one we could have used in context lib called closing. You don't always have to implement your own context managers, but you can if you want to, and it's not that hard. So whenever you have a cleanup step, think of a context manager. Okay, let's talk about for loops This code loops over something. You can tell that even though it's blurred out. This code actually does a little bit of something more though Specifically, the purpose of this code is to loop over something, check a condition, and create a new list from every item that passes that condition. So we're using a list append, an if statement, and a for loop to accomplish this task. There's a better way to write this code though.
Speaker 2: Here we are accomplishing the same task, but instead of using a for loop and an if statement and a depend call, we're using a list comprehension. We've removed a lot of the unnecessary information that our brains would have to process otherwise. At a glance, we can see that this code is not just looping, it's creating one list from another list. That's a better description of what our code actually does. When you have a specific problem, use a specific tool. Okay, I'm going to skip this one. You can look into the slides afterwards. Essentially, if you have something that has methods that look like this, contains, set, add, remove, is empty.
Speaker 2: This is very similar to what Python objects actually do under the hood. What a container does of any variety, what a list does, what a dictionary does. Think about whether or not you should be making your own version of those containers. You can do that with abstract base classes in the standard library, or you can roll it yourself using Dunder methods. Let's talk about functions. This code connects to an IMAP server and reads email Notice that one of these functions returns a server object, and the other three functions accept a server object. This should be a hint that something weird is going on here. If you ever find that you're passing the same data to multiple functions, think about making a class.
Speaker 2: This is exactly what classes were designed for. I know there's a big pushback against making classes uh and object-oriented programming in general, but classes bundle functionality and data together. Whenever you're doing that in your code, it's appropriate to use them Okay, let's do a recap. When you find yourself wrapping code in redundant tri -finalies or tri-accept blocks, and whenever you have a cleanup step in general, think about using a context manager. Also, when you're making one list from another list, there's an idiom for that. It's called a list comprehension. It might be a little bit uh more clear to use than using a for loop. And when one object uh looks like a container
Speaker 2: but isn't a container, it probably should be a container. You can turn it into a container by using Gunder methods Don't be afraid to use those Dunder methods either, regardless of whether it's for a context manager, a container, or anything else. Dunder methods are are your friend. They're not, I'm not calling them magic methods because they are not magical If you have a specific problem, use a specific solution. When you're writing code, stop to pause every once in a while and actively consider the readability of your code. You can use this checklist as a starting point for your own reflections on your own code's readability As you use this checklist on your code, start to build up that code style guide that we talked about earlier
Speaker 2: that I know you all have but want to improve. And remember that every project does need a detailed code style guide. The more decisions you can offload to the style guide, the more brain power you'll have left over to spend on more interesting and less trivial things. And finally, here's a list of videos I recommend watching when you get home. Some of which contradict some of the things I said, some of which support them. Do we have time for questions?
Speaker 3: We do. We have time for two short questions. Hi
Speaker 4: Trey, great talk. I often forget myself as a pythonista when I can break up a list comprehension. So are there rules? Does Python care and what are the rules if I'm trying to follow your example and break up a list comprehension to replace a for if statement?
Speaker 2: When you can create a list comprehension out of a for loop?
Speaker 4: No, more like I'm used to writing it as one big long thing. Does Python care where I split it into multiple lines?
Speaker 2: Oh right, right. So as long if you are inside parentheses, square brackets, or curly braces in Python Python allows you to break that up wherever you want. In fact, you can even indent your code in really weird ways just to make people unhappy. Don't do that though. Implicit line wrapping is um a really easy thing in Python. So you can break it up wherever. I would recommend breaking it up before the for clause and before the if, basically before the logical components.
Speaker 4: Cool. Thank you.
Speaker 2: Yep. Thanks
Speaker 5: Twee. Good talk. Um do you have a suggestion for a good guide a starting guide that's out there that you recommend to look at to then adopt our
Speaker 2: own? Uh did it look to to what?
Speaker 5: To are there good style guides out there that we can adopt to and adjust to our own personal needs.
Speaker 2: No, um this is kind of one of those do as I say, not as I do situations. None of my open source projects have a style guide So you're welcome.
Speaker 3: All right. Well with that, let's get ready for the next session coming up in five minutes. Thanks, Trey.
Readable code is a prerequisite for maintainability because fixing bugs, changing behavior, and adding features all require reading code. It also makes it easier to onboard new developers.
Discussed at 1:01Focus on readability and text width rather than an arbitrary line-length limit. Text width ignores indentation, and Trey prefers roughly 60 characters, but short lines are not the goal by themselves.
Discussed at 2:32Line breaks should separate logical parts of the code rather than being inserted wherever a maximum width is reached. For multiline calls, Trey generally prefers putting keyword arguments on separate lines with a trailing comma, but says each project should document its own conventions.
Discussed at 4:04A project style guide should explicitly document the team's formatting and naming conventions, including decisions that go beyond PEP 8. Consistency across the project is central to readability.
Discussed at 6:30Use accurate, complete, descriptive names, even if they are longer than ideal; a long name can be shortened later. Avoid cryptic abbreviations such as two-letter names when real words would make the code clearer.
Discussed at 7:18Usually, yes: tuple unpacking replaces arbitrary indexes with named variables, making the data's meaning explicit. Index access should be treated as a warning sign when unpacking can express the intent more clearly.
Discussed at 8:54Read the code aloud and name the concepts and intermediate results it is actually using. Comments can help, but they may indicate that a missing or better variable name—or a helper function—is needed to express the algorithm's intent.
Discussed at 11:58Use a context manager when code has a cleanup step that must happen even if an exception occurs, such as closing a database connection. You can write one with __enter__ and __exit__, or use an existing helper such as contextlib.closing.
Discussed at 15:52Use a list comprehension when the task is to create one list from another by looping over items and filtering them with a condition. It communicates that purpose more directly than a loop with an if statement and append call.
Discussed at 17:23Consider a class when the same data is passed to several functions, because classes bundle related data and functionality together. Trey uses this as an example of choosing a more specific construct for the problem.
Discussed at 18:48Python allows implicit line wrapping inside parentheses, square brackets, and curly braces, so a list comprehension can be split wherever needed. Trey recommends breaking before logical components such as the for and if clauses.
Discussed at 21:37Note: We understand that names change, people change, and bodies change. We respect each individual's journey and privacy. If you have any concerns about a video or need us to remove content, please don't hesitate to contact us. We will handle your request with care and promptly address any issues.
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 15, 2026
Published July 14, 2026