sumguy
sumguy

Reputation: 47

Using getProperty() in a class instead of self.property?

I don't know the proper terminology for this so couldn't find anything online about this.

Take this example code:

def Fruit(object):
    def __init__(self, color):
        self._color = color
    def color(self):
        return self._color

Now, say I want to check to see whether a fruit is red:

    def isRed(self):
        if self._color == "red":
            return True
        return False

Would work perfectly fine. However, so does

    def isRed(self):
        if self.color() == "red":
            return True
        return False

Is there a reason why it is good practice to have a getProperty function? (I'm assuming it is, since an MIT professor, whose course I'm taking, does this with his classes and expects students to do the same on their homework.)

Are either of these two examples different, and why is it against convention to simply refer to the property by self.property?

Edit: Added underscore to make self._color for convention.

Upvotes: 0

Views: 1754

Answers (2)

das-g
das-g

Reputation: 9994

TL;DR: Not all general programming best practices aren't Python best practices. Getter and setter methods are a general (OOP) best practice, but not a Python best practice. Instead, use plain Python attributes when you can and switch to Python @propertys as-needed.

It many object-oriented programming languages (e.g. Java and C++), it is regarded as good practice to:

  • make data members (a.k.a. "attributes") private
  • provide getter and / or setter methods to access them

Why?

  • Enable change through "encapsulation", by keep interface stable while keeping implementation flexible (decoupling)
  • Allow for more granular access levels

Let's look at these in detail:

"encapsulation" in object orientation

One of the core ideas of object orientation is that bundling the definition of small chunks of data together with functionality related to that data makes imperative/"structured"/procedural programs more manageable and evolvable.

These bundles are called "objects". Each "class" is a template of a group objects with the same data structure (though potentially different data) and the same related functionality.

The data definition are the (non-static) data members of a class (the "attributes" of the objects). The related functionality is encoded in function members ("methods").

This can also be seen as a way to build new user-defined types. (Each class is a type, each object is kinda like a value.)

Often, the methods need more guarantees about the attribute values to work properly than the types of the data members already provide. Let's say you have

class Color() {
    float red;
    float green;
    float blue;

    float hue() {
        return // ... some formula
    }

    float brightness {
        return // ... some formula
    }
}

If red, green and blue are in the range [0, 1], the implementation of these methods would probably depend on that fact. Similarly, if they were in the range [0, 256). And whatever the class-internal convention is, it is the task of the methods of that class to uphold it and only assign values to the data members that are acceptable.

Though, usually, objects of different classes have to interact for a meaningful object-oriented program. But you don't want to think about another class' internal conventions, just because you're accessing it, as that would require a lot of lookups to find out what those conventions are. So you shouldn't assign to the data members of objects of a class from code outside that class.

To avoid this happening by mistake or negligence, the widely accepted best practice in these languages is to declare all data members private. But that means that they cannot be read from outside, either! If the value is of interest to the outside, we can work around this by providing a non-private getter method that does nothing but provide the value of the attribute.

Enabling change while limiting ripple effects

