3

I work with Python classes that define properties that return some privately managed data member. When accessing this data in the body of other methods, is it better practice to use the public or private interface?

class A:

  def __init__(self):
    self._x = something
  
  @property
  def x(self):
     return self._x

  def f(self):
    # Access via self.x or self._x?
    ...

Would the recommendation change if, say, this data member is not simply a private property (i.e. the body of self.x is more complicated)? For example:

class A:

  def __init__(self):
    self._container = SomeContainer()
  
  @property
  def x(self):
     return self._container.some_accessor_method()

  def f(self):
    # Access via self.x or self._container.some_accessor_method?
    ...
3
  • I would be fascinated to know the reasons for downvotes. If you think it is a bad question- please let me know why.
    – Jon Deaton
    Feb 27, 2021 at 19:10
  • I did not downvote, so I can only guess, but IMHO this question does not show much research effort of yours, which is expected here for any good question. Some research would have probably bring you the insight that your question is not Python specific, it applies to any language with classes and a notion or convention of encapsulating private members by public accessors. So it is IMHO pretty much a duplicate of an existing Java question from 2013:
    – Doc Brown
    Mar 3, 2021 at 6:51
  • 2
    Should the methods of a class call its own getters and setters? - and when the bounty ends, don't be astonished when the question gets closed as a duplicate.
    – Doc Brown
    Mar 3, 2021 at 6:52

4 Answers 4

1

Usually when public/private are talked about in terms of object design it is about who is allowed to access what external to the class. From this perspective it does not matter since these methods are "internal" to the class (or the module, etc). But that doesn't mean there isn't some nuance.

For the most basic usage of this - using a property to restrict the mutability of a field (only having a "getter" and no "setter") - it is probably better to use the property in other parts of the class. That way its mildly easier to scan and confirm that the property isn't being set in any methods.

class Pos:
   def __init__(self, x, y):
       self._x = x
       self._y = y

   @property
   def x():
       return self._x

   @property
   def y():
       return self._y


   def hypotenuse(self):
       # probably doesn't matter either way
       return ((self.x * self.x) + (self.y * self.y))**(1/2)

There are other potential usages where it might be inappropriate to do this. For example, say you have a class that maintains some "list of things". It would be a reasonable design choice to make that list be a "private" thing and create a property that would make a clone of the list when accessed. (That way no caller could call .append() and affect its internal state). In this situation, you would want to use the private version to avoid making that copy.

class ThingHolder:
    def __init__(self):
        self._things = [1, 2, 3]

    @property
    def things(self):
        return self._things[:]

    def print_things(self):
        for thing in self._things:
            print(thing)

Accessing the public one instead of the private also makes it easier to later refactor to doing things like the "mixin pattern".

# If you start with this
class Dog:
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name

    def speak(self):
        print("I am " + self.name)

# It might be mildly easier to refactor to this
# if you want to for whatever reason
class NamedSpeaker:
    def speak(self):
        print("I am " + self.name)

class Dog(NamedSpeaker):
    def __init__(self, name):
        self._name = name

    @property
    def name(self):
        return self._name


class Person(NamedSpeaker):
    def __init__(self, first_name, last_name):
        self._first_name = first_name
        self._last_name = last_name

    @property
    def name(self):
        return self._first_name + " " + self._last_name

I'm sure there are more examples of where it might swing one way or another, I just can't think of many.

0

It depends. The benefits of using the getter/setter pattern backed by private fields vary by usage, but there are multiple, see this question and the answers, which has a number of reasons, some of which may or may not be important to you when getting or setting fields on your class.

One of the main reasons to use that pattern is to prevent outside consumers from modifying things in ways they shouldn't or to separate read vs write access, which are both unnecessary abstractions from the point of view of the class itself. Because of this, it isn't bad to use the private self._x field internally instead of self.x() - though other parts of the logic of your class or program may make the other benefits of using a getter method appealing enough to use that instead.

If self.x() is going to be defined anyway, it won't hurt to use it internally unless the value returned by it is different to the private backing field in some cases, and if you need to intercept reads to x for debugging, adding a breakpoint in one place is much easier than adding one for every usage of self._x throughout the class definition

0

Use public API when possible. Being a public API it is not expected to change and will future-proof your private references. However, if your public API is narrow (it should be) don't hesitate to use internal access for publicly unavailable functions.

0

A practical argument in favor of using the public interface is that it makes your class a bit more flexible. For example, if your property ever changes to:

@property
def x(self):
    return self._container.some_better_accessor_method()

You now need to hunt all occurrences of self._container.some_accessor_method and modify them to self._container.some_better_accessor_method. Of course, this isn't particularly hard given modern text editors and IDEs, but nevertheless the risk of forgetting to change a line and/or introducing bugs is there.

If you dislike this because self.x actually has a complicated body and needs to perform many calculations, I suggest you give functools.cached_property a try to cache the attribute after its first access:

from functools import cached_property

# ...

@cached_property
def x(self):
    # cached on subsequent accesses
    return self._container.some_very_costly_accessor_method()

On a more theoretical level, I wonder what is the use of defining a public property if you're not willing to use it. I'm sure people have their very specific contexts where this is actually useful, but as a rule of thumb, I'd classify a property that you don't access as a code smell.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.