Access to the caller's globals, not your own

Steven D'Aprano steve+comp.lang.python at pearwood.info
Thu Nov 17 00:17:51 EST 2016


On Thursday 17 November 2016 04:52, Rob Gaddi wrote:

>> import library
>> result = library.make_spam(arg)
>>
>>
>> versus:
>>
>> import library
>> make_spam = library.make_library()
>> result = make_spam(arg)
>>
>> What a drag.
>>
>>
> 
> And there you have it; an entire extra line at import time.

Multiplied by every function that has a configurable default. Multiplied by 
every module that imports any of those functions, *whether or not* they change 
the default. If there are four functions, the user has to do this:

import library
make_spam = library.make_spam_factory()
make_eggs = library.make_eggs_factory()
make_cheese = library.make_cheese_factory()
make_toast = library.make_toast_factory()


before they can even begin to do what they actually want to do, which is make 
spam, eggs, cheese etc. And yes, there's likely to be four or more of these 
functions.

You can slightly work around this by offering pre-built functions that avoid 
needing to call the factory, but this is adding more complexity to what really 
isn't that complicated: a function with a user-configurable default setting.


> It's the answer that's explicit.

An explicit solution would be to offer no default setting at all and require 
the user to always provide the setting as an explicit argument to the function.

But despite the Zen of Python (which has always been intended as a humorous and 
intentionally self-contradictory way to think about the design of the language, 
not as a way to shut down discussion), explicit is not ALWAYS better than 
implicit. That's why we have default values, and why we don't require imports 
to be written like this:

    # explicitly giving the name to bind to is better
    import math as math


It's very unusual to require the caller of a library function to create the 
function first: factory functions and builders are a powerful technique, but 
they're more useful for the library, less so for the user of the library.

Conceptually, my library offers a simple function. The API of functions is 
well-known:

import library
result = library.function(arg)

The API for providing user-configurable default values is well-known and easily 
understood: you set a global variable (or even an environment variable):

DEFAULT = foo
result = library.function()

There's about half a century of programming practice and probably ten billion 
person-hours of collective experience behind this idea.

Whereas fancy tricks involving factory functions, builders and class-based 
solutions are *much* less well-known or understood. Outside of functional 
programming circles, the idea of calling a function to return a function is 
often considered mind-blowing voodoo.


> It's versatile (you can create
> multiple library instances with different defaults if that sort of thing
> is really your jam), 

Indeed: factory functions are great. But I'm saying that as the writer of the 
library, not the user of the library. Can you imagine expecting users to do 
this?

from math import trig
sin = trig.build('sine')
result = sin(0.1)

It might only be one extra line of code, but conceptually it is an order of 
magnitude more complex. The user has to comprehend factory functions and first-
class functions as values before they can even begin to understand this.


> and it uses no tricky mechanisms that unskilled
> programmers won't understand.

The internal details of how the implementation works is not important to the 
user. They only need to understand that the "scheme" parameter takes its value 
from the first found of:

- an explicit argument
- a global variable called "WHATEVER"
- the library default

which is pretty simple to understand and use.



-- 
Steven
299792.458 km/s — not just a good idea, it’s the law!




More information about the Python-list mailing list