FI-Info
FI-Info

Reputation: 705

Define a property in python

I have a class like this:

class MyItem:

    def __init__(self):
        self.person = None
        self.place = None            
        self.thing = None # property

However I also want to define a property for one of the attributes, for example:

@property
def thing(self):
    return self.person + self.place

In other words, I want to make my initialization show all my variables so it's explicit, but I want to "override" one of those attributes with a class property. How would I do this?

As an example of what I currently have:

class MyItem:
    def __init__(self):
        self.person = None
        self.place = None            
        self.thing = None # property
    @property
    def thing(self):
        return self.person + self.place

m=MyItem()
m.person='A'
m.place='B'
print m.thing
>>> None

Upvotes: 2

Views: 307

Answers (2)

Matt Hall
Matt Hall

Reputation: 8122

Maybe this isn't the answer you're looking for, but my advice:

Don't define MyItem.thing in more than one place.

That would be confusing and probably give you heartburn in the long run. And the short run.

The thing is that MyItem.__init__() is not documentation, it's just defining how instantiation happens. Documentation is documentation and unfortunately it is a bit of a hassle to write sometimes. Have a look at sphinx's autodoc and/or the pdoc3 project to see how these tools can write your docs for you, based on your classes, properties, methods, etc.

Here's how I would do it:

class MyItem:
    """
    Defines an item.

    Attributes
    ----------
        person: A person.
        place: A place.

    Properties
    ----------
        thing: A thing, the sum of person and place.

    Methods
    -------
        There aren't any, but there will be!
    """
    def __init__(self, person=None, place=None):
        self.person = person
        self.place = place

    @property
    def thing(self):
        """
        Property of MyItem instance. The sum of person and place,
        if both of those things exist.

        Returns
        -------
        str. The sum of the person and place attributes.
        """
        if (self.person is not None) and (self.place is not None):
            return self.person + self.place
        else:
            return None

m = MyItem('A', 'B')
print(m.thing)

This returns:

'AB'

I'd also start using Python 3 as soon as possible.

Upvotes: 1

juanpa.arrivillaga
juanpa.arrivillaga

Reputation: 95948

There are two things wrong here. Firstly, your code as written is allowing the shadowing of a property by an instance attribute because you are on Python 2 and have not inherited from object, the descriptor protocol requires new-style classes to work properly. So, note what happens when we do this:

class MyItem(object):
    def __init__(self):
        self.person = None
        self.place = None
        self.thing = None # property
    @property
    def thing(self):
        return self.person + self.place

m=MyItem()
m.person='A'
m.place='B'
print m.thing

The output should be:

Traceback (most recent call last):
  File "test.py", line 10, in <module>
    m=MyItem()
  File "test.py", line 5, in __init__
    self.thing = None # property
AttributeError: can't set attribute

Which is what we should expect, since we haven't created a setter. I would argue this is the behavior you would want. However, there is nothing stopping you from implementing a setter that simply does nothing:

class MyItem(object):
    def __init__(self):
        self.person = None
        self.place = None
        self.thing = None # property
    @property
    def thing(self):
        return self.person + self.place
    @thing.setter
    def thing(self, val):
        pass

m=MyItem()
m.person='A'
m.place='B'
print m.thing

Which now ouputs:

AB

Although, I consider this an example of failing silently in a way that will probably cause headaches down the road. I would strongly advise against doing this, but it's possible.

Upvotes: 2

Related Questions