views:

89

answers:

5
>>> class S(object):
...     def __init__(self):
...             self.x = 1
...     def x(self):
...             return self.x
...
>>> s = S()
>>> s.x
1
>>> s.x()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable

Why, in this example, is s.x a method, but also an integer? It seems to me that self.x = 1 should replace the def x(self): declaration of the attribute x during instantiation. Why is it that I can get and call, resulting in an integer and a method, respectively, the same attribute? My guess is that the variable look-up pattern in new-style classes is duck typed, so as to return the most relevant result to the caller. I would love to hear the whole story.

+2  A: 

It looks like you're having a misunderstanding of the error you're seeing. When your s object is instantiated, its constructor replaces the method x by an integer, so in the s object, x is an integer, not a function. Trying to call it as a method results in an exception being thrown.

Python is duck-typed in the sense that method calls are resolved at runtime - the compiler has no problem with s.x() because x might have been created as a method dynamically. However, when the interpreter actually calls x as a method, it notices x is an integer and can't be called, hence the TypeError.

Eli Bendersky
@Eli - Ah, thanks. I don't know why I try to get philosophical late at night. It never works out quite right. I was in the @decorator frame of mind and only saw the `return self.x` as what was being finally called. And, I don't even know what made me think to try this, not to make matters worse :)
orokusaki
+1  A: 

I'm not sure what you think is going on, but there's nothing that tricky happening. When you assign self.x = 1, the method x is no longer accessible. From that point forward, s.x is only an integer -- attempts to call it as a method result in an exception, as you saw.

Adam Rosenfield
A: 

It seems that the x property is defined as a method in the class definition. However, actually instantiating an object overwrites that name with an integer - hence, the behavior observed. It's never actually two at once. So, this is basically some faulty code.

Mark Snidovich
+3  A: 

Python doesn't use separate spaces for callable and non-callable objects: a name is a name is a name. s.x, by Python rules, must refer to exactly the same object, whether you're going to call it or not. Another way of putting it: assuming that _aux is a name not otherwise used,

_aux = self.x
_aux()

and

self.x()

must have absolutely identical semantics in Python, the fact that in the former the intermediate value self.x is being bound to a name and called later notwithstanding.

Having single, "unified" namespaces for callables and non-callables has a huge number of advantages -- it makes name-lookup rules (for each of bare and qualified names) enormously simpler, for example, by decoupling them totally from the purpose to which the name being looked up is going to be put (be it immediately after the lookup's result, or later still), and also from the type (callable or non-callable) of whatever object turns up to be first referenced according to the lookup rules.

Especially considering how many different callable types Python has (functions, classes, instances of classes which define __call__, special types such as staticmethod and classmethod, ...!-), any other rule could only lead to total chaos. (Note also, for example, that even C++, a language which definitely is not afraid by complexity but which also lets class-instances be callable [[if the class overloads operator()]], uses a similar unified-namespace rule -- again, discriminating between callables and non-callables would be a totally unwarranted nightmare, were the rules any different in this regard!-).

Alex Martelli
@Alex - as always, great answer. I think @Eli answered my question best though, as it wasn't based on practical theory, but rather a lack of paying attention on my part.
orokusaki
@Alex: totally unrelated but I've got to ask anyways. What's the `!-)`? I've heard the `!` can be used as a wink but I'm used to seeing `;-)` as winking. `-)` means tongue-in-cheek, and maybe you combine it with the exclamation point for emphasis. I've seen you use it many times, and I've always wondered what it was. Sorry for putting it here, but I'm curious :P
vlad003
@vlad, it's a variant-wink-smiley, just like, e.g. `?-)` -- I guess `;-)` is also fine, but maybe I have more of an "all-face" wink-smile so I prefer the more "extensive" punctuation marks in my emoticons.
Alex Martelli
@Alex: why is `staticmethod` a "special type"? My Python 2.7 gives a simple function object, for static methods…
EOL
@EOL, make one (`>>> x=staticmethod(lambda: None)`) and you'll see that `type(x)` is `<type 'staticmethod'>` -- a very special type and specifically a descriptor type with a custom `__get__` (so that fetching it **from a class** produces the "simple function" you're observing: that takes a special-indeed type for `staticmethod` itself!-).
Alex Martelli
@Alex: I see, thank you! When I "checked" the type of staticmethod, I did fetch a static method from a class…
EOL
@EOL, remember that `getattr(someclass, 'someattr')` (or the equivalent syntax `someclass.someattr`) is calling the `__get__` method of the object `someattr` actually _refers_ to (if any) -- that's the powerful concept of "descriptor types", of which `staticmethod` is one (OTOH I was elliptical in describing `staticmethod` instances as being _callable_... they aren't, _directly_ !-).
Alex Martelli
@Alex - I think ?-) is a guy with a fishing hook stuck in his eye :)
orokusaki
A: 

This is what your code is doing:

  1. Create a class named S with 2 methods, __init__ and x
  2. Create an instance of S and name it s
    1. Call S.__init__ with s as parameter
      1. Set s.x with the value 1
  3. Print s.x
  4. Print the result of calling s.x

Now, if you look in 2.1.1 you will see that you have overrided the method x with an integer, which means that you cannot call that again withing s (but it stills in S class)

If you have done that, and yet, need call x function, try it:

>>> class S(object):
...     def __init__(self):
...         self.x = 1
...     def x(self):
...         return self.x
... 
>>> s = S()
>>> s.x
1
>>> S.x(s)
1
>>> 

I just did it so you understand why you are losing the x as method, do it in the right way and avoid to have instances variables with the same name as class methods

Rafael SDM Sierra
@Rafael - thanks. I understand. I just misread the exception when it printed out (tired and retarded from camping), and jumped to a conclusion.
orokusaki