1

Method overloading is not possible in Python! Can you please explain why Properties.setter in Python is not a case of method overloading?

class newOne():
    def __init__(self):
            self.__x = 0
    @property
    def val(self):
            return self.__x
    @val.setter
    def val(self,value):
            self.__x = value

In the above code, I have two methods with the same name 'val' (but different set of args) and both behave differently.

wim
  • 338,267
  • 99
  • 616
  • 750
goDDu
  • 45
  • 6
  • 1
    Do you understand that properties are descriptors, and what that means? I'm not going to explain that and it's kind of required knowledge to understand the rest. But basically, properties are somewhat magic and this is a special case, there's no actual method overloading here. The class only has one `val` attribute. – Alex Hall Mar 23 '18 at 19:35
  • 2
    `class A:def f(): print(1); def f(): print(2); A().f() -> prints 2`. The second definition *actually* overrides the first one. However `val.setter` in reality is composing the function definition into the `property` object, thus the first definition is not actually "lost". Then descriptors come into play. – Bakuriu Mar 23 '18 at 19:48

2 Answers2

2

It's not method overloading because there aren't two methods with different signatures that can be called. If it was method overloading, you could do something like this:

obj = newOne()

print(obj.val())
obj.val(5)

But that doesn't work, because val is a property and not an overloaded method.


So what's going on there? Why are we defining two methods with the same name, and what happens to them, if not overloading?

The magic happens in the decorators. As a prerequisite, you have to know that

@deco
def func(...):
    ...

is equivalent to

def func(...):
    ...

func = deco(func)

So, the first thing that happens is that the @property decorator turns your getter function into a property with that function as its getter function:

class newOne:
    @property
    def val(self):
        return self.__x

print(newOne.val)
print(newOne.val.fget)
# output:
# <property object at 0x002B1F00>
# <function newOne.val at 0x0052EAE0>

After this, the @val.setter decorator creates a new property with a getter and setter function:

class newOne:
    @property
    def val(self):
        return self.__x

    @val.setter
    def val(self, value):
        self.__x = value

print(newOne.val)
print(newOne.val.fget)
print(newOne.val.fset)
# output:
# <property object at 0x0221B7B0>
# <function newOne.val at 0x0226EB70>
# <function newOne.val at 0x0226EAE0>

(The getter and setter functions have the same name because they were both defined as def val(...), but they're still different functions. That's why they have different ids.)

So in the end you have a val property with a getter and a setter function. You do not have an overloaded method.

For details about how properties work and how the getter and setter functions are called, see the descriptor documentation.

Aran-Fey
  • 39,665
  • 11
  • 104
  • 149
1

First, the @property decorator creates a descriptor named val and sets it aside to add to the class once it is defined. Then, the @val.setter decorated takes its function and simplyeffectively adds a reference to it to the val descriptor.

Your code is roughly equivalent to

d = {}

def __init__(self):
    self.__x = 0

d['__init__'] = __init__

def val(self):
    return self.__x

d['val'] = property(val)

def val(self, value):
    self.__x = value

# Not d['val'].__set__ = val, as previously stated
d['val'] = property(fget=d['val'], fset=val)

newOne = type('newOne', (object,), d)

# These are all "local" to, or part of the implementation of,
# the class statement, so they don't stick around in the current
# namespace.
del __init__, val, d  # These are all "local" to or part of the imple
chepner
  • 497,756
  • 71
  • 530
  • 681
  • –1 The name is not irrelevant. – wim Mar 23 '18 at 19:39
  • The call to `@val.setter` is equivalent to `d['val'] = property(fget=d['val'], fset=val)` rather than `d['val'].__set__ = val`. That might help explain. Here you could make the names of the getter and setter different and that might make it even clearer. But wim is right than in a class definition like OPs the getter and setter have to have the same name so that ultimately there's only one attribute encompassing both. – Alex Hall Mar 23 '18 at 19:42
  • Right; I meant to double check that after I finished the answer. It *seems* like it should be irrelevant :) – chepner Mar 23 '18 at 19:43
  • @AlexHall Does `@val.setter` really redefine the property, or is that just the closest you can do in this pseudo-Python? – chepner Mar 23 '18 at 19:46
  • It actually creates a new property. Something like `type(self)(...)`. I don't really understand why it needs to do this, rather than mutate existing property, but that's what it does. *shrug* – wim Mar 23 '18 at 19:48
  • Actually I was just guessing and now that I think about it you may be right. Certainly it has to return something so that `val` isn't None in the end. But maybe it just adds the attribute and then returns the same property again. – Alex Hall Mar 23 '18 at 19:48
  • If you know why a new property gets created each time, go and get your +450 points [here](https://stackoverflow.com/q/49081819/674039). – wim Mar 28 '18 at 19:43