Search code examples
pythonclassattributeerror

Python .__class__.__name__ works for one class and doesn't for another


I'm sorry if I come across as incredibly stupid, I'm relatively new to python and I can not figure out what I am doing wrong here.

I have two classes, and I try to get the class names of both with

*class*.__class__.__name__

Now, as I said, this works for one class, but doesn't for another.

These are my classes:

class fb():
    val = 300
    mult = 2.0
    shsym = pygame.image.load('img/dummy2.png')
    value = 50
class a():
    occ = 0
    shsym = pygame.image.load('img/it/a/shsym.png')
    plsym = pygame.image.load('img/it/a/plsym.png')
    value = 100
    hesyms = [pygame.image.load('img/a/hesym0',pygame.image.load('img/dummy.png'))]
    coord = [(50, 300),(30, 435),(310, 350)]

The variables inside probably don't really matter but as I can't figure out the problem I just included the whole thing.

Then I define them

fob = fb()
ita = a()

Then I set a variable as one of the defined classes

itemselect = fob

And then, finally, I try to check the class' name and look if it begins with 'f' (to see if it is part of a group of items)

if not itemselect.__class__.__name__.startswith("f"):

And in that line I get the error message

Traceback (most recent call last):
  File "D:\Programmieren\Cola Atsume\main.py", line 283, in <module>
     if not itemselect.__class__.__name__.startswith("f"):
AttributeError: class fa has no attribute '__class__'

When itemselect is ita everything works just fine, but with fob it doesn't. I know I could do this differently, and my whole class system isn't really conventional and all but I don't really want to change it if I don't have to.


Solution

  • Note: This is grindy Python 2 stuff. In Python 3, things are different because in Python 3, every class is "new style" by default. New-Style classes themselves are also "simply" instances of their meta-class (often this meta-class is type). This means that a class in Python 3 (and a Python 2 class inheriting from object) in fact has a __class__ attribute (which carries its meta-class). Do not worry about meta-classes now.

    When the error happens, itemselect is a class, not an instance, which is why it doesn’t have a __class__ attribute.

    You say that:

    Then I set a variable as one of the defined classes

    Exactly, and classes do not have a __class__ attribute. Only instances (also called objects) of classes have that attribute:

    >>> class Foo:
    ...     pass
    ... 
    >>> hasattr(Foo, "__class__")
    False
    >>> f = Foo()
    >>> hasattr(f, "__class__")
    True
    >>> f.__class__ is Foo
    True
    

    You need to distinguish classes and objects strongly, because in most cases it is the wrong thing to do to mix those.


    Also, you really, really, really should not be doing this. You can easily convert this to an issubclass-based check.

    Create an empty class called f:

    class f:
        pass
    

    Now base all your classes whose name starts with f on that class:

    class fb(f):
        val = 300
        mult = 2.0
        shsym = pygame.image.load('img/dummy2.png')
        value = 50
    

    And now, instead of doing the nasty .startswith check, use issubclass:

    if issubclass(itemselect, f):
    

    That is already a lot cleaner than checking for the first character in the name of a class.


    Also, as a beginner nowadays, you really really really should not be using Python 2 and now you don’t have an excuse to use old-style classes either, because you know new-style exists. It will make porting your code and your mental model of how Python works to Python 3 easier.