1

Say I have a simple object:

class Providers:
    Apple = "Apple"
    Banana = "Banana"
    Cherries = "Seedless Cherries"

I'd like to be able to do something like if "Apple" in Providers.., which I believe requires me to set two magic methods, __len__ and __getitem__.

I tried something as simple as

@classmethod
def __len__(cls):
    return 3

but when I run len(Providers) I get TypeError: object of type 'type' has no len()

but Providers.__len__() returns 3.

How can I get the len of a class without instantiating it? Or do they need to always be instantiated with __init__ and self.Apple = 'Apple'?

pedram
  • 2,931
  • 3
  • 27
  • 43
  • 3
    you've created a class object, if you want to implement those methods in the class object, then you need to implement it in the class object's class, i.e., in the metaclass. Or, more reasonably, don't try to use a class like a container. – juanpa.arrivillaga Sep 13 '19 at 17:54
  • 3
    Congratulations, you've reinvented the [`enum`](https://docs.python.org/3/library/enum.html). – Aran-Fey Sep 13 '19 at 17:58
  • That makes sense. I saw similar code in Airflow's definition of Executors: https://github.com/apache/airflow/blob/master/airflow/executors/__init__.py#L57 and thought it a good practice. I'll take a look at enum. – pedram Sep 13 '19 at 17:58
  • 1
    @pedram it doesn't look like they are trying to use that class like your use-case, you seem to want a simple record/container. That looks like just name-spaced constants / enum-like class – juanpa.arrivillaga Sep 13 '19 at 18:01

2 Answers2

1

Use a metaclass:

class FooMeta(type):
    def __len__(self):
        return 10

class Foo(metaclass=FooMeta):
    pass

print(len(Foo)) # 10

This works because classes are in fact objects, i.e., they are instances of they metaclasses

geckos
  • 5,687
  • 1
  • 41
  • 53
  • 2
    yeah, but this is totally an over-engineered solution to a problem that shouldn't exist in the first place – juanpa.arrivillaga Sep 13 '19 at 17:55
  • I agreed, they right way would be extend the container classes where made to be extended https://docs.python.org/3/reference/datamodel.html#emulating-container-types – geckos Sep 13 '19 at 17:57
  • generally, I prefer composition over inheritance, especially in this case which wouldn't be a natural extension of any of the container classes. The OP seems to almost want an enum, but that is just speculating on my part. – juanpa.arrivillaga Sep 13 '19 at 17:58
  • I prefer FP over OOP. Composing lead to less coupled code, but I think that metaclasses are so mind-blowing that they need to be show, this is a good example of a bad use of metaclasses, but still is a good example – geckos Sep 13 '19 at 18:02
  • I'm really just trying to validate that incoming data matches one of the defined constants for a Provider. I suppose I could do something like `if var in Providers.__dict__.values()` but that seemed ugly. – pedram Sep 13 '19 at 18:07
  • `if var in dir(Providers)` would work, I guess------- edit ---- no it will not, you want values not keys :) – geckos Sep 13 '19 at 18:18
  • Use the enum class, or a namedtuple – geckos Sep 13 '19 at 18:20
1

You need to use metclass because When you do len(Providers) it's like you do type(Providers).__len__(Providers) and in your case:

       type(Providers) ==  <class 'type'>   # no __len__ method here 

something like this:

    class ProviderType(type):
        def __len__(self):
            # this will call __len__ defined on the class it self
            return self.__len__(self)


    class Providers(metaclass=ProviderType):
        Apple = "Apple"
        Banana = "Banana"
        Cherries = "Seedless Cherries"

        def __len__(self):
            return 1


    p = Providers()
    print(len(Providers))  # type(Providers).__len__(Providers)  equavalent to : ProviderType.__len__(Providers)
    print(len(p))    # type(p).__len__(p)  equal to:  Providers.__len__(p)

This how python Interpreter deal with dunder (magic method) method: type(SomeObject).__dunder__(SomeOBject)

Charif DZ
  • 14,415
  • 3
  • 21
  • 40
  • 1
    An excellent explanation from @Martijn Pieters here: https://stackoverflow.com/questions/57911603/overriding-the-str-method-for-classmethods-in-python/57911666#57911666 – Charif DZ Sep 13 '19 at 18:18