Please ignore secret bonuses. Secret tests do NOT award bonus. Max hw grade is 30+2 bonus efficiency

Do you need help?

Redundant Functions' Variables Instantiations

S
Silktrader (2550 points)
2 6 16
in Programming in Python by (2.6k points)
edited by

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?

330 views
closed

1 Answer

Best answer
andrea.sterbini (207920 points)
750 1267 2373
by (208k points)
selected by
Other two ways: global variables and default parametrs
S
Silktrader (2550 points)
2 6 16
by (2.6k 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)
2 6 16
by (2.6k points)
edited by

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 (207920 points)
750 1267 2373
by (208k points)
It seems the compiler knows that the set is immutable (it's constant) and then optimizes it nicely ... beautiful!