staticmethod makes my brain hurt

R

Roy Smith

When I run this (python 2.6.1):

class C:
@staticmethod
def foo():
pass

print "inside", foo, callable(foo)

print "outside", C.foo, callable(C.foo)

I get:

inside <staticmethod object at 0x421df0> False
outside <function foo at 0x41e6f0> True

I don't understand. Why is foo not callable inside of the class
definition? Where this comes up is that I'm trying to use a callable
default in mongoengine:

class User(Document):
@staticmethod
def _get_next_id():
[blah, blah, blah]
return id

user_id = IntField(required=True, default=_get_next_id)

The way mongoengine works is if callable(default) is true, it calls
default() to get the real value to use. At the point where the
IntField() call is made, _get_next_id is not callable, and eventually I
end up with:

ValidationError: <staticmethod object at 0x2a3c1a0> could not be
converted to int
 
A

alex23

class C:
    @staticmethod
    def foo():
        pass

    print "inside", foo, callable(foo)

print "outside", C.foo, callable(C.foo)

I don't understand.  Why is foo not callable inside of the class
definition?

Consider this:
False

A staticmethod by itself does not appear to be callable.

Your internal 'foo' is referring to the staticmethod wrapper. Your
external 'C.foo' refers to the result returned by the class
mechanism's __getattr__, which I'm guessing is munged into a callable
at that point.

Where this comes up is that I'm trying to use a callable
default in mongoengine:

class User(Document):
    @staticmethod
    def _get_next_id():
      [blah, blah, blah]
      return id

    user_id = IntField(required=True, default=_get_next_id)

What you're effectively trying to do is use a class before it has been
constructed to help construct itself.

Just define it as a helper function before the class declaration.
 
R

Roy Smith

alex23 said:
What you're effectively trying to do is use a class before it has been
constructed to help construct itself.

Just define it as a helper function before the class declaration.

Yes, this is the workaround I ended up with.
 
E

Ethan Furman

Roy said:
class User(Document):
@staticmethod
def _get_next_id():
[blah, blah, blah]
return id

user_id = IntField(required=True, default=_get_next_id)

If you don't call '_get_next_id()' from any class methods (in other
words, if you don't need to ever say 'self._gen_next_id()') then you can
remove the '@staticmethod':

def _get_next_id():
[blah, blah, blah]
return id

user_id = IntField(required=True, default=_get_next_id)

If you do need to sometimes call it from a method then still leave off
the '@staticmethod', and give 'self' a default of 'None':

def _get_next_id(self=None):
[blah, blah, blah]
return id

user_id = IntField(required=True, default=_get_next_id)

~Ethan~
 
S

Steven D'Aprano

When I run this (python 2.6.1):

class C:
@staticmethod
def foo():
pass
print "inside", foo, callable(foo)

print "outside", C.foo, callable(C.foo)

I get:

inside <staticmethod object at 0x421df0> False
outside <function foo at 0x41e6f0> True

I don't understand. Why is foo not callable inside of the class
definition?


This has come up before.

http://bytes.com/topic/python/answers/34396-static-method-object-not-callable

http://bytes.com/topic/python/answers/462734-make-staticmethod-objects-callable


However, the fix is not as simple as merely making staticmethod objects
callable. This was discussed at the 2011 language summit:

http://www.boredomandlaziness.org/2011/03/python-language-summit-rough-notes.html

See also this thread:

http://mail.python.org/pipermail/python-dev/2011-March/109090.html
 
R

Roy Smith

Steven D'Aprano said:

Thanks for the links. It always makes me feel good when I get tripped
up by something complex and subtle. It almost makes up for all the
times when I feel like a dolt because I got tripped up by something
obvious and elementary...
 
D

Devin Jeanpierre

However, the fix is not as simple as merely making staticmethod objects

The notes didn't actually mention what was discussed, but re the
second thread, the issue was not changing staticmethod to be callable.
Making staticmethod callable is fine, but __get__ still has to return
the original function, not self.

