Just some more things that I thought of. Since my last post received some attention, I thought I write a little more about things that I came accross in Python. Coming originally from Java, most of those things came as a surprise to me.
Tilde operator
# You can index lists from the front
a = ['a', 'b', 'c', 'd', 'e']
print(a[0]) # 'a'
print(a[1]) # 'b'
# You can also index them from the back
# starting with -1 and going backwards
print(a[-1]) # 'e'
print(a[-2]) # 'd'
# The tilde operator when applied to a number `x` calculates `-x - 1`,
# which can be useful for list indexing
#
# x ~x
# 0 -1
# 1 -2
# 2 -3
# 3 -4
# 4 -5
print(a[~0]) # 'e' = 0th element from the back
print(a[~1]) # 'd' = 1st from the back
[0] and 0
# Consider this function
def foo(data):
"""Prints the status of my list."""
if data: # Don't do this
print("The list contains some items.")
else:
print("We got an empty list.")
foo([]) # ok -> "We got an empty list."
foo([1,2,3,4]) # ok -> "The list contains some items."
# Empty lists are treated as false in Python.
# However, False and 0 are also false, which can lead to some
# unexpected behavior if your client, does not know that you
# expect him to pass a list.
foo([0]) # still ok -> "The list contains some items."
foo(0) # NOT ok -> "We got an empty list."
foo(False) # NOT ok -> "We got an empty list."
def foo(data):
if len(data) > 0: # Safer version
print("The list contains some items.")
else:
print("We got an empty list.")
Default values
Default values in Python should not be mutable, such as a list or a dictionary. Default values are evaluated when the def
statement they belong to is executed. That is, they are evaluated only once.
# Don't do this
def foo(bar=[]):
bar.append(1)
return bar
foo() # returns [1] --> ok
foo() # returns [1,1] --> not ok = the same list as before
foo() # returns [1,1,1] --> also not ok = again, the same list
# Do this instead
def foo(bar=None):
if bar is None:
bar=[]
...
A lot of ways to call the same function
def draw_line(length, type="solid", color="black"):
print(length, type, color)
draw_line(5) # 1) 5 solid black
draw_line(length=5) # 2) 5 solid black
draw_line(5, "dashed") # 3) 5 dashed black
draw_line(5, "dashed", "green") # 4) 5 dashed green
draw_line(5, type="dashed", color="green") # 5) 5 dashed green
# Can we take this a step further? Definitely.
draw_line(type="dashed", length=5) # 6) 5 dashed black
args = [5] # could also be a tuple (5,)
draw_line(*args) # 7) 5 solid black
args = {"length": 5, "color": green}
draw_line(**args) # 8) 5 solid green
More on functions
# Pre-fill functions with the arguments you use often
def draw_line(length, type="solid", color="black"):
print(length, type, color)
from functools import partial
draw_green_line = partial(draw_line, color="green")
draw_green_line(5) # 5 solid green
draw_unit_line = partial(draw_line, length=1)
draw_unit_line() # 1 solid black
# If you want to use keyword arguments but you do not want
# to provide default values, you can do that as well
def draw_line(*, length, type, color): # all three are required
...
# Allow some positional arguments, but forbid 3) and 4)
def draw_line(length, *, type="solid", color="black"): # only length is required
...
Any more ideas on how to call a function? Know any more pythonic coding things, that you found helpful in the past? Comment below.
Oldest comments (3)
Hi there,
good post, I remember when I crossed over from Java to Python and it took some time to adjust.
If we're talking about pythonic code, an interesting tidbit I once ran across is joining two dicts in a one liner.
I appreciate this article. I got me thinking about some of the ways I use python and made me want to make some improvements based on what you have here.
I was looking at your [0] and 0 examples and had a question/comment.
I was tinkering with your
foo
function and noticed that I could still pass a string in and it would be seen as valid. A string in Python has a length. If I called the function with a string,foo("hello")
for example, I would still get the output of "This list contains some items".I think it may be better to evaluate whether
data
is truly a list and not empty by doing something like this.What do you think? Am I missing something?
Hi,
Thanks for your comment, I think you are right. If you pass a string to
foo()
you also get some unexpected behavior. You already posted a remedy to the situation: checking the type of thedata
argument. That made me think, if there are other ways, that do not rely on type checking. (I know, type checking is not an expensive operation, but still...)Two more ways, you could alleviate the situation: Working with conventions, if the caller does not respect the contract, they get unexpected behavior:
Using type hinting: Since Python 3.5 you can specify the type of arguments, and static type checkers can use this information, to determine if you pass the correct types.