1

I've got a main "abstract" class Pet, and two "real" classes Dog and Cat. When I've got two instances of pets, I want to know if they are the "same kind of pets" without taking care of what kind of pets they are.

I tried this

#!/usr/bin/python

class Pet:
    name = "pet"

    def __eq__(self, other):
        return type(self) == type(other)
#        return type(self).__name__ == type(other).__name__
#        return self.__class__ == other.__class__
#        return self.__class__.__name__ == other.__class__.__name__
#        return self.__class__ is other.__class__
#        return self.__class__.__name__ is other.__class__.__name__
#        return isinstance(other, type(self).__name__)
#        return isinstance(other, type(self))
#        return isinstance(self, type(other).__name__)
#        return isinstance(self, type(other))
#        return type(self) is type(other)
#        return type(self).__name__ is type(other).__name__

class Dog:
    name = "dog"

class Cat:
    name = "cat"

dog1 = Dog()
dog2 = Dog()
cat1 = Cat()
cat2 = Cat()

print("dog1 == dog2: {0}".format(dog1 == dog2))
print("cat1 == cat2: {0}".format(cat1 == cat2))
print("cat1 == dog1: {0}".format(cat1 == dog1))

I tried all the commented return, and it always gives this result:

$ ./test.py 
dog1 == dog2: False
cat1 == cat2: False
cat1 == dog1: False

I guess it's because it consider the first operand as a Pet because the method is in this class.

Is there a way to such a test in the main class to avoid code duplication?

EDIT:
As multiple people mentioned it forgot the subclassing part...
This works as intended

#!/usr/bin/python

class Pet:
    name = "pet"

    def __eq__(self, other):
        return self.__class__ == other.__class__

class Dog(Pet):
    name = "dog"

class Cat(Pet):
    name = "cat"
Phantom
  • 833
  • 1
  • 9
  • 26

3 Answers3

1

One way is to use the name parameter defined in the code above. Additionally, Dog & Cat need to inherit Pet so they can make use of the eq function defined.

#!/usr/bin/python

class Pet:
    name = "pet"

    def __eq__(self, other):
        return self.name == other.name


class Dog(Pet):
    name = "dog"

class Cat(Pet):
    name = "cat"

dog1 = Dog()
dog2 = Dog()
cat1 = Cat()
cat2 = Cat()

print("dog1 == dog2: {0}".format(dog1 == dog2))
print("cat1 == cat2: {0}".format(cat1 == cat2))
print("cat1 == dog1: {0}".format(cat1 == dog1))
lane
  • 766
  • 5
  • 20
1

There are 3 approaches that you can take here, each leading to subtle differences in behavior, in particular with respect to any future subclasses you might create.

In all 3 cases, the overall code should look like this. I've taken the liberty of adding a class to illustrate some interesting points :

from abc import ABC, abstractmethod


class Pet(ABC):
    name: str
    def __eq__(self, other):
        ???

    @abstractmethod
    def __init__(self):
        pass


class Dog(Pet):
    name = "dog"
    def __init__(self):
        pass


class BadDog(Dog):
    def __init__(self):
        pass


class Cat(Pet):
    name = "cat"
    def __init__(self):
        pass


class UglyKitty(Cat):
    name = "ugly-kitty"
    def __init__(self):
        pass

Now let's look at the 3 things we can put in place of ???


return type(self) is type(other)

This means that the 2 objects will evaluate as equal if and only if they are the exact same type. In this scenario, an UglyKitty is not regarded as a Cat. Likewise for BadDog and Dog.


return isinstance(self, other.__class__) or isinstance(other, self.__class__)

This means that the 2 objects will evaluate as equal if one is the same class or a subclass of the other. In other words, in this case, an UglyKitty is considered equal to a Cat, but it is not equal to a Dog. Likewise, a BadDog is a Dog but it is not a Cat. On a related note, the judicious use of ABC and abstractmethod shown above will ensure that you will never directly instantiate a Pet that would qualify as equal to any other animal.


return self.name == other.name

In this scenario there is no guaranteed and universal behavior. You now have fine control over what is equal to what, at the cost of having to manually select these equalities. This is achieved by choosing the name attribute that matches what you want. In our case, a BadDog is equal to a Dog (as we have not changed its name attribute and so it inherits it from Dog) but an UglyKitty is not a Cat.


As you can see, different solutions lead to different behavior. Now all you need to do is decide which behavior you actually want.

ticster
  • 744
  • 2
  • 5
  • 19
0

You can use class.__name__ for this:

dog1__name__ == dog2.__name__

Update

As @Tbaki says, you can use type() as well:

type(dog1) == type(dog2)

Another Update

This is better:

type(dog1) is type(dog2) # notice that I use 'is'