Welcome toTencent Cloud + community, get more Tencent mass technology practice dry goods oh ~
This article is published in the cloud + community column by Goose Factory
Author: Arthur | tencent IEG senior engineer
What is a “Python craftsman”?
I’ve always felt that programming is something of a “craft,” because elegant and efficient code is as enjoyable as the perfect craft.
In the process of crafting code, there are big projects: what architecture to use, what design patterns to use. There are also more minor details, such as when to use Exceptions or how to name variables. The truly great code is made up of countless great details.
This “Python Craftsman” series is my little experiment. It focuses on sharing some of the “small” things in Python programming. I hope I can help every programmer on the road.
Series of articles:
- Python Craftsman: Use variables to improve code quality
The preface
Writing conditional branching code is an integral part of the coding process.
As a road metaphor, the code in the real world is never a straight highway, but more like a map of a city with countless fork roads. We coders are like drivers, and we need to tell our program whether to turn left or right at the next corner.
It is important to write good conditional branching code, because poor, complex branching is very confusing and can degrade code quality. So, this article will focus on some of the things you should do when writing branching code in Python.
Branching code in Python
Python supports the most common if/else conditional branch statements, though it lacks the switch/case statements common in other programming languages.
In addition, Python provides else branches for for/while loops and try/except statements, which can come in handy in special situations.
I’m going to talk about best practices, common techniques, and common pitfalls for writing good conditional branching code.
Best practices
1. Avoid multi-layer branch nesting
If this article could be reduced to a single sentence, it would be ** avoid branch nesting at all costs **.
Deep branch nesting is one of the most common mistakes many novice programmers make. If a novice JavaScript programmer writes many layers of branch nesting, you might see layer after layer of braces: if {if {if {… }}} instead! More commonly known as “Nested if Statement Hell”.
But because Python uses indentation instead of {}, deeply nested branches can have more serious consequences than in other languages. For example, too many indentation levels can easily cause code to exceed PEP8’s word-per-line limit. Let’s look at this code:
Def buy_fruit(nerd, store): """ def buy_fruit(nerd, store): """ if store.is_open(): if store.has_stocks("apple"): if nerd.can_afford(store.price("apple", amount=1)): nerd.buy(store, "apple", amount=1) return else: nerd.go_home_and_get_money() return buy_fruit(nerd, store) else: raise MadAtNoFruit("no apple in store!" ) else: raise MadAtNoFruit("store is closed!" )Copy the code
The biggest problem with the above code is that it translates the original conditional branching requirement too directly, resulting in only a dozen lines of code with three levels of nested branching.
Such code is poorly readable and maintainable. But we can optimize this code with a very simple trick: “End early” :
def buy_fruit(nerd, store):
if not store.is_open():
raise MadAtNoFruit("store is closed!")
if not store.has_stocks("apple"):
raise MadAtNoFruit("no apple in store!")
if nerd.can_afford(store.price("apple", amount=1)):
nerd.buy(store, "apple", amount=1)
return
else:
nerd.go_home_and_get_money()
return buy_fruit(nerd, store)
Copy the code
“Early termination” refers to the use of return or raise ** statements in a function to terminate the function in a branch. ** For example, in the new buy_fruit function, when the branch condition is not met, we simply throw an exception to end the branch. Such code has no nested branches and is more direct and readable.
Encapsulate overly complex logical judgments
If the expression in the conditional branch is too complex and there are too many not/and/ OR, then the readability of the code is impaired, as in this code:
# If the activity is still open and the remaining quota is more than 10, all genders are female, If activity.is_active and activity.remaining > 10 and \ user.is_active and (user.sex == 'female' or user.level > 3): user.add_coins(10000) returnCopy the code
For such code, we can simplify the code by encapsulating specific branch logic into functions or methods:
if activity.allow_new_user() and user.match_activity_condition():
user.add_coins(10000)
return
Copy the code
In fact, after rewriting the code, the previous comment text can actually be removed. ** Because this code is already self-explanatory. ** What kind of users meet the activity criteria? This question should be answered by the specific match_activity_condition() method.
Hint: Proper encapsulation not only directly improves the readability of the code, in fact, encapsulation is necessary if the above activity logic appears more than once in the code. Otherwise, repeating the code would greatly damage the maintainability of the logic.
3. Watch out for duplicate code under different branches
Duplicate code is the natural enemy of code quality, and conditional branch statements can easily become a disaster area of duplicate code. So when we write conditional branching statements, we need to be careful not to produce unnecessary duplicate code.
Let’s take a look at this example:
# create a new user profile for a new user, otherwise update the old profile if user.no_profile_exists: Create_user_profile (username=user.username, email=user.email, age=user.age, address=user.address, 0 points=0, created=now(),) else: update_user_profile( username=user.username, email=user.email, age=user.age, address=user.address, updated=now(), )Copy the code
In the above code, we can see at a glance that under different branches, the program calls different functions and does different things. But, because of the repetitive code, it’s hard to tell the difference.
In fact, thanks to Python’s dynamic nature, we can easily rewrite the above code to make it significantly more readable:
if user.no_profile_exists:
profile_func = create_user_profile
extra_args = {'points': 0.'created': now()}
else:
profile_func = update_user_profile
extra_args = {'updated': now()}
profile_func(
username=user.username,
email=user.email,
age=user.age,
address=user.address,
**extra_args
)
Copy the code
When you’re writing branch code, pay extra attention to duplicate code blocks generated by branches, and don’t hesitate to kill them if you can easily.
4. Use ternary expressions with caution
Ternary expressions are a syntax supported by Python since version 2.5. Until then, the Python community had thought that ternary expressions were unnecessary and that we needed to simulate them using x and a or B.
The truth is that, in many cases, code that uses plain if/else statements is actually more readable. The blind pursuit of ternary expressions can easily tempt you to write complex, unreadable code.
So, remember to use ternary expressions for simple branches of logic.
language = "python" if you.favor("dynamic") else "golang"
Copy the code
For most cases, use plain if/else statements.
Common techniques
1. Use de Morgan’s Law
When making branching judgments, we sometimes write code like this:
If not user.has_logged_in or not user.is_from_chrome: return "our service is only available for chrome logged in user"Copy the code
When you first see the code, do you have to think for a moment to understand what it wants? This is because there are two not and one OR in the logical expression above. We humans are just not good at dealing with too many “negatives” and “or” logic.
This is where de Morgan’s law comes in. In layman’s terms, de Morgan’s law states that not A or not B is equivalent to not (A and B). With this transformation, the above code can be rewritten to look like this:
if not (user.has_logged_in and user.is_from_chrome):
return "our service is only open for chrome logged in user"
Copy the code
So, is the code a lot easier to read? Remember de Morgan’s law, a lot of times it’s useful for simplifying code logic in conditional branches.
2. “Boolean true and false” for custom objects
We often say that in Python, “Everything is an object.” In fact, not only is everything an object, but there are a number of magical methods (called user-defined methods in the documentation) that can be used to define the behavior of objects. We can influence code execution in magical ways that we can’t in other languages.
For example, all Objects in Python have their own “Boolean true/false” :
- Boolean value of False object: None, 0, False, [], (), {}, set (), frozenset (),… .
- Boolean True objects: non-zero values, True, non-empty sequences, tuples, ordinary user class instances,… .
With the built-in bool() function, you can easily check whether an object is true or false. Python also uses this value for conditional branching:
>>> bool(object())
True
Copy the code
The point is, although all user class instances have true booleans. But Python provides a way to change this behavior: a custom class’s __bool__ magic method (nonzero in Python 2.x). When a class defines a bool method, its return value is treated as the class instance’s Boolean value.
In addition, bool is not the only way to influence whether an instance’s Boolean is true or false. If the class does not define a bool method, Python will also try to call len * (that is, len on any sequence object) to determine whether the instance is true or false by the result being zero.
So what does this feature do? Take a look at the following code:
class UserCollection(object): def __init__(self, users): self._users = users users = UserCollection([piglei, raymond]) if len(users._users) > 0: print("There's some users in collection!" )Copy the code
In the above code, the users._users length is used to determine if the UserCollection has content. In fact, the above branch can be made simpler by adding the len magic method to UserCollection:
class UserCollection: def __init__(self, users): self._users = users def __len__(self): Return len(self._users) users = UserCollection([piglei, Raymond]) # If users: print("There's some users in collection!" )Copy the code
By defining the magic methods len and bool, we can make the code more pythonic by letting the class control which Boolean values it wants to represent.
3. Use all()/any() in the conditional judgment.
The all() and any() functions are great for use in conditional judgment. These two functions take an iterable and return a Boolean value where:
- All (seq) : Returns True only if all objects in seq are Boolean True, False otherwise
- Any (seq) : Returns True as long as any object in seq is Boolean, False otherwise
Suppose we have the following code:
Def all_numberS_gT_10 (numbers): """ return True only if all numbers in the sequence are greater than 10 "" if not numbers: return False for n in numbers: if n <= 10: return False return TrueCopy the code
Using the all() built-in function and a simple generator expression, the above code could be written like this:
def all_numbers_gt_10_2(numbers):
return bool(numbers) and all(n > 10 for n in numbers)
Copy the code
Simple and efficient, with no loss of usability.
4. Use the else branch in try/while/for
Let’s look at this function:
def do_stuff(): first_thing_successed = False try: do_the_first_thing() first_thing_successed = True except Exception as e: Print ("Error while calling do_some_thing") return # if first_thing_successed if first_thing_successed: return do_the_second_thing()Copy the code
In the case of do_stuff, we want to proceed to the second function call only if the * (that is, no exception is thrown) * is successfully called by do_the_first_thing(). To do this, we need to define an additional variable, first_thing_successed, as a marker.
In fact, we can achieve the same effect in a simpler way:
def do_stuff():
try:
do_the_first_thing()
except Exception as e:
print("Error while calling do_some_thing")
return
else:
return do_the_second_thing()
Copy the code
After the else branch is appended to the end of the try block, the do_the_second_thing() branch is executed only after all statements under the try have executed normally (i.e., no exceptions, no returns, no breaks, etc.).
Similarly, the For /while loop in Python supports the addition of else branches, which indicate that code under the else branch is executed only when the iterator used by the loop is normally exhausted, or when the condition variable used by the while loop becomes False.
Common pitfalls
1. Comparison with None
In Python, there are two ways to compare variables: == and is, which have fundamentally different meanings:
- == : indicates whether the two points to the same value
- Is: indicates whether they refer to the same content in memory, that is, whether id(x) equals id(y).
None is a singleton object in Python. If you want to determine whether a variable is None, use is instead of ==, because only is can strictly indicate whether a variable is None.
Otherwise, the following situation may occur:
>>> class Foo(object):
... def __eq__(self, other):
... return True
...
>>> foo = Foo()
>>> foo == None
True
Copy the code
In the above code, the Foo class easily satisfies the == None condition by customizing eq magic methods.
So, when you want to determine if a variable is None, use is instead of ==.
2. Pay attention to the priority of and and or
Take a look at these two expressions and guess if they have the same value.
>>> (True or False) and False
>>> True or False and False
Copy the code
The answer is: no, the values are False and True respectively, did you guess correctly?
The crux of the matter is that the and operator takes precedence over or. So the second expression above is actually True or (False and False) in Python’s view. So the result is True instead of False.
When writing expressions that contain more than one AND and OR, pay extra attention to the precedence of and and OR. Even if the execution priority is exactly what you need, you can add extra parentheses to make the code clearer.
conclusion
That’s the second article in the Python Craftsman series. I don’t know if the content of the article is to your taste.
Branching statements within code are inevitable, so when writing code, we need to pay special attention to its readability to avoid confusing others who see the code.
After reading the article, do you have anything to tease? Let me know in the comments.
Question and answer
Beginner Python code problems
reading
Python | four black run other program of science and technology
Python | line 21 easily get spell checker
Python package | automatically generated expression, dou figure invincible hand from now on!
Machine learning in action! Quick introduction to online advertising business and CTR knowledge
This article has been authorized by the author to Tencent Cloud + community, more original text pleaseClick on the
Search concern public number “cloud plus community”, the first time to obtain technical dry goods, after concern reply 1024 send you a technical course gift package!
Massive technical practice experience, all in the cloud plus community!