5

I'm looking to check if two variables are of the same type in python 3.x. What is the most ideal way to do this?

Take the following example:

class A():
    def __init__(self, x):
        self.x = x

class B(A):
    def __init__(self, x):
        x += 5
        super(B, self).__init__(x)

Ideally, I'd like to return True if two variables of type A and B are compared against one another. Here are some potential solutions that don't work:

>>> a = A(5)
>>> b = B(5)
>>>
>>> type(a) is type(b)
False
>>> isinstance(a, type(b))
False
>>> isinstance(b, type(a))
True

The last one isn't ideal because, as seen in the middle example, if the type being checked against is a subclass of the variable's type, False is returned.

The only solution I've tried that can cover all bases here is:

>>> isinstance(a, type(b)) or isinstance(b, type(a))
True

Is there a better way?

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
Liam Rahav
  • 275
  • 1
  • 5
  • 17
  • 1
    Do you also have to cover the case where you would have class C inheriting from A, then comparing an instance of B with an instance of C? – JBGreen Jul 09 '19 at 16:42
  • 4
    This is a bit tricky. Let's say you have `class C(A): ...`, then an instance `c` of that type would be "the same type" as `a`, right? But would it be also compatible with `b`? In the end, all classes are descendants of `object`, so you can always find some common base. – jdehesa Jul 09 '19 at 16:42
  • 1
    Interesting use case, but what is the actual problem you are trying to solve in your code? I'm curious about this one now, but if you have a problem that can more easily be solved through the implementation of methods in your classes (i.e. you're checking class type and doing something different based on type), then just defining a method to do the right thing for each class is probably the preferred approach. – Engineero Jul 09 '19 at 16:44
  • You may find interesting [How to determine the closest common ancestor class](https://stackoverflow.com/q/15788725) (e.g. you could consider two objects compatible if they have some common ancestor more specific than `object`). – jdehesa Jul 09 '19 at 16:46
  • @JBChouinard @jdehesa Exactly why I'm interested in seeing if someone in the community has a better solution to this problem. That should return `True` in some cases. I suppose it depends on the exact use case of your code, but agreed that there's an issue as everything descends from `object` – Liam Rahav Jul 09 '19 at 16:50
  • @Engineero For the use case in my code this isn't an issue as the "solution" I have at the end of my post (`isinstance(a, type(b)) or isinstance(b, type(a))`) works fine. I'm asking out of curiosity for the best practices around this type of situation. – Liam Rahav Jul 09 '19 at 16:51
  • 1
    "Ideally, I'd like to return True if two variables of type A and B are compared against one another." What exactly do you mean by "compared against one another"? Do you want `a == b` to be true? – chepner Jul 09 '19 at 16:51
  • @chepner No, I mean I'd like to know the best way to compare their types against each other. – Liam Rahav Jul 09 '19 at 16:53
  • 1
    But what comparison is that? Do you just want to know if one type is a subtype of another (where any type is considered a subtype of itself)? – chepner Jul 09 '19 at 17:02
  • @chepner Broadly I'd like to know if there's a good solution to see if two variables are "compatible" with one another via checking if their types are the same. I know this isn't extremely specific, but I'm looking for a general answer / explanation as to why this is or isn't possible to do simply. – Liam Rahav Jul 09 '19 at 17:06
  • 1
    Compatible in what way? I think you are putting too much emphasis on their types and not enough on their interface. – chepner Jul 09 '19 at 17:11
  • @chepner. I was thinking the exact same thing. I've posted an alternative answer, that is probably a far-fetched interpretation (but that you seem to be hinting at as well). – Mad Physicist Jul 09 '19 at 18:11
  • It's extremely unclear what you mean by "most ideal" and "best way". I am voting to close as "unclear what you are asking" because it is impossible to optimize something without knowing what you are optimizing for. – Mad Physicist Jul 09 '19 at 18:14

2 Answers2

2

This program goes through all __bases__ of provided objects and check for common intersection between them (sans object):

class A:
    def __init__(self, x):
        self.x = x

class B(A):
    def __init__(self, x):
        x += 5
        super(B, self).__init__(x)

class C(B):
    def __init__(self, x):
        self.x = x

class D:
    def __init__(self, x):
        self.x = x

class E(C, B):
    def __init__(self, x):
        self.x = x

a = A(5)
b = B(5)
c = C(5)
d = D(5)
e = E(5)

def check(*objs):
    def _all_bases(o):
        for b in o.__bases__:
            if b is not object:
                yield b
            yield from _all_bases(b)
    s = [(i.__class__, *_all_bases(i.__class__)) for i in objs]
    return len(set(*s[:1]).intersection(*s[1:])) > 0

print(check(a, b)) # True
print(check(a, c)) # True
print(check(a, d)) # False
print(check(a, e)) # True
print(check(b, c)) # True
print(check(b, d)) # False
print(check(b, e)) # True
print(check(e, d)) # False
print(check(a, b, c)) # True
print(check(a, b, c, e)) # True
print(check(a, b, c, d)) # False
print(check('string1', 'string2')) # True
Andrej Kesely
  • 168,389
  • 15
  • 48
  • 91
  • Thanks for the response! This seems like a pretty good solution. Wondering if there's anything built into Python that can achieve something similar, or if you'd need to include a function like this in any case where you want this kind of check. – Liam Rahav Jul 09 '19 at 18:03
  • There's no built in function in Python to do this. It's pretty unusual to do this kind of type-checking in Python, normally you'd just rely on duck-typing, or, if you must, you might do `isinstance(thing, A)`. – JBGreen Jul 09 '19 at 18:05
  • 1
    @LiamRahav I'm not aware of such function (I could be wrong though). Maybe you can check `inspect.getmro(cls)` function (https://docs.python.org/3/library/inspect.html#inspect.getmro) - this function lists cls’s base classes, including cls. – Andrej Kesely Jul 09 '19 at 18:07
0

Given the stated goal of assessing compatibility between descendants of A, I think you may be over-complicating the issue. When it comes to rich comparison, at least, Python already does this check for you. According to the docs:

If the operands are of different types, and right operand’s type is a direct or indirect subclass of the left operand’s type, the reflected method of the right operand has priority, otherwise the left operand’s method has priority. Virtual subclassing is not considered.

This means that all you have to do is implement operators in A. If any of the descendants need to add functionality, they should do so. Here is an example:

class A():
    def __init__(self, x):
        self.x = x

    def __eq__(self, other):
        if not isinstance(other, __class__):
            return NotImplemented
        return self.x == other.x


class B(A):
    def __init__(self, x, y):
        super(B, self).__init__(x + 5)
        self.y = y

    def __eq__(self, other):
        if isinstance(other, __class__):
             return super().__eq__(other) and self.y == other.y
        return super().__eq__(other)  # Or alternatively, NotImplemented
Mad Physicist
  • 107,652
  • 25
  • 181
  • 264