loading...

4 Useful Things in Python

r0f1 profile image Florian Rohrer ・3 min read

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

SO link

[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

Some explaination

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.

Relevant mystery link

Posted on Sep 18 '17 by:

r0f1 profile

Florian Rohrer

@r0f1

such software.. much wow!

Discussion

markdown guide
 

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.

old_dict1, old_dict2 = {'One':1, 'Two':2}, {'Two':22, 'Four':4}

new_dict = {**old_dict1, **old_dict2}

print(new_dict) # {'One':1, 'Two':22, 'Four':4}
 

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".

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.")

I think it may be better to evaluate whether data is truly a list and not empty by doing something like this.

def foo(data):
    if type(data) == list and len(data)>0:     # Safer version
        print("The list contains some items.")
    else:
        print("We got an empty or invalid list.")

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 the data 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:

def foo(alist):
    """This function expects a list."""
    ...

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.

def foo(data: list) -> None:
    ...