3

So I have a class with a couple of methods defined as:

class Recognizer(object):

    def __init__(self):
        self.image = None
        self.reduced_image = None

    def load_image(self, path):
        self.image = cv2.imread(path)
        return self.image

Say I wanna add a third method that uses a return value from load_image(). Should I define it like this:

    def shrink_image(self):
        self.reduced_img = cv2.resize(self.image, (300, 300))
        return self.reduced_img

Or should I define it like this:

    def shrink_image(self, path):
        reduced_img = cv2.resize(self.load_image(path), (300, 300))
        return reduced_img

What exactly is the difference between the two? I can see that I can have access to the fields inside of init from any method that I declare within that class so I guess if I update the fields within init I would be able to access those fields for a instance at a given time. Is there a consensus on which way is better?

dm03514
  • 54,664
  • 18
  • 108
  • 145
Dikshant Adhikari
  • 664
  • 1
  • 10
  • 24
  • https://stackoverflow.com/questions/11017364/should-internal-class-methods-returnvalues-or-just-modify-instance-variables-in – dm03514 Oct 12 '17 at 13:42

2 Answers2

1

In the second way the variable is scoped to the shrink_image function.

In the first way the variable is scoped to the objects lifetime, and having self.reduced_img set is a side-effect of the method.

Only seeing your code sample, without seeing clients, the second case is "better", because reduced_img isn't used anywhere else, and is unecessary to bind it to the instance. There def may be a use case where you need to persist the last self.reduced_img call making it a necessary side-effect.

In general it is extremely helpful to minimize side effects. Having side effects especially ones that mutate state can make reasoning about your program more difficult.

This is especially seen when you have multiple accessors to your object.

Imagine having the first shrink_image, you release your program, you have a single client in a single call site of the program calling shrink_object, easy peasy. After the call self.reduced_img will be the result.

Imagine sharing the object between multiple call sites?? It introduces a temporal-ish coupling: you may no longer be able to make an assumption about what reduced_img is, and accesses to it before calling shrink_image may no longer be None, because there may be other callers!!!

Compare this to the second shrink image, callers no longer have the mutatable state, and it's easier to reason about the state of Recognizer instance across shrink_image calls.


Something really nuts happens for the first example when multiple concurrent calls are introduced. It goes from being difficult to reason about and potentially logically incorrect to being a synchronization and data race issue.

Without concurrent callers this isn't going to be an issue. But it's def a possibility, If you're using this call in a web framework and you create a single instance to share between multiple web worker processes you can get this implicit concurrency and could potentially, maybe be subject to race conditions :p

dm03514
  • 54,664
  • 18
  • 108
  • 145
1

What exactly is the difference between the two?

In Python the function with the signature __init__ is the constructor of the object, which is invoked implicitly when calling it via (), such as Recognizer()

The term "better" is vague, because in the former example you are saving the image as a property on the object, hence making the object larger.

But in second example you are simply returning the data from the function, to be used by the caller.

So it's a matter of context and style.

A simple rule of thumb is if that you are going to be using the property reduced_img in the context of the Recognizer object then it would be ideal to save it as a property on the object, to be accessed via self. If the caller is simply using the reduced_img and Recognizer is unaware of any state changes, then it's fine to just return it from the function.

Ólafur Aron
  • 352
  • 2
  • 12