[Python-ideas] Fixing class scope brainstorm

Joao S. O. Bueno jsbueno at python.org.br
Tue Mar 27 11:51:30 EDT 2018


Well, there is an idiom to "keep everything as is", and work around
the current limitations:

class A:
   def b():
       x = 1
       d = [i + x for i in range(2)]
       return locals()
   locals().update(b())
   del b

Maybe if we could find a syntactic sugar for this idiom
(with an abuse of the `with` keyword, for example), people could be happy,
with class bodies working by default as they are now, and enabling the
reuse of defined members by using the special syntax -

class A:
   with class:
      x = 1
      d = [i + x for in range(2)]





On 27 March 2018 at 12:27, Paul Moore <p.f.moore at gmail.com> wrote:
> On 27 March 2018 at 15:32, Joao S. O. Bueno <jsbueno at 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.


More information about the Python-ideas mailing list