Image-processing filters using decorator pattern in Python 3
First of all, since Python is dynamically typed, there is not really a need for interfaces. That’s why there is no such language concept. So your IImage type does not really serve any need.

Similarly, the base type ImageDecorator is not really that useful. In statically typed language you are using these design patterns, and ultimately such a base type, because you need to interact with some common base type. Since we have duck typing in Python, this is not really necessary.

Following the Zen of Python, “Simple is better than complex”; and looking at those classes you wrote, it looks somewhat complex to me considering what it does. Compare it with the following solution:

#!/usr/bin/python3
import PIL.Image, PIL.ImageFilter, PIL.ImageOps

def blur(image, radius):
    return image.filter(PIL.ImageFilter.GaussianBlur(radius))

def flip(image):
    return PIL.ImageOps.flip(image)

im_obj = PIL.Image.open('lena.gif')
im_obj = im_raw.convert('L')
im_obj_flipped = flip(im_obj)
im_obj_flipped_blurred = blur(im_obj_flipped, 5)

im_obj.show('original')
im_obj_flipped.show('flipped')
im_obj_flipped_blurred.show('flipped and blurred')


I think this is a lot more to the point, and it shows exactly what’s going on without unnecessarily wrapping the image in an object that does not really serve any purpose.

Of course, I don’t know if your BaseImage is actually needed for something; but if it’s just what you’re showing here, then it’s just a wrapper that doesn’t do anything on its own. You should avoid creating types that don’t have any behavior, especially if they only contain a single object. It’s much easier to just use that actual object then instead.

That being said, if the BaseImage actually is more complicated, then you can still keep your filters simple:

def blur(image, radius):
    rawImage = image.get_img_obj()
    modifiedImage = rawImage.filter(PIL.ImageFilter.GaussianBlur(radius))
    return BaseImage(modifiedImage)

def flip(image):
    rawImage = image.get_img_obj()
    modifiedImage = PIL.ImageOps.flip(rawImage)
    return BaseImage(modifiedImage)


Now you retain your image type, even after the transformation.