1

I have a test class:

class TestClass:
    def someFunction(self, someInt):
        self.i = someInt
        return self.i

And was testing it with:

x = MyClass()
x.someFunction = 3
print(x.someFunction)
x.someFunction(4)

However, this leads to TypeError: 'int' object is not callable. I understand why I am getting this error--someFunction has been rebound to an integer (in this case, 3) and, well, an integer is not a function and therefore someFunction is no longer callable. But I am wondering what the reasoning is behind allowing a member function to be rebound just as if it is a data member? It seems prone to accidental errors. I am just starting to learn Python and feel like I'm missing a fundamental aspect of Python that this behavior would fit into.

uncreative
  • 341
  • 5
  • 11
  • 6
    Methods are just callable attributes. That's it. Python is a very dynamic language and names (including attribute names) aren't typed. – jonrsharpe Jun 22 '16 at 07:27
  • Normally functions have names which are sufficiently different from variable names that the risk is minimal. For instance, your function is called `someFunction`. It seems pretty unlikely that you would accidentally try to assign a value to it. – Tom Karzes Jun 22 '16 at 07:46

4 Answers4

1

This is allowed because of the way attribute lookup works in Python and this is by design. In Python, many things that are discouraged, forbidden or impossible in other languages, are allowed to leverage your use case (if used wisely). Of course, more power implies more responsibility.

After all, we're all consenting adults here.

Some background information on attribute resolution

Class instances start with an empty __dict__ attribute where all object attributes are stored. By accessing x.someFunction you are implicitly trying x.__dict__['someFunction']. If 'someFunction' does not exist in x.__dict__, the same is tried for the class of x, i.e. type(x).someFunction or better type(x).__dict__['someFunction'].

When your write x by doing x.someFunction = 3, what actually happens is x.__dict__['someFunction'] = 3, regardless of what the reading attribute access might return.

The only real (?) magic happens during method calls, where self is provided automatically, i.e. x.someFunction(4) is resolved to type(x).__dict__['someFunction'](x, 4) instead of type(x).__dict__['someFunction'](4) or x.__dict__['someFunction'](4). This is somewhat related to attribute access and may cause confusion.

So, you actually do not "rebind" the function, but hide the class attribute someFunction with the instance attribute someFunction of x. If you do

print(MyClass.someFunction)
print(MyClass().someFunction)

you will see that the method is still there. You can even restore the initial state with

del x.__dict__['someFunction']

Note: The things I described as resolution illustrate the concept. In reality the inner workings of python may be more subtle or complex, but they will have the same effect. For example, in Python 2, methods have been wrapped as so called unbound methods before being stored in the class dictionary. This has been dropped in Python 3, where the class dictionary contains the plain function.

code_onkel
  • 2,759
  • 1
  • 16
  • 31
0

Please execute your demo code in a python shell:

>>> class TestClass:
...     def someFunction(self, someInt):
...         self.i = someInt
...         return self.i
...
>>> x = TestClass()
>>> x.someFunction = 3
>>> print(x.someFunction)
3
>>> x.someFunction(4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable

Please see the following content:

>>> type(x.someFunction)
<type 'instancemethod'>
>>> x.someFunction = 3
>>> type(x.someFunction)
<type 'int'>

If you try x.someFunction = 3, the instancemethod caled x.someFunction translates into a int.

debug
  • 991
  • 10
  • 14
  • 1
    The OP knows *what's* happening, as is clear from reading the question - they want to know *why that's allowed*. – jonrsharpe Jun 22 '16 at 07:43
  • 1
    http://stackoverflow.com/questions/972/adding-a-method-to-an-existing-object-instance – debug Jun 22 '16 at 08:05
0

In python

  • functions are threated as first-class objects
  • and variables are not typed.

Or just "Everything is an object" in python. So there is just no fundamental difference of a variable containing an integer vs a variable containing a function. Both, first-class functions and untyped variables have it's own pros and cons but allow you to e.g. simply make a list that contains integers, string, other objects as well as functions.

Sebastian
  • 5,721
  • 3
  • 43
  • 69
0

The reason you can assign anything to function variables is because they are implemented as objects. This enables you to return functions and pass functions as arguments, unlike in C.

The usefulness of this feature far surpasses the 'accidental errors'.

For example, you can do something like this:

# some code

def sum(a, b):
    return a+b

def product(a, b):
    return a*b

def ret_func(str):
    if str=='add':
        func = sum
    elif str=='multiply':
        func = product
    return func

addition_result = ret_func('add')(x, y)
multiplication_result = ret_func('multiply')(x, y)

# some more code
SvbZ3r0
  • 638
  • 4
  • 19