I've been reading OOP and trying to grasp the concept of self and __init__ and I think I found an explanation that makes sense (to me at least).  this is an article on building a linear regression estimator using OOP concepts.
class MyLinearRegression:
    def __init__(self, fit_intercept=True):
        self.coef_ = None
        self.intercept_ = None
        self._fit_intercept = fit_intercept
The layman explanation is as follows:
At a high level,
__init__provides a recipe for how to build an instance ofMyLinearRegression... Since an instance ofMyLinearRegressioncan take on any name a user gives it, we need a way to link the user’s instance name back to the class so we can accomplish certain tasks. Think ofselfas a variable whose sole job is to learn the name of a particular instance
so I think this makes sense.  what I dont get is why self is used again in when defining new methods.
def predict(self, X):
    """
    Output model prediction.
    Arguments:
    X: 1D or 2D numpy array 
    """
    # check if X is 1D or 2D array
    if len(X.shape) == 1:
        X = X.reshape(-1,1) 
    return self.intercept_ + np.dot(X, self.coef_)
In this version.  What is self referring to?
 
     
     
     
     
    