3

I got a piece of code like this:

foo = None

def outer():
    global foo
    foo = 0

    def make_id():
        global foo
        foo += 1
        return foo


    id1 = make_id() # id = 1
    id2 = make_id() # id = 2
    id3 = make_id() # ...

I find it ugly to have foo defined in the outermost scop, I would prefer to have it only in outer function. As I understand correctly, in Python3 this is done by nonlocal. Is there a better method for what I want to have? I would prefer to declare and assign foo in outer and maybe to delcare it global in inner:

def outer():
    foo = 0

    def make_id():
        global foo
        foo += 1     # (A)
        return foo

    id1 = make_id() # id = 1
    id2 = make_id() # id = 2
    id3 = make_id() # ...

(A) does not work, foo seems to be searched in the outermost scope.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
wal-o-mat
  • 7,158
  • 7
  • 32
  • 41

3 Answers3

6

I use 1-element lists for this purpose:

def outer():
    foo = [0]
    def make_id():
        r = foo[0]
        foo[0] += 1
        return r
    return make_id

make_id = outer()
id1 = make_id()
id2 = make_id()
...

This is the same as using nonlocal, at the cost of a slightly more cumbersome syntax (foo[0] instead of foo).

luispedro
  • 6,934
  • 4
  • 35
  • 45
  • As I solved my original problem with the function attribute, I did not try this. But it looks promising, I'll keep that in mind. – wal-o-mat Mar 08 '12 at 15:46
4

No, have this as a parameter to your make_id function. Even better put your id in a class, and have make_id as an instance method, and have that instance as a global (if you must).

Marcin
  • 48,559
  • 18
  • 128
  • 201
  • 3
    I always use classes for this type of behaviour as well; I think its a much better pattern. – mikebabcock Mar 07 '12 at 14:39
  • I think this is a classic example of "classes as a poor man's closure." I prefer the closure. – luispedro Mar 07 '12 at 14:51
  • @luispedro: Not really. In this case it looks like the attempts to keep the data in the function are a poor man's object system. – Marcin Mar 07 '12 at 14:55
  • Yes, closures are poor man's objects, too. My point is that it's more of a style thing. I feel the closure is a much more direct expression of intent in this case. – luispedro Mar 07 '12 at 14:58
  • @luispedro My point is not that closures are always a poor man's object system, but rather that there are potential benefits to real objects here, which closures don't provide, such as persisting the value between invocations of the outer function. – Marcin Mar 08 '12 at 16:02
0

Nope, the best alternative for it is function attributes.

orlp
  • 112,504
  • 36
  • 218
  • 315
  • @Marcin Just like a seperate object or a `nonlocal` variable - you have multiple closures/function objects/hand-rolled objects, and either you keep each to exactly one thread or you'll need synchronization. –  Mar 07 '12 at 14:52
  • @delnan I mean specifically, what happens if another thread enters `outer` - presumably, this will reset `outer.foo`. – Marcin Mar 07 '12 at 14:54
  • 1
    @Marcin No, that's *not* what happens. The underlying bytecode is shared, but each time a `def` is executed, a fresh function object is created, each of which is independent from the others. –  Mar 07 '12 at 14:55
  • @delnan: Wait, are `module` instances thread-local? (I know nothing of python threading). – Marcin Mar 07 '12 at 15:06
  • @Marcin `module`s are global as far as I know. But since when are we talking about modules? I thought this was about *functions*. –  Mar 07 '12 at 15:08
  • @delnan: so, why would each thread execute the `def` construct separately? If the module is shared, I would expect each thread to simply use the `function` object stored in the shared module, i.e. to share the `function` objects. – Marcin Mar 07 '12 at 15:10
  • @Marcin Of course module-level functions exist only once per module (and thus across threads). But here, the function in question is nested in another function - pretty much a closure. But I just noticed the first comment got nightcracker wrong and uses `outer` where it should use `make_id` to store attributes. Yeah, when using `outer` for that you'd overwrite the value not only when threading but even when recursing - it's basically global. That's why the only correct way of doing what this answer suggests involved `make_id.foo`. –  Mar 07 '12 at 15:33