The add and mul definitions here are nonsensical because of their dependence on returning self, causing infinite loops. If they create a new distribution using the lambdas then it works fine, as in my own answer below.
I'm just playing around with classes and overriding trying to build a small statistics tool. However, when I run this code I get stuck in a recursion loop inside the __mul__ call which is being run in the n1.pdf call and I cannot figure out why. I think it has something to do with Python lazily executing the __mul__ instead of doing what I kind of 'wanted' (let's say in the language of CS) which was to create a new pointer to the old function call for pdf that is owned by the new pointer to pdf, and then to set the old pointer (the main .pdf pointer) to the new function.
I think this is quite poorly worded so edits extremely welcome if you understand what I'm asking.
import math
import random
class Distribution:
    def __init__(self, pdf, cdf):
        self.pdf = pdf
        self.cdf = cdf
    def pdf(self, x):
        return self.pdf(x)
        
    def cdf(self, x):
        return self.cdf(x)
    def __mul__(self, other):
        if isinstance(other, float) or isinstance(other, int):
            newpdf = lambda x : self.pdf(x) * other
            self.pdf = newpdf
            newcdf = lambda x : self.cdf(x) * other
            self.cdf = newcdf
            return self
        else:
            return NotImplemented
    def __add__(self, other):
        self.pdf = lambda x : self.pdf(x) + other.pdf(x)
        self.cdf = lambda x : self.cdf(x) + other.cdf(x)
        return Distribution(self.pdf, self.cdf)
    
class Normal(Distribution):
    def __init__(self, mean, stdev):
        self.mean = mean
        self.stdev = stdev
    def pdf(self, x):
        return (1.0 / math.sqrt(2 * math.pi * self.stdev ** 2)) * math.exp(-0.5 * (x - self.mean) ** 2 / self.stdev ** 2)
    def cdf(self, x):
        return (1 + math.erf((x - self.mean) / math.sqrt(2) / self.stdev)) / 2
    def sample(self):
        return self.mean + self.stdev * math.sqrt(2) * math.cos(2 * math.pi * random.random())
if __name__ == "__main__":
    n1 = Normal(1,2)
    n1half = n1 * 0.5
    x = n1.pdf(1)
    print(x)
p.s. I know that it is no longer a pdf after being multiplied by 0.5, this is not an issue.
 
     
    