Recursion error when combining abstract factory pattern with delegator pattern

Question:

I am learning about design patterns in Python and wanted to combine the abstract factory with the delegation pattern (to gain deeper insights into how the pattern works). However, I am getting a weird recursion error when combining the two patterns, which I do not understand.

The error is:

  [Previous line repeated 987 more times]
  File "c:UsersjennyDocumentsdesign_patterncreationalabstract_factory.py", line 60, in __getattribute__
    def __getattribute__(self, name: str):

RecursionError: maximum recursion depth exceeded 

It is raised when client_with_laptop.display() is called. However a/the recursion error is already stored in client_with_laptop._hardware during the __init__, although factory.get_hardware() returns a laptop instance.

The code is:

from abc import abstractmethod


class ITechnique:
    #abstract product

    @abstractmethod
    def display(self):
        pass

    def turn_on(self):
        print("I am on!")

    def turn_off(self):
        print("I am off!")


class Laptop(ITechnique):
    #concrete product
    def display(self):
        print("I'am a Laptop")

class Smartphone(ITechnique):
    #concrete product
    def display(self):
        print("I'am a Smartphone")

class Tablet(ITechnique):
    #concrete product
    def display(self):
        print("I'm a tablet!")


class IFactory:

    @abstractmethod
    def get_hardware():
        pass

class SmartphoneFactory(IFactory):

    def get_hardware(self):
        return Smartphone()


class LaptopFactory(IFactory):

    def get_hardware(self):
        return Laptop()

class TabletFactory(IFactory):

    def get_hardware(self):
        return Tablet()


class Client():

    def __init__(self, factory: IFactory) -> None:
        self._hardware = factory.get_hardware()

    def __getattribute__(self, name: str):
        return getattr(self._hardware, name)

if __name__ == "__main__":

    client_with_laptop = Client(LaptopFactory())
    client_with_laptop.display()

    client_with_tablet = Client(TabletFactory())
    client_with_tablet.display()

    client_with_smartphone = Client(SmartphoneFactory())
    client_with_smartphone.display()

When I access the attribute _hardware and remove the get_attribute section (so, basically, when I remove the delegation pattern), everything works as expected. See below the modified code section, which works:

class Client():

    def __init__(self, factory: IFactory) -> None:
        self._hardware = factory.get_hardware()

if __name__ == "__main__":

    client_with_laptop = Client(LaptopFactory())
    client_with_laptop._hardware.display()

    client_with_tablet = Client(TabletFactory())
    client_with_tablet._hardware.display()

    client_with_smartphone = Client(SmartphoneFactory())
    client_with_smartphone._hardware.display()

Can anybody help me explain why the recursion error occurs or how to fix it. My objective was (1) to have varying devices depending on the factory used in client and (2) to be able to call the methods from the _hardware without typing client._hardware all the time but calling it directly from the client object, e.g. client.display(). It is not whether this is, in reality, a useful approach or not; I simply want to understand the pattern – and the occurring error – better. 🙂

Asked By: JKupzig

||

Answers:

from abc import abstractmethod


class ITechnique:
    #abstract product

    @abstractmethod
    def display(self):
        pass

    def turn_on(self):
        print("I am on!")

    def turn_off(self):
        print("I am off!")


class Laptop(ITechnique):
    #concrete product
    def display(self):
        print("I'am a Laptop")

class Smartphone(ITechnique):
    #concrete product
    def display(self):
        print("I'am a Smartphone")

class Tablet(ITechnique):
    #concrete product
    def display(self):
        print("I'm a tablet!")


class IFactory:

    @abstractmethod
    def get_hardware():
        pass

class SmartphoneFactory(IFactory):

    def get_hardware(self):
        return Smartphone()


class LaptopFactory(IFactory):

    def get_hardware(self):
        return Laptop()

class TabletFactory(IFactory):

    def get_hardware(self):
        return Tablet()


class Client():

    def __init__(self, factory: IFactory) -> None:
        self._hardware = factory.get_hardware()

    def __getattribute__(self, name: str):
        return getattr(self._hardware, name)

After you created a Client object, that object has the
method __getattribute__. Inside this method you
then proceed to call this object’s __getattribute__
method when you access this objects display method.
This causes instant recursion. To solve this you need
to allow for figuring out what needs done.
Read the documentation for __getattr__ and __getattribute__ to determine
how you want to handle it.

Answered By: TimothyH

The recursion error occurs because the getattribute method in your Client class is triggering a recursive call when trying to access attributes of _hardware. This happens because getattribute calls getattr(self._hardware, name), which then tries to call getattribute again on the _hardware attribute, leading to infinite recursion.

To fix this, you can explicitly define which attributes should be delegated to the _hardware object by overriding the getattr method instead of getattribute. Here’s how you can modify your Client class:

class Client:
def init(self, factory: IFactory) -> None:
self._hardware = factory.get_hardware()

def __getattr__(self, name: str):
    return getattr(self._hardware, name)
Answered By: Katie Dillan
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.