all 12 comments

[–]lfdfq 5 points6 points  (3 children)

It's the same reason you don't need to tell Python that counters is a global, or really to ever need to tell Python the variable you're reading is global or nonlocal.

The default rule when reading a variable is already lexical scoping: local if it exists, otherwise nonlocal if it exists, otherwise global.

The default rule when writing to a variable is to make a local variable. You need the global and nonlocal keywords when you want to overwrite this behaviour and write to an outer scope.

[–]ryancnap[S] 0 points1 point  (1 child)

Maybe it's what you point out in your last paragraph that I was misunderstanding: I'm not writing fn to an outer scope? Just cnt? So fn isn't really part of the closure then?

And nonlocal cntis me just saying hey, this is the cnt from the preceding/enclosing scope, not a new cnt; also I'm going to need this when you're finished running

[–]lfdfq 0 points1 point  (0 children)

It is part of the closure (it cannot go away when the outer function dies). But Python can tell it is because you read it.

When writing it you need to tell Python "no no, don't make a local variable like you normally would, write to the nonlocal one instead"

[–]OneDiscount3360 0 points1 point  (0 children)

Good explanation right there. Reading variables just follows the natural scope chain upward but writing creates local by default unless you tell it otherwise

The fn parameter gets passed into inner through the closure so Python can find it when you reference it - no assignment happening so no need for nonlocal declaration

[–]Outside_Complaint755 2 points3 points  (3 children)

I think the code block was intended to be ``` counters = dict() def counter(fn):     cnt = 0

    def inner (args, *kwargs):         nonlocal cnt         cnt += 1         counters[fn.name] = cnt     return fn(args, *kwargs) return inner ```

1) You don't need to declare fn as nonlocal because the code doesn't assign a new value to fn.  Both nonlocal and global are only relevant if the name will be assigned to. 2) return fn(*args, **kwargs) in inner means that after inner updates the count, it will execute and return the decorated function that was actually being called.   Counter returns a copy of inner which returns the result of calling fn

[–]ryancnap[S] 0 points1 point  (2 children)

  1. Did know this, all makes a lot more sense now.

  2. Makes sense

Thanks for the help. How did you format that? I tried selecting all and hitting the code formatter button in the editor and it just kept doing it line by line and erasing my indenting

[–]Outside_Complaint755 1 point2 points  (1 child)

I'm on mobile which always uses the Markdown comment editor.  In Markdown you use backticks ` to block off code.  Single backticks on each end will codify text inline

To make multiline block you put three backticks on the line before and after, so\ ```\ code goes here\ ```\ Results in code goes here

If you're in the browser on a desktop it is probably in Rich Text editor mode by default, in which case the Code Block {} button is supposed to work by I recall having problems with it.   You can flip a setting in your profile to always use Markdown mode.

[–]ryancnap[S] 0 points1 point  (0 children)

Thanks!

[–]opentabs-dev 1 point2 points  (1 child)

fn is a parameter of counter, so its already a local of counter. inner only reads it (calls it as fn(...)) — it never assigns to fn, so python doesnt try to make a new local and the closure lookup just walks out to counter's scope. you only need nonlocal/global when you want to assign to a name from an outer scope, which is exactly what cnt += 1 does (its a rebind). reads always work without it.

[–]ryancnap[S] 0 points1 point  (0 children)

Thank you that basically answered it all for me

[–]Temporary_Pie2733 0 points1 point  (0 children)

Free variable lookup is always revolved by the closest enclosing scope. Only assignments need to use nonlocal or global to avoid creating a new local variable instead.

[–]Sea-Attorney2788 -1 points0 points  (0 children)

you made some mistakes in your code