u/RojerGS
-
1,209points
RojerGS
RojerGS
Hey there, there's a nice series of blog posts (I wrote) at https://mathspp.com/blog/pydonts whose aim is that: teaching Pythonic code through real examples. I did write them, but the community reads them and enjoys them and gives good feedback. There's also a book version at https://gum.co/pydonts .
RojerGS
I interpreted your post as also asking for good Python practices, not just project practices, which is what most people seem to be sharing. There's a nice compilation of articles about best practices of Python itself here: https://mathspp.com/blog/pydonts, with the corresponding book here: https://leanpub.com/pydonts; full disclosure, I'm the author of both.
Throughout those articles I linked I try to dissect what elegant Python code looks like and I back my claims with examples taken out of real code, more often than not from the Python Standard Library itself. What is more, I include all my references so you can actually exercise your right to question what I write. There may be a couple of things in there that are worth your time :)
RojerGS
Hey there, full disclosure, but I will end up linking to two articles I wrote myself.
Python's match is introduced as a statement for "structural pattern matching", i.e. so that you can match things like iterables or recursive data structures; that's when match will really shine (plenty of examples and use cases in https://mathspp.com/blog/pydonts/pattern-matching-tutorial-for-pythonic-code).
When a match looks like a plain switch (that Python doesn't have a specific statement for) then you are likely to be able to find a better solution, or the better solution is just a simple if statement.
I show some of these pitfalls, and actual better solutions, in https://mathspp.com/blog/pydonts/structural-pattern-matching-anti-patterns.
Don't have time to write more instead of linking because I am preparing a Python stream right now. If you ping me in 5 or 6 hours I can be more explicit here in case you really don't want to read the articles :)
Python is A LOT OF FUN, so keep learning ;)
Hey, I see some people already suggested alternative ways of writing your function, and those are cool alternatives, but I would just like to show you step by step how I would get there.
So, Python's loops are cool because they allow you to traverse structures directly, so if you have a list and you want to access its elements in turn, you can just do something like
for element in mylist:. However, in your case, you also need the indices of the elements, to detect words in even/odd positions, so the Python pattern is to useenumerate(I have an introductory article aboutenumeratehere if you want).So, you could rewrite the loop as
py def myfunc(a): empty = [] for i, word in enumerate(a): if i%2 == 0: empty.append(word.upper()) else: empty.append(word.lower()) return ''.join(empty)Now, another thing you could change is the variable
empty, because it doesn't reflect its usage. Rename it to something not original, but more clear, likeresult, or to something likewords. These names aren't perfect, but they are better than callingemptyto something that you are filling up immediately:py def myfunc(a): words = [] for i, word in enumerate(a): if i%2 == 0: words.append(word.upper()) else: words.append(word.lower()) return ''.join(words)Now, something interesting is that the
if: ... else: ...has a portion of repeated code, which is the.appendbit. You always want to append, you just append different things, so you can factor that repetition out:py def myfunc(a): words = [] for i, word in enumerate(a): if i%2 == 0: capitalisation = word.upper() else: capitalisation = word.lower() words.append(capitalisation) return ''.join(words)Now, this might not seem like a huge gain, but having that bare
if: ... else: ...sets up the ground for theif...elseexpression, also known as the ternary if expression, which allows you to assign a value conditionally:py def myfunc(a): words = [] for i, word in enumerate(a): capitalisation = word.upper() if i%2 == 0 else word.lower() words.append(capitalisation) return ''.join(words)This also shows that, instead of saving the value in a variable to use it right afterwards, you can use it directly:
py def myfunc(a): words = [] for i, word in enumerate(a): words.append(word.upper() if i%2 == 0 else word.lower()) return ''.join(words)Now, the next step is recalling that Python has this amazing feature that is the Truthy and Falsy values of objects (read about it here if you want) and that means you don't need to do the check
i%2 == 0if you swap the return values, becausei%2is interpreted asFalseif it gives0and interpreted asTrueif it gives1:py def myfunc(a): words = [] for i, word in enumerate(a): words.append(word.lower() if i%2 else word.upper()) return ''.join(words)You can read
if i%2as “ifihas a remainder when divided by2”, which is the same as “ifiis odd”.Finally, something that you learn to spot is that a series of
appends to a list might be expensive, so it is nice to try and replace those with list comprehensions if possible:py def myfunc(a): words = [word.lower() if i%2 else word.upper() for i, word in enumerate(a)] return ''.join(words)Now, for a list comprehension, you can actually get away with slightly shorter names:
py def myfunc(a): words = [w.lower() if i%2 else w.upper() for i, w in enumerate(a)] return ''.join(words)Now again, sometimes assignments can be avoided (sometimes they can't, better to have an intermediate assignment than to have a REALLY LONG line) and you can just add the list comprehension inside the
join:py def myfunc(a): return ''.join([w.lower() if i%2 else w.upper() for i, w in enumerate(a)])Now, something cool to note here is that the
[...]of the list comprehension is creating the whole list, and only then feeding it to thejoin, but if you remove the[...], then you are writing a generator expression that “only computes the capitalised words asjoinasks for them”, so you can save some memory (and maybe even some time!) ifais really long:py def myfunc(a): return ''.join(w.lower() if i%2 else w.upper() for i, w in enumerate(a))Now, you don't need to be in love with this final result, but the intermediate steps are worth studying IMO, and I think a nice short/mid-term goal for you would be to aim to write functions that are more or less at this level:
py def myfunc(a): words = [] for i, word in enumerate(a): words.append(word.lower() if i%2 else word.upper()) return ''.join(words)