Applying a string-identified effect on an image Task:

Given an image object and an effect identifier (string), apply the effect identified by the string on the image. The library of effects can grow as the program grows.

Code 1:

def apply_fx(img, fx):
    if fx == 'sepia':
        return sepia(img)
    elif fx == 'b&amp;w':
        return contrast(grayscale(img), 2)
    elif fx == 'faded':
        return faded(img)
    elif fx == 'hdr':
        return hdr(img)


Code 2:

effects = {
    'sepia': sepia,
    'b&amp;w': lambda x: contrast(grayscale(x), 2),
    'faded': faded,
    'hdr': hdr
}

def apply_fx(img, fx):
    return effects[fx](img)


Opinion: I personally lean toward the second approach.

Note: the concern here generalizes to: dictionary of lambda functions vs if-elif in such scenarios.

Extra code: as suggested by @PeilonRayz.

sepia_filter = np.array([[.393, .769, .189],
                         [.349, .686, .168],
                         [.272, .534, .131]])


def sepia(img):
    sepia_img = np.dot(img, sepia_filter.T)
    return sepia_img / np.max(sepia_img)
IMO your second solution (using a dict to store callables) is exactly right.

The first solution, with a series of if/else statements is not only inefficient, it also encourages and facilitates writing unrelated code inside the apply_fx function itself. To wit:

elif fx == 'b&amp;w':
    return contrast(grayscale(img), 2)


Once you have put something other than a simple return func(img) in the code, a future maintainer will be tempted to "just write this inline here" rather than creating a lambda or a named function to encapsulate their code:

elif fx = 'grayscale':
    from thirdpartymodule import grayscale, formatconverter

    gsh = grayscale.Handle()
    img3p = formatconverter(img)
    result = grayscale.grayscale(gsh, img3p)
    return formatconverter(img3p, format=img)


Your second alternative, on the other hand, has a much more restricted, and thereby much cleaner interface: all operations must be encapsulated as callables.

Now, they could be functions, or they could be lambdas. They could be functools.partials or dynamically loaded or web api calls to a satellite orbiting planet Nebulon. But from the maintenance point of view, "create a callable, plug it in here with a name" is as good as you're likely to get. You could even do it dynamically, in response to a configuration file or plugin module:

def img2grayscale(img):
    from thirdpartymodule import grayscale, formatconverter

    gsh = grayscale.Handle()
    img3p = formatconverter(img)
    result = grayscale.grayscale(gsh, img3p)
    return formatconverter(img3p, format=img)

effects['grayscale'] = img2grayscale