comprehension used in static variable cannot see local function?

Viewed 182

(See final question at EDIT below. The original title was Why static var in static var is ok but static method not ok in Python?)

Original post: I have a Python question much related to Refering to static methods from static variables but not exactly the same. So code where one refers to a static method in a static variable, e.g.,

class A:
   @staticmethod
   def f(a):
      return a*a

   v = f(2)    # A.f(2) does not work either

is invalid (and as far as I can tell same holds without @staticmethod as well). Fine. Yet, one can refer to another static variable without any problem:

class A:
   i = 2
   v = i * i    # not using A.i here

print(A.v)    # gives 4

What is the rationale behind the different treatment of a static variable and a static method here? I am trying from Python 3.6, if it matters.

EDIT:

The suggestion by gilch to think of it as a namespace indeed helps. Now I realize that my test case was too simplistic, however. What failed was initializing a list/dictionary for some reason:

class A:
   def f(a):  return a*a

   i = f(2)                           #works, indeed
   lst = [2, 3, 4]
   lst2 = [ v*v for v in lst]         #works
   lst3 = [ f(v) for v in lst]        #fails??
   dct =  { f(v) : v for v in lst }   #fails??

Of course, the last two lines both work if f is defined outside A. So this may be a question of scope somehow...

1 Answers

Because staticmethods are not callable. They're descriptors that return a callable when accessed through a dot (.). The class doesn't exist until after the class body executes. In the meantime, it's a namespace, similar to a module, but with slightly different scoping rules. After the body executes, the namespace is dumped into the class object's __dict__ and is discarded. So this works.

class A:
    def f(a): return a*a
    v = f(2)
    f = staticmethod(f)

>>> A.v
4
>>> A.f(2)
4

This also works

class A:
    @staticmethod
    def f(a): return a*a
    v = f.__get__(...)(2)

The ellipsis doesn't mean anything. @staticmethod's __get__() doesn't use its argument. (It can't be None, or it expects another argument. But we have neither a class nor an instance to give it yet.)


What failed was initializing a list/dictionary for some reason:

This is due to the "slightly different scoping rules" that I mentioned earlier.

Comprehensions are compiled like generators -- functions that contain a yield. So this would fail for similar reasons:

class A:
    def f(a): return a*a
    xs=[2,3,4]
    def comp(it):
        for i in it:
            yield f(i)
    ys=list(comp(xs))

Remember that I said the body namespace is discarded. Usually, methods are called after the class body has executed. So methods are compiled to look up names that aren't defined locally in the global namespace, instead of the temporary class body namespace that probably doesn't exist anymore. It is possible to save this temporary namespace somewhere if you need it, for example,

class A:
    def f(a): return a*a
    lst=[2,3,4]
    global ns
    ns = locals()
    lst2=[ns['f'](v) for v in lst]

>>> A.lst2
[4, 9, 16]

You could also do the comprehensions the old-fashioned way to avoid compiling generators:

class A:
    def f(a): return a*a
    lst=[2,3,4]
    lst2=[]
    for v in lst: lst2.append(f(v))
    dct={}
    for v in lst: dct[f(v)] = v

Or you could wait until after you have a class object to work with (the temporary namespace has been dumped into the object's __dict__ at this point, so they're available as attrs):

class A:
    @staticmethod
    def f(a):  return a*a
    lst = [2, 3, 4]

A.lst2 = [A.f(v) for v in A.lst]
Related