You've reached a fork: on the left lies "in general," while on the right you can see "in specific."
In general, yes, you want your object's members to be determined by the class that defines the object. That's the whole point of having a class - to contain and specify similar behavior by grouping together some code (methods) and some data (instance and class variables).
In specific, however, sometimes you have a function that just needs to hang some data someplace. It may have nothing to do with the class at all - just "have I seen you before" or "here is the memoized result of a computation."
So in general, yes, you should be striving to put all your class-related data into your class. But don't get too bent out of shape if there happens to be some specific case where that doesn't happen.
Update:
After some comments, let's consider examples of use:
Use of attributes by methods:
If you have one or more methods that use an attribute, but it's not generally available to the public, then you can delay setting the attribute until you use it:
class Window:
def print_text(self, text):
if self._bg_color is None:
self._bg_color = WHITE
# continue...
This attribute won't have to be in your __init__ method, since you are taking responsibility for making sure it is set before use in your own methods.
Use of attributes as part of the API of the class.
In the case where your attribute is part of the API of the class, you cannot control how the attribute will be used. Some dumb user is going to do whatever thing they want, and when it doesn't work, they'll be complaining about your code on Stack Overflow. ;-)
In this case, you have two choices: (a) you can ensure that it is always set, or (b) you can intercept accesses to the member (with an @property method) and set it on demand.
class Color:
def __init__(self, r, g, b):
self.r = r
self.g = g
self.b = b
# Option (a): Ensure properties are always set:
self.h, self.s, self.v = _compute_hsv(r, g, b)
# Option (b): Intercept property access and compute on demand
@property
def h(self):
if self._h is None:
self._h, self._s, self._v = _compute_hsv(self.r, self.g, self.b)
return self._h
# likewise for s, v