92

Consider the following example:

class A:
    @property
    def x(self): return 5

So, of course calling the a = A(); a.x will return 5

But imagine that you want to be able to modify the property x.
This way, for example:

class A:
    @property
    def x(self, neg = False): return 5 if not neg else -5

And call it with a = A(); a.x(neg=True)

That will raise a TypeError: 'int' object is not callable, that is quite normal, since our x is evaluated as 5.

So, I would like to know how one can pass more then one argument to the property getter, if it is possible at all.

Rizo
  • 3,003
  • 5
  • 34
  • 49

8 Answers8

104

Note that you don't have to use property as a decorator. You can quite happily use it the old way and expose the individual methods in addition to the property:

class A:
    def get_x(self, neg=False):
        return -5 if neg else 5
    x = property(get_x)

>>> a = A()
>>> a.x
5
>>> a.get_x()
5
>>> a.get_x(True)
-5

This may or may not be a good idea depending on exactly what you're doing with it (but I'd expect to see an excellent justification in a comment if I came across this pattern in any code I was reviewing)

ncoghlan
  • 40,168
  • 10
  • 71
  • 80
  • Man... that is actually a brilliant idea. Wish more APIs used this instead of only setters-getters / only properties. – Luke Davis Nov 30 '19 at 22:02
  • More decorators? They all support being used this way, as the decorator syntax is just sugar for a function call and name rebinding. – ncoghlan Dec 18 '19 at 07:17
  • Oh, I see what you mean - more APIs exposing both the setter/getter methods *and* the related property. – ncoghlan Dec 18 '19 at 07:19
  • BTW, if you use the property decorator, in theory you are not even allowed to have arguments. Failing in that you will be getting errors like <...> object is not callable – Andrea Moro Mar 26 '20 at 13:07
  • 2
    The idea of encapsulation is absent here. There is no point in using getters in this way. The interface is broken there. – pentanol Feb 09 '21 at 11:45
  • 1
    I would use instead of `x = property(get_x)`, `@property def x(self): return self.get_x()`. More readable. – Gulzar Jan 02 '22 at 10:58
  • I needed to use this pattern to be able to pass `**kwargs` to the underlying function during unit testing, but still maintain the existing property API for the getter. – DrCord Oct 04 '22 at 20:14
57

I think you did not fully understand the purpose of properties.

If you create a property x, you'll accessing it using obj.x instead of obj.x(). After creating the property it's not easily possible to call the underlying function directly.

If you want to pass arguments, name your method get_x and do not make it a property:

def get_x(self, neg=False):
    return 5 if not neg else -5

If you want to create a setter, do it like this:

class A:
    @property
    def x(self): return 5

    @x.setter
    def x(self, value): self._x = value
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
22

a property should only depend on the related object. If you want to use some external parameters, you should use methods.

Sepp
  • 221
  • 1
  • 2
10

In your second example, you're using a.x() as if it were a function: a.x(neg=True). With this in mind, why not just define it as a function?

NPE
  • 486,780
  • 108
  • 951
  • 1,012
  • That's a direct solution, but i'm using very complex getters and setters and would like to keep using them without having to write separate functions for the attributes. – Rizo Apr 19 '11 at 11:34
  • This does not answer my question. I now I can use functions in python! :) I just want to know if there some way to force a property to act as a function. – Rizo Apr 19 '11 at 11:41
  • 5
    @Rizo why? If you want it to act like a function, then use a function (method). But in answer to your question, the only way would be to return a callable from the property that takes parameter. Then you would have something that looks exactly like a function but less efficient. – Keith Apr 19 '11 at 11:47
  • 4
    If a getter does anything but returning a simple value (or a default value) it's not a "getter" anymore but a regular function. – ThiefMaster Apr 19 '11 at 11:48
  • 2
    @Rizo: in the example you are giving, it is probably wiser to just let the *caller* decide what to do with the return value. after all it is the *caller* passing in the condition. – XORcist Apr 19 '11 at 11:55
9

I know this question is old, but, for reference, you can call your property with an argument like that:

a = A()
assert a.x == 5
assert A.x.fget(a, True) == -5

As mentioned by others, this is not advised.

Campi
  • 1,932
  • 1
  • 16
  • 21
4

In this particular case, you could define two properties, which call an underlying function:

class A:
    @property
    def x(self):
        return self._x(neg = False)

    @property
    def x_neg(self):
        return self._x(neg = True)

    def _x(self, neg):
        return 5 if not neg else -5
Kim SJ
  • 138
  • 1
  • 9
0

I just ran into this issue. I have class Polynomial() and I'm defining a gradient that I would like to return a function if no arguments or evaluate if there are arguments. I want to store the gradient as an attribute so I don't need to calculate it every time I need to use it, and I want to use @property so the gradient attribute is calculated lazily. My solution was to define a class Gradient with a defined call method for Polynomial's grad property to return.

@property
def grad(self):
    """
    returns gradient vector
    """
    class Gradient(list):
        def __call__(self, *args, **kwargs):
            res = []
            for partial_derivative in g:
                res.append(partial_derivative(*args, **kwargs))
            return res
    g = Gradient()
    for i in range(1, len(self.term_matrix[0])):
        g.append(self.derivative(self.term_matrix[0][i]))
    return g

And then I have the following tests pass successfully:

def test_gradient(self):
    f = Polynomial('x^2y + y^3 + xy^3')
    self.assertEqual(f.grad, [Polynomial('2xy + y^3'), Polynomial('x^2 + 3xy^2 + 3y^2')])
    self.assertEqual(f.grad(x=1, y=2), [12, 25])
    f = Polynomial('x^2')
    self.assertEqual(f.grad(1), [2])

So, for this issue we could try:

class A:
    @property
    def x(self):
        class ReturnClass(int):
            def __call__(self, neg=False):
                if not neg:
                    return 5
                return -5
        return ReturnClass()
  • Not sure that the solution is working: If I say `a = A(); print(a.x)` I get 0 as a result - the default value of an non-initialized int. The solution - as far as understand - should be `5` for `a.x`. – Make42 Feb 10 '21 at 22:57
0

Sometimes you need to choose appropriate action based on variable with limited distinct values (cause you have limited distinct choices). Basically you may implement this by a tuple or dictionary, but you may implement it by inner class that defines allowed types.

For example we may generate specifically formatted data based on different sources, to further processing:

from formatted_data import FormattedData

class DataGenerator:

class TYPE:
    CSVFILE = 0
    SQLITE  = 1
    HTTP    = 2

@classmethod
def produceData(c, source_type: int, source_address) -> FormattedData:
    match source_type:
        case c.TYPE.CSVFILE:
            pass
        case c.TYPE.SQLITE:
            pass
        case c.TYPE.HTTP:
            pass

To call such classmethod:

DataGenerator.produceData(DataGenerator.TYPE.CSVFILE, '/financial_history.csv')
DataGenerator.produceData(DataGenerator.TYPE.SQLITE, '/financial_history.sqlite')
Zbyszek
  • 647
  • 2
  • 8
  • 21