22

In C++ we have static keyword which in loops is something like this:

for(int x=0; x<10; x++)
    {
      for(int y=0; y<10; y++)
      {
        static int number_of_times = 0;
        number_of_times++;
      }
    }

static here makes number_of_times initialized once. How can I do same thing in python 3.x?

EDIT: Since most of the people got confused I would like to point out that the code I gave is just example of static usage in C++. My real problem is that I want to initialize only ONE time variable in function since I dont want it to be global(blah!) or default parameter..

kuskmen
  • 3,648
  • 4
  • 27
  • 54
  • Declare that variable outside of the for loops. – Maxime Chéramy Oct 29 '14 at 12:51
  • 5
    That's not the only thing that declaring a local variable `static` does. Please tell us what you want to do with the variable in Python, and what problem you are actually trying to solve ([read about the XY problem](http://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)). – Some programmer dude Oct 29 '14 at 12:54
  • Note that Python has far fewer scopes than C++, so there is less need for such constructs. `number_of_times` in a Python version of this code would not be restricted to the inner for loop. – chepner Oct 29 '14 at 12:54
  • The thing is that I gave these nested loops just for example for static. I want to use this functionality in recursive function so I don't have default parameter. – kuskmen Oct 29 '14 at 12:57
  • this is a really horrible feature – njzk2 Oct 29 '14 at 13:03
  • 1
    @kuskmen Then show *that* in your question, instead of the irrelevant loop example. – chepner Oct 29 '14 at 13:15
  • 1
    @kuskmen: what's wrong with default parameters ? – bruno desthuilliers Oct 29 '14 at 13:18
  • 1
    Have you thought about classes and objects? – Some programmer dude Oct 29 '14 at 13:47
  • @brunodesthuilliers Lets say u have function with default parameter but if someone actually tries to give one more argument to your function it could be catastrophic. Yea I know that this is their problem and bla bla .. I just wanted to know if there is feature like in C++.If I wanted to ask which style is better I would ask different question :) – kuskmen Oct 29 '14 at 15:48
  • @kuskmen: you could write the recursive function as a private function and add a public function that doesn't take the default parameter and delegates to the private one (nb: for Python's definitions of 'public' and 'private'). – bruno desthuilliers Oct 29 '14 at 15:59
  • @brunodesthuilliers Can you show me an example of this? Cause I dont seem to get it really well? – kuskmen Oct 30 '14 at 11:30
  • 1
    @kuskmen: dummy exemple here http://pastie.org/9685335. If it doesn't make sense for you, _please_ post your a question with your _real_ use case. – bruno desthuilliers Oct 30 '14 at 11:46
  • @brunodesthuilliers Nope , that clarified everything I wanted to know , thank you very much, sadly I cant give up votes for now.You gave me exactly what I needed, thanks :) – kuskmen Oct 30 '14 at 18:09
  • 1
    @kuskmen: edited my answer with the code snippet. – bruno desthuilliers Oct 30 '14 at 18:23
  • Also and just for the record: you don't even really need the wrapper, just prefixing the argument name with a single underscore and mentionning in the doc the caller should not provide value for it would be enough. Python has this "we're all consenting adults" philosophy (IOW: just using naming conventions instead of language-inforced rules), and Python users tend to be surprisingly disciplined. – bruno desthuilliers Oct 30 '14 at 18:31

6 Answers6

39

Assuming what you want is "a variable that is initialised only once on first function call", there's no such thing in Python syntax. But there are ways to get a similar result:

1 - Use a global. Note that in Python, 'global' really means 'global to the module', not 'global to the process':

_number_of_times = 0

def yourfunc(x, y):
    global _number_of_times
    for i in range(x):
        for j in range(y):
            _number_of_times += 1

2 - Wrap you code in a class and use a class attribute (ie: an attribute that is shared by all instances). :

class Foo(object):
    _number_of_times = 0

    @classmethod
    def yourfunc(cls, x, y):
        for i in range(x):
            for j in range(y):
                cls._number_of_times += 1

Note that I used a classmethod since this code snippet doesn't need anything from an instance

3 - Wrap you code in a class, use an instance attribute and provide a shortcut for the method:

class Foo(object):
    def __init__(self):
         self._number_of_times = 0

    def yourfunc(self, x, y):
        for i in range(x):
            for j in range(y):
                self._number_of_times += 1

yourfunc = Foo().yourfunc

4 - Write a callable class and provide a shortcut:

class Foo(object):
    def __init__(self):
         self._number_of_times = 0

    def __call__(self, x, y):
        for i in range(x):
            for j in range(y):
                self._number_of_times += 1


