0

In C/C++, a function can declare a local variable as static. When doing so, the value remains in memory and is available to subsequent calls to the function (this variable is no longer local, but that's besides the point).

Is there a way to do something similar in Python, without having to declare any global variable outside of the function?

Use case: a function that (among other things) uses a regex to extract a value from an input string. I want to pre-compile the pattern (re.compile()), without having to declare the variable outside of the function scope.

I can inject a variable directly into globals():

globals()['my_pattern'] = re.compile(...)

But it doesn't look like a good idea.

noamtm
  • 12,435
  • 15
  • 71
  • 107
  • 1
    Why not just use a **class**? – Abdul Aziz Barkat Jan 31 '21 at 14:36
  • i dont think so – Jakub Dóka Jan 31 '21 at 14:37
  • @AbdulAzizBarkat in the given context, everything is static (single functions) and a class won't really help. – noamtm Jan 31 '21 at 14:42
  • @JakubDóka me neither, but maybe I'm missing something. – noamtm Jan 31 '21 at 14:43
  • You can tackle this with a decorator the caching would happen there. You profile looks experienced enough so I can be lazy and not give you an example – Sedy Vlk Jan 31 '21 at 14:44
  • You can use a persistent default argument to preserve state between calls. See ["Least Astonishment" and the Mutable Default Argument](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) – Paul M. Jan 31 '21 at 14:46
  • @noamtm classes are the solution see [S.Lott's answer on - Simulating a 'local static' variable in python](https://stackoverflow.com/a/460811/14991864) just make the class callable. – Abdul Aziz Barkat Jan 31 '21 at 14:47

1 Answers1

2

You could use a function attribute. In Python, functions are first-class objects, so you can abuse of this feature to simulate a static variable:

import re

def find_some_pattern(b):
    if getattr(find_some_pattern, 'a', None) is None:
        find_some_pattern.a = re.compile(r'^[A-Z]+\_(\d{1,2})$')
    m = find_some_pattern.a.match(b)
    if m is not None:
        return m.groups()[0]
    return 'NO MATCH'

Now, you ca try it:

try:
    print(find_some_pattern.a)
except AttributeError:
    print('No attribute a yet!')

for s in ('AAAA_1', 'aaa_1', 'BBBB_3', 'BB_333'):
    print(find_some_pattern(s))

print(find_some_pattern.a)

This is the output:

No attribute a yet!
initialize a!
1
NO MATCH
3
NO MATCH
re.compile('^[A-Z]+\\_(\\d{1,2})$')

It is not the best approach (wrappers or callables are way more elegant, and I suggest you use one of them), but I thought this clearly explains the meaning of:

In Python, functions are first-class objects.

PieCot
  • 3,564
  • 1
  • 12
  • 20