Effective Python 16 - 20

Click here for the first post, which contains the context of this series.

Item #16: Prefer get over in and KeyError to handle missing dictionary keys.

Prefer

my_result = my_dict.get(my_key, my_default_value)

over both

if my_key in my_dict:
    my_result = my_dict[my_key]
else:
    my_result = my_default_value

and

try:
    my_result = my_dict[my_key]
except KeyError:
    my_result = my_default_value

Item #17: Prefer defaultdict over setdefault to handle missing items in internal state.

Prefer

my_dict = defaultdict(set)
my_dict[my_key].add(my_value)

over

my_dict = {}
my_dict.setdefault(my_key, set()).add(my_value)

Note that setdefault always executes its second argument and is prone to difficult exception handling.

Item #18: Know how to construct key-dependent default values with __missing__.

If you need to handle a missing item by calling a function that:
  • takes at least one argument and
  • should be called only when necessary,
then defaultdict and setdefault, respectively, will not work. Instead, do something like this:

class MyDict:
    def __missing__(self, key):
        try:
            value = open(key, 'a+b')
            self[key] = value
            return value
        except OSError:
            print(f'failed to open {key}')
            raise

my_dict = MyDict()
file = my_dict[key]

Item #19: Never unpack more than three variables when functions return multiple values.

Avoid doing this since an accidental reorder is not difficult:

minimum, maximum, average, median, count = get_statistics(data)

Item #20: Prefer raising exceptions to returning None.

This looks like it works:

def careful_divide(a, b):
    return a / b if b != 0 else None

c = careful_divide(a, b)
if not c:
    print('division by 0')

But if a, b = 0, 1, then division by 0 is printed, which is undesired behavior. Instead, do something like this:

def careful_divide(a, b):
    try:
        return careful_divide(a, b)
    except ZeroDivisionError:
        raise ValueError('division by 0')