Redundant Functions' Variables Instantiations

S
Silktrader (2550 points)
1 6 16
asked Dec 17, 2020 in Programming in Python by Silktrader (2,550 points)
edited Dec 17, 2020 by Silktrader

Suppose one writes a function relying on local objects whose instantiation can be somewhat taxing. I'd like to check whether a word is of the "woody" kind, here:

def is_woody(word):
    return word in {"gone", "vacuum", "prodding", "sausage", "bound", "vole", "recidivist", "caribou"}

The set will be instantiated during each function call, which is a waste. It doesn't matter whether the object is mutable or immutable; new instances will sprout after each function invocation.

In "C" one could declare a locally scoped function static variable, in "C#" one might declare a compile-time const ...

In Python, one might:

  • declare the set in a parent scope and pass it down to the function as an argument — pollutes the namespace and function parameters, hinders the separation of concerns, etc.
  • embed the set in a function attribute — requires either verbose syntax and impaired performance or the function attribute to be set in an outer scope
  • create a singleton that initialises the set as a class attribute and contains a method accessing it — imposes object oriented approach, a multitude of singletons to keep concerns separate and slow class member access

Are there better patterns to address this rather trivial issue?

173 views

1 Answer

Best answer
andrea.sterbini (172780 points)
514 935 1789
answered Dec 17, 2020 by andrea.sterbini (172,780 points)
selected Dec 17, 2020 by Silktrader
Other two ways: global variables and default parametrs
S
Silktrader (2550 points)
1 6 16
commented Dec 17, 2020 by Silktrader (2,550 points)

Under normal circumstances (therefore excluding homeworks) I would have used global variables, although it's less than ideal — one exposes variables not meant to be tinkered with to all the module's functions.

The second suggestion is as subtle as it's clever. Great insight @andrea.sterbini, thanks indeed! 

So far I've actively avoided default mutable parameters and relied on the "None" pattern:

def avoid_danger(dangers=None):
    dangers = dangers or []
    dangers.append('mutable default argument')
    return dangers

Default arguments are evaluated once in Python, unlike other interpreted languages. So the empty list below would be constantly populated with new entries and passed around, which is often undesirable:

def seek_trouble(dangers=[]):
    dangers.append('mutable default argument')
    return dangers

I would prefer immutable data structures with default parameters and go for:

def is_woody(word, woody_words = frozenset(("gone", "vacuum", "prodding", "sausage", "bound", "recidivist", "caribou"))):
    return word in woody_words

... and when objects initialisation gets excessively long one could:

def fetch_woody_words():
    return frozenset(("gone", "vacuum", "prodding", "sausage", "bound", "recidivist", "caribou"))

def are_woody(word, woody_words=fetch_woody_words()):
    return word in woody_words

S
Silktrader (2550 points)
1 6 16
commented Dec 17, 2020 by Silktrader (2,550 points)
edited Dec 17, 2020 by Silktrader

To my surprise, I noticed that the following code ...

def is_woody(word):
    return word in {"gone", "vacuum", "prodding", "sausage", "bound", "vole", "recidivist", "caribou"}

... is actually bytecode-compiled into:

 36           0 LOAD_FAST                0 (word)
              2 LOAD_CONST               1 (frozenset({'sausage', 'caribou', 'vacuum', 'gone', 'bound', 'vole', 'recidivist', 'prodding'}))
              4 COMPARE_OP               6 (in)
              6 RETURN_VALUE

On further investigation it would seem that CPython actually interns immutable unassigned literals (sets are turned into their immutable counterpart). So, in this specific case, one would be free to initialise the set inside the function at no cost.

andrea.sterbini (172780 points)
514 935 1789
commented Dec 19, 2020 by andrea.sterbini (172,780 points)
It seems the compiler knows that the set is immutable (it's constant) and then optimizes it nicely ... beautiful!