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)
Jason Aller
  • 3,541
  • 28
  • 38
  • 38
C.LECLERC
  • 510
  • 3
  • 12
  • 3
    At least part of your problem is name mangling, see e.g. http://stackoverflow.com/q/7456807/3001761 – jonrsharpe Apr 19 '16 at 19:54
  • Isn't a metaclass the (almost) only example where double underscore is not a bad practice ? – C.LECLERC Apr 19 '16 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). – mgilson Apr 19 '16 at 19:59
  • 1
    @C.LECLERC where'd you hear that? Read the error, the (first) problem is pretty clear. – jonrsharpe Apr 19 '16 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 ... – mgilson Apr 19 '16 at 20:01
  • Thank you guys, it worked by removing the decorator and the double underscore. I didn't think that the interperter cared about the double underscore. I thought it was only used for a communication purpose. – C.LECLERC Apr 19 '16 at 20:07

1 Answers1

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.

Bharel
  • 23,672
  • 5
  • 40
  • 80
  • 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 ? – C.LECLERC Apr 19 '16 at 20:13