2

How can I check if a class is child of a parent created by a factory class?

Consider the following trivial example:

from itertools import count
def basefactory():
    class Base:
        _idc = count(0)
        def __init__(self):
            self._id = next(self._idc)
    return Base

Then I create children using the factory class:

class A(basefactory()):
    def __init__(self):
        super().__init__()

class B(basefactory()):
    def __init__(self):
        super().__init__()

How can I test if A or B are children of a class created by the factory class?
Without the factory class I would use: issubclass(A, Base)) but of course this doesn't work with the factory class, since Base is not defined in the global space.

The reason of this setup is that I want A and B to have a different _idc counters to generate consecutive _ids for each instance of the child classes.
If A and B inherits directly from Base the counter is shared. Let me illustrate with an example what I mean.

With the factory class setup described above, if I do:

ll = [A(), A(), B(), B()]   
for e in ll:
    print(e._id, end=", ")

it prints: 0, 1, 0, 1, . If instead I move Base in the global space and A and B inherit directly from Base, the previous code prints: 0, 1, 2, 3, .

I want the _id to be 0, 1, 0, 1, , this is the reason I'm using the factory class setup. In my real case I don't have just A and B (I have 10 classes created by the factory class and they could increase in future), so I don't want to repeat myself and initialize the counters in each child.

However I also need to assert if a class is child of Base, or in other terms, if it is created through the factory class. And this is the question. Is it possible to do by checking the class hierarchy with issubclass? Or I need to change the setup or use a workaround like people suggested in the comments?

About the proposed duplicate

I don't think is a duplicate of Understanding Python super() with __init__() methods. I know what super does, and is not useful in this specific case. I want each child class to have a different counter, I don't see how referring to the parent class is helpful.
Maybe it is helpful (if so, please post an answer), but since the proposed duplicate doesn't deal with factory classes, I think this question is still different.

Community
  • 1
  • 1
Valentino
  • 7,291
  • 6
  • 18
  • 34
  • 1
    In a nutshell, you don't. You'd have to save a reference to a manufactured class to compare against. You should rather look into `__new__` to implement your counter instead of your current approach. – deceze May 30 '19 at 10:19
  • This is a very difficult question to answer because there's no way to do it with your current setup, and there are just so many different ways to make it work... You could use a metaclass, or you could make `Base` inherit from yet another class, or you could rewrite `Base` so that it counts instances of each subclass separately, etc. – Aran-Fey May 30 '19 at 10:22
  • Possible duplicate of [Understanding Python super() with \_\_init\_\_() methods](https://stackoverflow.com/questions/576169/understanding-python-super-with-init-methods) – Song Jul 06 '19 at 13:33
  • Keep in mind that `A` and `B` do *not* share a base class; they each have a *different* base, both of which happen to have the same name. Is there any reason to repeatedly create distinct but otherwise identical classes? – chepner Jul 06 '19 at 14:16
  • @chepner I edit my question to better explain the situation, hope is more clear now – Valentino Jul 06 '19 at 15:16

2 Answers2

2

I think a decorator would do what you want (add an instance counter to a class) more simply than a factory function that generates an otherwise unnecessary base class.

from itertools import count

def add_counter(cls):
    cls._idc = count(0)
    old_init = cls.__init__
    def __init__(self, *args, **kwargs):
        old_init(self, *args, **kwargs)
        self._id  = next(self._idc)
    cls.__init__ = __init__
    return cls

@add_counter
class A:
    pass

@add_counter
class B:
    pass

ll = [A(), A(), B(), B()]
for e in ll:
    print(e._id, end=", ")
chepner
  • 497,756
  • 71
  • 530
  • 681
0

Well, I don't know how to access this information properly, but when typing help(A), you get Base:

Help on class A in module __main__:
class A(Base)
 |  Method resolution order:
 |      A
 |      Base
 |      builtins.object
 |  
 |  Methods inherited from Base:
 |  
 |  __init__(self)
 |  
 |  ----------------------------------------------------------------------
 |  Data descriptors inherited from Base:
 |  
 |  __dict__
 |      dictionary for instance variables (if defined)
 |  
 |  __weakref__
 |      list of weak references to the object (if defined)

I get it must be possible to access it properly

sashaboulouds
  • 1,566
  • 11
  • 16