The context of the second thread was that staticmethod was imagined as
one way to eliminate the difference between C and Python functions,
when added to a class. But this doesn't quite work, unless
staticmethod.__get__ returned self (which broke for related reasons).
If it returns the original function (as it does now) then the issue is
unresolved: C functions still behave differently from Python
functions, so you can't quite just hide them behind a staticmethod and
let everything remain the same. It doesn't eliminate the difference in
behavior in certain circumstances, e.g.:

class MyClass:
foo = staticmethod(foo)

class MyOtherClass:
foo = MyClass.foo

MyOtherClass().foo() # what happens if foo is builtin vs pure-Python?

In the context of this thread, though, just making it callable without
modifying __get__ is fine. Or at least, I don't see the issue.

Devin
 
D

Dotan Cohen

When I run this (python 2.6.1):

class C:
   @staticmethod
   def foo():
       pass

   print "inside", foo, callable(foo)

print "outside", C.foo, callable(C.foo)

I get:

inside <staticmethod object at 0x421df0> False
outside <function foo at 0x41e6f0> True

I don't understand.  Why is foo not callable inside of the class
definition?  Where this comes up is that I'm trying to use a callable
default in mongoengine:

class User(Document):
   @staticmethod
   def _get_next_id():
     [blah, blah, blah]
     return id

   user_id = IntField(required=True, default=_get_next_id)

The way mongoengine works is if callable(default) is true, it calls
default() to get the real value to use.  At the point where the
IntField() call is made, _get_next_id is not callable, and eventually I
end up with:

ValidationError: <staticmethod object at 0x2a3c1a0> could not be
converted to int

Try this (untested):

class C:
@staticmethod
def foo():
pass

print "inside", C.foo, callable(C.foo)
 
I

Ian Kelly

Try this (untested):

class C:
  @staticmethod
  def foo():
      pass

  print "inside", C.foo, callable(C.foo)

If you had tested this, you would have found that you get a NameError,
since C is not yet bound inside the class block where you define it.
 
T

Thomas Rachel

Am 17.11.2011 03:30 schrieb Roy Smith:
When I run this (python 2.6.1):

class C:
@staticmethod
def foo():
pass

print "inside", foo, callable(foo)

print "outside", C.foo, callable(C.foo)

I get:

inside<staticmethod object at 0x421df0> False
outside<function foo at 0x41e6f0> True

Right. The reason is that on an attribute access, you get a __get__ call
of the "real" attribute.

That is, if your class has a "regular" method, it is stored there as a
formal function. But if you access it (C.f), you get
C.__dict__["f"].__get__(None, C) (not sure about the arguments, but you
should get the idea).

A functions __get__ returns a unbound or bound method, depending on its
own arguments.

With a static method, you don't want this to happen. So you wrap your
"regular" function into a staticmethod object, which has a __get__()
method itself just returning the wrapped function object.

Look at this:
<function <lambda> at 0x00B43E30>


That's how things work.

If you want to get back the "real" function from a staticmethod, you
either call its __get__ with an arbitrary argument, or you do it the
clean way and do a

def deref(s):
class C(object): s=s
return s.s

and so do a

class User(Document):
@staticmethod
def _get_next_id():
[blah, blah, blah]
return id

user_id = IntField(required=True, default=deref(_get_next_id))


HTH,


Thomas
 
D

Dotan Cohen

If you had tested this, you would have found that you get a NameError,
since C is not yet bound inside the class block where you define it.

I hadn't tested, I'm at work far from Idle. Just shooting from the hip.

For that matter, though, this does work in Java (I'm pretty sure but
again, untested right now).
 
A

alex23

If you do need to sometimes call it from a method then still leave off
the '@staticmethod', and give 'self' a default of 'None':

     def _get_next_id(self=None):
       [blah, blah, blah]
       return id

     user_id = IntField(required=True, default=_get_next_id)

And if the OP needs it to be a staticmethod as well, he can just wrap
the nested function:

gen_next_id = staticmethod(_gen_next_id)

I think I like this approach best. I'm annoyed that I forgot functions
declared in a class scope were callable within the definition :)
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top