yourfunc = Foo()

4 bis - use a class attribute and a metaclass

class Callable(type):
    def __call__(self, *args, **kw):
        return self._call(*args, **kw)

class yourfunc(object):
    __metaclass__ = Callable

    _numer_of_times = 0

    @classmethod
    def _call(cls, x, y):
        for i in range(x):
            for j in range(y):                 
                cls._number_of_time += 1

5 - Make a "creative" use of function's default arguments being instantiated only once on module import:

def yourfunc(x, y, _hack=[0]):
    for i in range(x):
        for j in range(y):
            _hack[0] += 1

There are still some other possible solutions / hacks, but I think you get the big picture now.

EDIT: given the op's clarifications, ie "Lets say you have a recursive function with default parameter but if someone actually tries to give one more argument to your function it could be catastrophic", it looks like what the OP really wants is something like:

# private recursive function using a default param the caller shouldn't set
def _walk(tree, callback, level=0):
    callback(tree, level)
    for child in tree.children:
        _walk(child, callback, level+1):

# public wrapper without the default param
def walk(tree, callback):
    _walk(tree, callback)

Which, BTW, prove we really had Yet Another XY Problem...

Ross Smith II
  • 11,799
  • 1
  • 38
  • 43
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
8

You can create a closure with nonlocal to make them editable (python 3.x only). Here's an example of a recursive function to calculate the length of a list.

def recursive_len(l):
    res = 0
    def inner(l2):
        nonlocal res
        if l2:
            res += 1
            inner(l2[1:])
    inner(l)
    return res

Or, you can assign an attribute to the function itself. Using the trick from here:

def fn(self):
    self.number_of_times += 1
fn.func_defaults = (fn,)
fn.number_of_times = 0

fn()
fn()
fn()
print (fn.number_of_times)
Brian Burns
  • 20,575
  • 8
  • 83
  • 77
parchment
  • 4,063
  • 1
  • 19
  • 30
  • 1
    The 'attribute on the function itself' is *very* brittle, as it depends on the global binding between the name ('fn') and the function object to remain stable. – bruno desthuilliers Oct 29 '14 at 13:18
  • Why not just assign the function attribute directly after the function definition? `fn.number_of_times = 0`. – Steven Rumbalski Oct 29 '14 at 13:36
  • @StevenRumbalski I've edited it in, thanks. I think someone suggested it first, but they deleted it already. – parchment Oct 29 '14 at 13:38
  • @parchment: Why do you still have a `self` argument? If you need `self`, you may as well use a class with class attributes with a `__call__` method. If you get rid of `self` your function is breakable as per bruno desthuilliers: `from somemodule import fn as somethingelse`. – Steven Rumbalski Oct 29 '14 at 13:44
  • @StevenRumbalski It's from the SO answer [here](https://stackoverflow.com/a/3209862/3816975). The `fn.func_defaults` line changes the argument `self` to have a default value of the function itself. – parchment Oct 29 '14 at 13:52
5

Python doesn't have static variables by design. For your example, and use within loop blocks etc. in general, you just use a variable in an outer scope; if that makes it too long-lived, it might be time to consider breaking up that function into smaller ones.

For a variable that continues to exist between calls to a function, that's just reimplementing the basic idea of an object and a method on that object, so you should make one of those instead.

Community
  • 1
  • 1
Mark Reed
  • 91,912
  • 16
  • 138
  • 175
2

The another function-based way of doing this in python is:

def f(arg, static_var=[0]):
    static_var[0] += arg

As the static_var object is initialised at the function definition, and then reused for all the calls, it will act like a static variable. Note that you can't just use an int, as they are immutable.

>>> def f(arg, static_var=[0]):
...         static_var[0] += arg
...         print(static_var[0])
... 
>>> f(1)
1
>>> f(2)
3
>>> f(3)
6
matsjoyce
  • 5,744
  • 6
  • 31
  • 38
  • I am actually trying to do exactly the opposite of that :) I want to remove that default parameter initialize static_var = [](for example) ONCE and then change it however I like in C++ this can be done with static keyword but apparently this feature is not implemented in python – kuskmen Oct 29 '14 at 15:58
0

You can also use the global keyword:

def main(args):
    for i in xrange(10):
        print i
        global tmp 
        tmp = i

But be careful... I most cases it will add more issues than it solves.

frlan
  • 6,950
  • 3
  • 31
  • 72
0

Use defaultdict:

from collections import defaultdict

static = defaultdict(lambda: 0)

def myfunc():
    for x in range(10):
        for y in range(10):
            static['number_of_times'] += 1
alex
  • 67
  • 4