On 27 March 2018 at 15:32, Joao S. O. Bueno <jsbu...@python.org.br> wrote:
> Yes - but that would be the intention of the code beign written as in
> your example -
>
>     class C:
>         x = 1
>         f = staticmethod(lambda: print(x))
>
> While, the classic behavior can be attained by doing:
>
>     class C:
>          x = 1
>          f = classmethod(lambda cls: print(cls.x))
>
> And the behavior in both cases if one of no-surprises for me. For
> coders who don't have the mechanism of class creation in their
> mind, that could come as a surprise, but it is better than being
> inconsistent.

I wouldn't describe myself as "having the mechanism of class creation
in my mind", but I'm not 100% sure that's a necessary criterion here.
In an ideal world, Python's semantics is supposed to be intuitive,
which means that understanding subtle details shouldn't be necessary
to anticipate the behaviour of certain constructs.

Looking at the following definitions:

    class C:
        x = 1

        y1 = x
        y2 = (lambda: x)()

        def meth1(self):
            return self.x
        meth2 = lambda self: self.x

        @staticmethod
        def sm1():
            return x
        sm2 = staticmethod(lambda: x)

        @classmethod
        def cm1(cls):
            return cls.x
        cm2 = classmethod(lambda cls: cls.x)

I would expect meth1 and meth2 to be equivalent. I'd expect cm1 and
cm2 to be equivalent. I would *not* expect sm1 to work, and I'd expect
sm2 to fail for the same reason - namely that sm1 has no access to the
class or an instance of it, so it should not be able to reference x.
And so we should get NameError.

These to me don't imply any sort of "understanding of how classes are
created" - they simply need an understanding of how methods (normal,
static and class) get access to the class/instance. They also require
that you *don't* expect a class statement to create a scope that can
be closed over. I didn't really think about that before I started
analysing this code, but once I did, I realised that I've never
expected that.

So my reaction to a bare nonlocal variable reference in a method
(whether defined in a def statement or as a lambda) would be "wait,
what would that mean? I guess it's the global". I wouldn't even be
looking at the x defined in the class at that point.

The definition of y2 follows that rule as well - even though the fact
that it's not defining a method, but it's "just" a bare lambda,
slightly muddies the water.

The odd one out is y1. I actually can't quite explain the logic that
allows y1 to refer to the value of x, even though it's the most
"natural" case. As I said, I don't think of a class as defining a new
scope, rather I think of it as bundling together a set of statements
that will be run to populate the class (maybe that's "having the
mechanism of class creation in my mind"?). So I guess y1 = x is just
normal statement sequencing.

So I guess I'm saying that I don't really see a problem with the
current behaviour here. Classes *don't* create a scope. So you don't
close over class variables.

For further emphasis of this point of view:

    @staticmethod
    def sm1():
        nonlocal x
        return x

gives

    nonlocal x
    ^
SyntaxError: no binding for nonlocal 'x' found

which again I can understand as "there's no outer scope containing x".

Having said all this, I guess you could say that I'm too close to the
current behaviour to see its deficiencies. Maybe that's true. But I
don't actually *use* any of this on a regular basis. I worked out all
of the above based on intuition from how I understood Python's class
model, plus a few small experiments that mostly just confirmed what I
expected.

If adding the ability to refer to a bare x in sm1/sm2 or y2 [1] means
complicating the behaviour to the point where my mental model needs to
involve injecting arguments into nested scopes, I'm a strong -1.If
there's a need to "fix" comprehensions, then I'd much rather that we
do so in a way that doesn't change the current behaviour or execution
model. Specifically, I'd actually much *rather* that these 3 cases
continue giving NameError.

The comprehension cases:

  y3 = [x+i for i in (0,1,2)] #1
  y4 = [1+i for i in (x, x+1)] #2

(#1 fails with NameError, and #2 works) strike me as odd corner cases
that are a bit odd, but are not worth disrupting the normal cases for.
And they should be viewed as oddities in how comprehensions look names
up, and not as peculiarities of class scope.

Sorry - I intended that to be an "it's all pretty simple and obvious"
comment, but it turned into a rather long post. But I do think the
basic idea remains simple.

Paul

[1] The other cases don't need to refer to a bare x, they already have
perfectly good ways of accessing the class variable x.
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to