Say the outside (e.g. another class) must be able to set the value of some attribute of your class. And say there aren't any restrictions necessary beyond what that attribute's type already imposes. Should you make that attribute public? (Still assuming this isn't in Python!) No! Instead, provide a setter method that does nothing but taking a value as argument and assigning it to the attribute!

Seems kinda dull, so why do that? So that we can change our mind later!

New side effect

Say you want to log to the console/terminal (std-out) each time the red-component of your color object changes. (For whatever reason.)

In a (setter) method, you add one line of code and it does that, without requiring any change in the callers.

But if you need to first switch from assigning to a public attribute to calling a setter method, all the code pieces doing assignments to these attributes (which might be many by that time) have to be changed, too! (Don't forget to make the attribute private, so that none will be forgotten.)

So it's better to have only private attributes from the beginning, and add setter method when code outside the class has to be able to set the value.

Change of internal representation

Say you just noticed that for your application, colors should really be represented internally as hue, value and saturation rather than red, green and blue components.

If you have setter and getter methods, the ones for red, green and blue will become more complicated due to the neccesary conversion calculations. (But the brightness and hue method will become much simpler.) Still, changing them can be much less work than having to change all the code outside the class that uses the class. As the interface stays the same, callers won't have to be changed at all and won't notice a difference.

But if you need to first switch from assigning to a public attribute to calling a setter method ... well, we've been there, haven't we?

decoupling

So accessor methods methods (that what we call getters and setters) help you decouple the public interface of a class from its internal implementation, and thereby the objects from their users. This allows you to change the internal implementation without breaking the public interface, so that code using your class doesn't have to be changed when you do that.

granular access levels

Need an attribute that can only be read from the outside, but not written from the outside? Easy: Provide only a getter method, but no setter method (and have the attribute itself be private).

Less common, but more common than you might think:

Need an attribute that can only be written from the outside, but not read from the outside? Easy: Provide only a setter method, but no getter method (and have the attribute itself be private).

Not sure if your attribute should be accessed (and accessible) from outside your class? Make it private and don't provide any getter and setter for now. You can always add them later. (And then think about what visibility level they should have.)

As you see, there's no reason to ever have a non-private attribute in a mutable object. (Assuming that the runtime overhead doesn't matter for your application (it probably doesn't, indeed) or is optimized away by the compiler (it probably is, at least partially).)

Not a security feature!

Note that "visibility" levels of attributes and methods are not meant for providing application security or privacy (they don't). They're tools to help programmers from making mistakes (by avoiding them to access stuff they shouldn't by accident), but they won't keep adversarial programmers from accessing that stuff anyway. Or, for that matter, honest programmers who think they know what they're doing (whether they do know or not) and willing to take the risk.

Python is different

In Python, everything is public

While Python is also imperative, "structured", procedural and very object-oriented, it takes a much more laid back approach to visibility. There is no real "private" visibility level in Python, nor "protected" or "package" (default in Java) levels.

Essentially, everything in a Python class is public.

This makes sense when Python is used as scripting language for quick-and-dirty ad-hoc solutions that you'll probably code once and then throw away (or keep like that without further development).

If you make more involved applications in Python (and that's certainly possible with Python and also done a lot) you'll probably want to distinguish between a class' public interface and its internal implementation details. Python provides two levels of "hiding" internal members (both, functions and data attributes):

  • by convention: _ prefix
  • by name mangling: __ prefix

"hiding" by convention

Beginning a name with _ signals to everyone outside a namespace (whether a class or a module or a package):

You shouldn't access this, unless you know what you're doing. And I (the implementor of stuff in that namespace) may change that at will, so you probably don't know what you will be doing by accessing it. Stuff may break. And if it does, it'll be your (the one accessing it) fault, not mine (the one implementing it). This member isn't a part of this namespace's public interface.

Yes, you can access it. That doesn't mean that you should. We're all adults here. Be responsible.

And you should adhere to that, even if you'd happen to not be an adult, yet.

hiding by name mangling

Beginning a name with __ signals to everyone outside a namespace (whether a class or a module or a package):

The same as with _ applies, only, you know, even stronger!

Additionally, and only if the namespace is a class (and the attribute name ends in no more than one underscore):

To make sure you don't access these things from outside by accident, Python "mangles" the names of these attributes for access from outside the class. The resulting name is perfectly predictable (it's _ + (simple) class name + original attribute name), so you can still access these things, but you most certainly won't simply by mistake.

Also, this can help avoid name collisions between members of base classes and members of their subclasses. (Though, it won't work as intended if the classes share the same class name, as the "simple class name" is used, not including modules and packages.)

In either case, you may have good reasons to access these values anyway (e.g. for debugging) and Python doesn't want to stand in your way when you do (or with name mangling, at most only slightly so.)

Python has method-based "properties" that can be accessed just like data attributes

So, as there is no real private in Python, we can't apply the pattern/style from Java and C++. But we might still need stable interfaces to do serious programming.

Good thing that in Python you can replace a data attribute with methods, without having to change its users. Pils19's answer provides an example:

class Fruit(object):
    def __init__(self, color):
        self._color = color

    @property
    def color(self):
        return self._color

(Documentation of this decorator here.)

If we also provide a property-setter-method and a property-deleter-method ...

class Fruit(object):
    def __init__(self, color):
        self._color = color

    @property
    def color(self):
        return self._color

    @color.setter
    def color(self, c):
        self._color = c

    @color.deleter
    def color(self):
        del self._color

Then this will act equivalent to a simple data attribute:

class Fruit(object):
    def __init__(self, c):
        self.color = c

But now we have all the freedom of methods. We can leave out any of them (most usual is to only have the getter, so you have a read-only attribute), we can give them additional or different behavior, etc.

This is the recommended approach in Python:

  • use (public) data members if in doubt
  • prefix with _ for implementation details
  • if/when you need additional/different behavior or to disable reading, writing or deleting, use properties or replace public data members with properties

Your professor

I'm assuming [that there is a good practice to define non-property getters and setters in Python], since an MIT professor, whose course I'm taking, does this with his classes and expects students to do the same on their homework.

Are you sure this is what your professor did, or did he use Python's properties mechanism?

If he did, is this a class about Python or does it just so happen that Python is used for the examples (and that your professor also used it to demonstrate something actually only applicable to other languages)?

And let's not forget: Even MIT professors might be forced to teach classes where they aren't experts on every aspect of the subject.

Upvotes: 1

Bierbarbar
Bierbarbar

Reputation: 1479

Normally it's a good practice to you the @Property decorator. And have the internal properties with an single leading underscore. For you example it would look like:

class Fruit(object):
    def __init__(self, color):
        self._color = color

    @property
    def color(self):
        return self._color

Upvotes: 0

Related Questions