rpiboy
Posts: 110
Joined: Mon Mar 20, 2017 8:39 pm

@property

Wed Mar 06, 2019 9:14 pm

Code: Select all

class Foo:     
@property     
def foo(self):         
    return "bar" 
From my understanding property has a getter and a setter.

Code: Select all

a=Foo()
a.foo
Why does "a" return "bar"?


If i have this code without @property it outputs

Code: Select all

bound method Foo2.foo of <__main__.Foo2 object at 0x7fa0d9834390

Code: Select all

class Foo:
def foo(self):
return "bar"

User avatar
ben_nuttall
Raspberry Pi Foundation Employee & Forum Moderator
Raspberry Pi Foundation Employee & Forum Moderator
Posts: 231
Joined: Sun Aug 19, 2012 11:19 am
Location: Cambridge, UK
Contact: Website

Re: @property

Wed Mar 06, 2019 10:20 pm

You can think of properties as attributes with calculated values and custom rules for what they can be (or what happens when they're set).

When you typed a.foo when it wasn't a property, it was a method. You call methods with brackets, so a.foo() would have returned 'bar'.

Here is a class with a foo property (getter and setter), and a bar method:

Code: Select all

class Foo:
    def __init__(self, foo):
        self.foo = foo
        
    @property
    def foo(self):
        return self._foo
        
    @foo.setter
    def foo(self, value):
        if type(value) == str:
            self._foo = value
        else:
            raise TypeError("foo must be a string")
        
    def bar(self):
        return "bar"
Here I will use it:

Code: Select all

>>> f = Foo("hello")
>>> f.foo
'hello'
>>> f.bar
<bound method Foo.bar of <__main__.Foo object at 0x7f804810e7f0>>
>>> f.bar()
'bar'
>>> f.foo = 'hello world'
>>> f.foo
'hello world'
>>> f.foo = 1
...
TypeError: foo must be a string
>>> f.foo
'hello world'
You'll notice I added a type enforcement to the foo setter, so it could only be set to a string. This is just one use for this.

Properties are really useful ways of providing a nice API for more complex code. foo.value = 1 is better than foo.set_value(1).

We use properties a lot in gpiozero. You can use led.on() or led.value = 1 to turn on an LED. This means you can remove if statements from your code, e.g. led.value = button.value instead of if button.value: led.on() else: led.off

Take a look at the source code for gpiozero for many more examples: https://github.com/rpi-distro/python-gpiozero
Community Manager - Raspberry Pi Foundation
Author of GPIO Zero and creator of piwheels

rpiboy
Posts: 110
Joined: Mon Mar 20, 2017 8:39 pm

Re: @property

Thu Mar 07, 2019 2:47 am

Why do you put

Code: Select all

self._foo
instead of

Code: Select all

self.foo

scotty101
Posts: 3732
Joined: Fri Jun 08, 2012 6:03 pm

Re: @property

Thu Mar 07, 2019 9:46 am

rpiboy wrote:
Thu Mar 07, 2019 2:47 am
Why do you put

Code: Select all

self._foo
instead of

Code: Select all

self.foo
Convention.
Unlike other languages python doesn't have public/private properties so to indicate which properties the user of the class should NOT make use of externally, the convention is to begin them with an underscore.
Electronic and Computer Engineer
Pi Interests: Home Automation, IOT, Python and Tkinter

User avatar
ben_nuttall
Raspberry Pi Foundation Employee & Forum Moderator
Raspberry Pi Foundation Employee & Forum Moderator
Posts: 231
Joined: Sun Aug 19, 2012 11:19 am
Location: Cambridge, UK
Contact: Website

Re: @property

Thu Mar 07, 2019 4:37 pm

That example is a pattern for providing getter/setter with logic but you're saving the value in a "private" attribute with the same name. I say "private" because it's not really private, they could still write to it with f._foo = "blah" but the public API is given as f.foo.

Without using _foo:

Code: Select all

@property
def foo(self):
    return self.foo

@foo.setter
def foo(self, value):
    self.foo = value
This would fail because both the getter and setter would call themselves infinitely:

Code: Select all

RecursionError: maximum recursion depth exceeded
Community Manager - Raspberry Pi Foundation
Author of GPIO Zero and creator of piwheels

rpiboy
Posts: 110
Joined: Mon Mar 20, 2017 8:39 pm

Re: @property

Fri Mar 08, 2019 10:57 pm

Why would they call themselves infinitely?

User avatar
Paeryn
Posts: 2698
Joined: Wed Nov 23, 2011 1:10 am
Location: Sheffield, England

Re: @property

Sat Mar 09, 2019 2:46 am

rpiboy wrote:
Fri Mar 08, 2019 10:57 pm
Why would they call themselves infinitely?
Look at what is happening if you try, e.g.

Code: Select all

class Foo:
    @property
    def foo(self):
        return self.foo

f = Foo()
print(f.foo)
You know that f.foo is going to call the method f.foo() to get the value to return. f.foo() tries to return self.foo, you know that self is f in this instance so the value to return is f.foo which means calling f.foo().

As you see, you end up with f.foo() calling itself to get the value to return which will never end because f.foo() never reaches the end of the function.
She who travels light — forgot something.

User avatar
ben_nuttall
Raspberry Pi Foundation Employee & Forum Moderator
Raspberry Pi Foundation Employee & Forum Moderator
Posts: 231
Joined: Sun Aug 19, 2012 11:19 am
Location: Cambridge, UK
Contact: Website

Re: @property

Sun Mar 10, 2019 1:25 am

rpiboy wrote:
Fri Mar 08, 2019 10:57 pm
Why would they call themselves infinitely?
Imagine the following regular function:

Code: Select all

def foo(bar):
    return "hello " + foo(bar)
This would call itself indefinitely and trigger a RecursionError.

What I did with the property first was define the foo attribute as "look up _foo and return that". That's fine. But what I did next was declare foo as "look up foo and return that". It's like looking up "recursion" in the dictionary and it telling you to look up "recursion". Try googling "recursion" too.
Community Manager - Raspberry Pi Foundation
Author of GPIO Zero and creator of piwheels

Return to “Python”