2

I have issues trying to implement an easy to use abstract factory.

Goal

To be able to define concrete factories this way:

class MyConcreteFactory( ... ):
    @classmethod
    def __load(cls, key):
        obj = ... # Loading instructions here
        return obj

To be able to use concrete factories this way

obj = MyConcreteFactory[key]

My attempt

I tried to define a metaclass for factories which override bracket operator and encapsulate the factory pattern:

class __FactoryMeta(type):

    __ressources = {}

    @classmethod
    def __getitem__(cls, key):
        if key not in cls.__ressources:
            cls.__ressources[key] = cls.__load(key)
        return cls.__ressources[key]

    @classmethod
    def __load(cls, key):
        raise NotImplementedError


class ConcreteFactory(metaclass=__FactoryMeta):

    @classmethod
    def __load(cls, key):
        return "toto"


a = ConcreteFactory["mykey"]
print(a)

Issue

This failed because the __load method called is the one from the metaclass and not the one from the concrete class. The result is:

Traceback (most recent call last):
  File "C:\Users\walter\workspace\Game\src\core\factories.py", line 34, in <module>
    a = ConcreteFactory["mykey"]
  File "C:\Users\walter\workspace\Game\src\core\factories.py", line 19, in __getitem__
    cls.__ressources[key] = cls.__load(key)
  File "C:\Users\walter\workspace\Game\src\core\factories.py", line 24, in __load
    raise NotImplementedError
NotImplementedError

I tried to remove the __load method from the meta class but then I got this (predictable) error:

Traceback (most recent call last):
  File "C:\Users\walter\workspace\Game\src\core\factories.py", line 30, in <module>
    a = ConcreteFactory["mykey"]
  File "C:\Users\walter\workspace\Game\src\core\factories.py", line 19, in __getitem__
    cls.__ressources[key] = cls.__load(key)
AttributeError: type object '__FactoryMeta' has no attribute '_FactoryMeta__load'

Questions

Is there a way to access class method from a metaclass? Am I wrong and should do this in an other way? Then which way?

Solution

class __FactoryMeta(type):

    ressources = {}

    def __getitem__(cls, key):
        if key not in cls.ressources:
            cls.ressources[key] = cls.load(key)
        return cls.ressources[key]

    def load(cls, key):
        raise NotImplementedError


class ConcreteFactory(metaclass=__FactoryMeta):

    @classmethod
    def load(cls, key):
        return "toto"


a = ConcreteFactory["mykey"]
print(a)
6
  • 3
    At least part of your problem is name mangling, see e.g. stackoverflow.com/q/7456807/3001761 Commented Apr 19, 2016 at 19:54
  • Isn't a metaclass the (almost) only example where double underscore is not a bad practice ? Commented Apr 19, 2016 at 19:59
  • 1
    Jon's correct. __name gets mangled by the interpreter for the express purpose of making it inaccessable to the super class. Change __load to _load and you'll get rid of that error (though you might have some with __resources too -- depending on how you use __getitem__, in the subclasses). Commented Apr 19, 2016 at 19:59
  • 1
    @C.LECLERC where'd you hear that? Read the error, the (first) problem is pretty clear. Commented Apr 19, 2016 at 20:00
  • It's also weird that you're decorating the metaclass methods with @classmethod ... Usually regular methods on metaclasses become class methods on the class ... Commented Apr 19, 2016 at 20:01

1 Answer 1

1

You should not use @classmethod in the metaclass. An instance of the metaclass is the class itself so:

def __getitem__(cls, key):

is actually the class and:

@classmethod
def __getitem__(metacls, key):

gets the metaclass as first argument.

Eiher case, I believe metaclasses make this problem much more complicated. I believe a more viable approach would be to create a base factory class, subclass it accordingly, and use the subclass'es instance as the factory.

Sign up to request clarification or add additional context in comments.

1 Comment

Ty for your help, it solves the problem (+ the mangling issue), but i wanted to overide factories bracket operator to make them to use and to define. Is there a way to do this without metaclass ?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.