Python Duck Typing, LBYL, EAFP

Duck Typing

A programming style which does not look at an object’s type to determine if it has the right interface; instead, the method or attribute is simply called or used (“If it looks like a duck and quacks like a duck, it must be a duck.”) By emphasizing interfaces rather than specific types, well-designed code improves its flexibility by allowing polymorphic substitution. Duck-typing avoids tests using type() or isinstance(). (Note, however, that duck-typing can be complemented with abstract base classes.) Instead, it typically employs hasattr() tests or EAFP programming.


class Duck:



    def quack(self):

        print('Quack, quack')



    def fly(self):

        print('Flap, Flap!')





class Person:



    def quack(self):

        print("I'm Quacking Like a Duck!")



    def fly(self):

        print("I'm Flapping my Arms!")





# Not Duck-Typed (Non-Pythonic)

def quack_and_fly(thing):

    if isinstance(thing, Duck):

        thing.quack()

        thing.fly()

    else:

        print('This has to be a Duck!')





d = Duck()

quack_and_fly(d)

# Quack, quack

# Flap, Flap!



p = Person()

quack_and_fly(p)

# This has to be a Duck!

EAFP

Easier to ask for forgiveness than permission. This common Python coding style assumes the existence of valid keys or attributes and catches exceptions if the assumption proves false. This clean and fast style is characterized by the presence of many try and except statements. The technique contrasts with the LBYL style common to many other languages such as C.


class Duck:



    def quack(self):

        print('Quack, quack')



    def fly(self):

        print('Flap, Flap!')





class Person:



    def quack(self):

        print("I'm Quacking Like a Duck!")



    def fly(self):

        print("I'm Flapping my Arms!")





# LBYL (Look before you leap) (Non-Pythonic)

def quack_and_fly(thing):

    if hasattr(thing, 'quack'):

        if callable(thing.quack):

            thing.quack()



    if hasattr(thing, 'fly'):

        if callable(thing.fly):

            thing.fly()





d = Duck()

quack_and_fly(d)

# Quack, quack

# Flap, Flap!



p = Person()

quack_and_fly(p)

# I'm Quacking Like a Duck!

# I'm Flapping my Arms!

LBYL

Look before you leap. This coding style explicitly tests for pre-conditions before making calls or lookups. This style contrasts with the EAFP approach and is characterized by the presence of many if statements.

In a multi-threaded environment, the LBYL approach can risk introducing a race condition between “the looking” and “the leaping”. For example, the code, if key in mapping: return mapping[key] can fail if another thread removes key from mapping after the test, but before the lookup. This issue can be solved with locks or by using the EAFP approach.


class Duck:



    def quack(self):

        print('Quack, quack')



    def fly(self):

        print('Flap, Flap!')





class Person:



    def quack(self):

        print("I'm Quacking Like a Duck!")



    def fly(self):

        print("I'm Flapping my Arms!")





# EAFP (Easier to ask forgiveness than permission) (Pythonic)

def quack_and_fly(thing):

    try:

        thing.quack()

        thing.fly()

        thing.bark()

    except AttributeError as e:

        print(e)





d = Duck()

quack_and_fly(d)

# Quack, quack

# Flap, Flap!

# 'Duck' object has no attribute 'bark'



p = Person()

quack_and_fly(p)

# I'm Quacking Like a Duck!

# I'm Flapping my Arms!

# 'Person' object has no attribute 'bark'

LBYL (Look before you leap) (Non-Pythonic)


person = {'name': 'Jim', 'age': 25}

person_missing = {'name': 'Jack'}



# LBYL (Look before you leap) (Non-Pythonic)

def print_person(person):

    if 'name' in person and 'age' in person:

        print(f'I am {person["name"]}. I am {person["age"]} years old.')

    else:

        print('Missing some key.')





print_person(person) # I am Jim. I am 25 years old.

print_person(person_missing) # Missing some key.

EAFP (Easier to ask forgiveness than permission) (Pythonic)


person = {'name': 'Jim', 'age': 25}

person_missing = {'name': 'Jack'}



# EAFP (Easier to ask forgiveness than permission) (Pythonic)

def print_person(person):

    try:

        print(f'I am {person["name"]}. I am {person["age"]} years old.')

    except KeyError as e:

        print(f'Missing {e} key.')





print_person(person) # I am Jim. I am 25 years old.

print_person(person_missing) # Missing 'age' key.

LBYL (Look before you leap) (Non-Pythonic)


my_list = [1, 2, 3, 4, 5]

my_list_short = [1, 2, 3, 4]



# LBYL (Look before you leap) (Non-Pythonic)

def print_list(list):

    if len(list) >= 5:

        print(list[4])

    else:

        print('That index does not exist')



print_list(my_list) # 5

print_list(my_list_short) # That index does not exist

EAFP (Easier to ask forgiveness than permission) (Pythonic)


my_list = [1, 2, 3, 4, 5]

my_list_short = [1, 2, 3, 4]



# EAFP (Easier to ask forgiveness than permission) (Pythonic)

def print_list(list):

    try:

        print(list[4])

    except IndexError as e:

        print(f'{e}')



print_list(my_list) # 5

print_list(my_list_short) # list index out of range

LBYL (Look before you leap) (Non-Pythonic)


import os



my_file = 'data.txt'



# LBYL (Look before you leap) (Non-Pythonic)

# Race-Condition

if os.access(my_file, os.R_OK):

    with open(my_file) as f:

        print(f.read())

else:

    print('File cannot be accessed')

EAFP (Easier to ask forgiveness than permission) (Pythonic)


import os



my_file = 'data.txt'



# EAFP (Easier to ask forgiveness than permission) (Pythonic)

# No Race-Condition

try:

    f = open(my_file)

except IOError as e:

    print(e) # [Errno 2] No such file or directory: 'data.txt'

else:

    with f:

        print(f.read())

Leave a Comment