Dependent classes in Python

123 views Asked by At

EDIT: After the discussion in the comments, this is the technical part of the question. There is also the aesthetic side, how to do this neatly.

How can I create classes that depend on parameters, but equal parameters result in equal classes? Something like this:

def create_class(n):
    class Bar:
        nn = n
    return Bar

a = create_class(3)
b = create_class(3)
print(a==b) #False

In this example the resulting classes are different. Is there a way to produce equal classes dynamically?

QUESTION BEFORE EDIT: The implementation I have now is using a mega class and the mega class itself creates the smaller classes as parameters. It fails on the equality test above, also has an ugly long class definition.

class Foo:
    def __init__(self, n):
        self.n = n

        class Bar:
            nn = n
            def __init__(self, m):
                print("This uses n={} and m={} in funny ways".format(Bar.nn, m))
        self.B = Bar

        class Barr(Bar):
            nnn = n+1
            def asd(self):
                print("I have inheritance too {} {}".format(Bar.nn, Barr.nnn))
        self.BB = Barr
    
    def __eq__(self, other):
        return type(self)==type(other) and self.n==other.n

And then I use them the following way:

a = Foo(3) 
b = a.B(10) 
#This uses n=3 and m=10 in funny ways
bb = a.BB(1) 
#This uses n=3 and m=1 in funny ways
bb.asd() 
#I have subclasses too 3 4
aa = Foo(5)
print(b == aa.B(10)) #should be false
#This uses n=5 and m=10 in funny ways
#False

It also fails to match the classes when the parameters agree:

ap = Foo(3)
print(ap==a) #True
print(ap.B==a.B) #False

The first inequality I forced with the eq override, but the resulting Bar classes seem to always differ. I am aware of the type(a, b, c) class constructor, but the classes depend on complex variables (if n were a complex object in the above example), so I can't number them like Bar1 Bar2...

Is there a neat and elegant way to do dependent type like classes in python?

2

There are 2 answers

0
Joseph Sible-Reinstate Monica On

One way would be to store the classes you've previously created, only making a new one if there's not already one with the same parameter. You can use a WeakValueDictionary so that you aren't unnecessarily storing them forever once they're gone everywhere else, like this:

import weakref

def create_class(n):
    class Bar:
        nn = n
    return Bar

d = weakref.WeakValueDictionary()
def get_or_create_class(n):
    try:
        return d[n]
    except KeyError:
        c = create_class(n)
        d[n] = c
        return c

a = get_or_create_class(3)
b = get_or_create_class(3)
print(a==b) #True
0
Lewwwer On

I have a hacky solution. It is possible to override the type metaclass and implement my custom equality. Here's a short example:

a = type("A", (object, ), {})
aa = type("A", (object, ), {})
print(a, aa, a==aa, type(a))

class test(type):
    def __eq__(self, other):
        return type(self)==type(other) and self.__name__ == other.__name__

b = test("B", (object, ), {})
bb = test("B", (object, ), {})
print(b, bb, b==bb, type(b))

ob = b()
obb = bb()
print(type(ob), type(obb), type(ob)==type(obb))

The output is:

<class '__main__.A'> <class '__main__.A'> False <class 'type'>
<class '__main__.B'> <class '__main__.B'> True <class '__main__.test'>
<class '__main__.B'> <class '__main__.B'> True

Not sure if this has any unwanted side effects, but solves the exact problem I was facing. The other answer with memoization works too. I guess different run instances could create different classes based on the same parameters (that might happen with this approach too, I haven't tested).