Applying a string-identified effect on an image
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