1

Here is the code:

class Animal:
    def __init__(self, animal_type):
        self.animal_type = animal_type

class Cat(Animal):
    def __init__(self, animal_type, favorite_food):
        super().__init__(animal_type)
        self.favorite_food = favorite_food

    def cat_print(self):
        print("{}'s favorite food is {}.".format(self.animal_type, self.favorite_food))

    def __getattribute__(self, item):
        print('__getattribute__() be called. Item is: ', item)

    def __getattr__(self, item):
        print('__getattr__() be called. Item is: ', item)

    def __setattr__(self, key, value):
        print('__setattr__() be called. key and Value is: ', key, value)


cat = Cat('cat', 'fish')
print(cat.animal_type)
print(cat.favorite_food)

When I print the cat.animal_type, it print None. I guess that because of I rewrite method: __setattr__() and __getattribute__(), The value can't pass into the attribute.

I want to know what is the process of assigning attribute and get the attribute in a class in python?

Thanks.

xin chen
  • 33
  • 3
  • 1
    Your `__getattr__` implicitly returns `None`. – Klaus D. Aug 08 '18 at 06:38
  • 2
    Anyway, yes, you’re right: the `__setattr__` override means an attribute never gets set and the `__getattribute__` override means an attribute that existed would never be read. Which part are you confused about? – Ry- Aug 08 '18 at 06:39

2 Answers2

2

The reason all of your attributes are None, even non-existent attributes like favorite, is this:

def __getattribute__(self, item):
    print('__getattribute__() be called. Item is: ', item)

You're overriding the normal object.__getattribute__ code with a method that always returns None. If you want it to print something and also do the normal thing, you have to do it explicitly, using super—the same way you already did in your initializer:

def __getattribute__(self, item):
    print('__getattribute__() be called. Item is: ', item)
    return super().__getattribute__(item)

If you fix that, lookup for attributes that exist will now work, but non-existent attributes are still going to return None instead of raising an AttributeError. Why?

def __getattr__(self, item):
    print('__getattr__() be called. Item is: ', item)

The normal object.__getattribute__ (that you're now properly calling) looks for an attribute in the object's __dict__, and in its class and all of its class's ancestors (with a bit of extra complexity related to descriptors that I'm going to ignore here), and then, if nothing is found (or something is found but fetching it raises AttributeError), it calls the class's __getattr__. And you provide a __getattr__ that returns None.

Here, you again want to delegate to the superclasses:

def __getattr__(self, item):
    print('__getattr__() be called. Item is: ', item)
    return super().__getattr__(item)

Except that there is no default implementation of __getattr__. So, you'll get an AttributeError, but it'll be about a super not having a __getattr__, not about a Cat not having a favorite.

If you know one of your base classes wants to provide a __getattr__ for you to delegate to, super to it. But if you know nobody does:

def __getattr__(self, item):
    print('__getattr__() be called. Item is: ', item)
    raise AttributeError(item)

And if you have no idea (because, say, your class was designed to be used as a mixin with someone else's class hierarchy), you probably want something like this:

def __getattr__(self, item):
    print('__getattr__() be called. Item is: ', item)
    try:
        ga = super().__getattr__
    except AttributeError:
        pass
    else:
        return ga(item)
    raise AttributeError(item)

If you fix both of those, now you'll get an AttributeError on the attributes you did set. Why?

def __setattr__(self, key, value):
    print('__setattr__() be called. key and Value is: ', key, value)

Same problem. Here, you might want to set the attribute explicitly, like this:

def __setattr__(self, key, value):
    print('__setattr__() be called. key and Value is: ', key, value)
    super().__getattribute__('__dict__')[key] = value

… or you might just want to delegate again:

def __setattr__(self, key, value):
    print('__setattr__() be called. key and Value is: ', key, value)
    super().__setattr__(key, value)
abarnert
  • 354,177
  • 51
  • 601
  • 671
  • If the `Cat` has not a super class, the method isn't need the `super()` statement, but use the `self.` statement, am I right? – xin chen Aug 08 '18 at 07:52
  • @xinchen No. if you just call `self.__getattribute__(item)`, you’re calling the exact same method, which will call the exact same method, which will… etc. 1000 times until you get a `RecursionError`. Also. `Cat` _always_ has a superclass—if nothing else, it’s `object`. And it’s the `object.__getattribute__` code that you ultimately want to be called here (unless some other intervening class wants to change that). So you definitely want `super`. – abarnert Aug 08 '18 at 07:57
  • This is very helpful for me. And another question is what mean with `ga = super().__getattr` at second `__getattr__()` method? Is it `ga = super().__getattribute__(item)`? – xin chen Aug 08 '18 at 08:17
  • @xinchen No. (It _is_ basically the same as `super().__getattribute__('__getattr__')`, but there's no reason to call it that way.) We're basically doing the same thing as `return super().__getattr__(item)`, except that there might be no `__getattr__` method on the superclass, and in that case, we want to make sure the caller gets an `AttributeError` about the `Cat` object not having an `item`, not an `AttributeError` about the `super` object not having a `__getattr__` (as explained in the answer). – abarnert Aug 08 '18 at 15:48
-3

Add this in init in class Cat:

self.animal_type = animal_type

And modify your print

print(cat.favorite)

to

print(cat.favorite_food)

Then analyze and try to understand your mistake :)

ZRTSIM
  • 75
  • 6
  • 1
    No, the whole point of using `super` to call `__init__` is to not have to copy and paste the behavior of the base class's initializer. – abarnert Aug 08 '18 at 06:46