3

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.

KeksamPC
  • 53
  • 1
  • 7
  • Cannot reproduce in either Python 2 or Python 3. It's also obvious this isn't your actual code, since you're missing a `)` in the definition of `a.hesyms`. – jwodder Mar 09 '17 at 20:11
  • Oh right, for formatting I tried around a little and didn't understand, but that should actually be the only difference. – KeksamPC Mar 09 '17 at 20:13
  • In general, you should use `type(foo)` instead of `foo.__class__`, (unless you have a good reason to support old-style classes, which I would say you usually don't, since they don't even exist as of Python 3, and best practice in Python 2 code was to make your classes new-style ever since new-style classes existed). – mtraceur Jun 08 '22 at 08:13
  • I'd say in Python 3 you should only do `foo.__class__` when you want `type(foo)` to be able to lie to you about what class it is (this can be good if a library like `wrapt` is wrapping the object you are handling with a proxy object, in which case `foo.__class__` actually refers to the wrapped class rather than the wrapper class - but even then, most cases where you care about the wrapped class are situations where it is equally or more appropriate to either use `isinstance` for type checks or go through the instance instead of its class for attribute access.) – mtraceur Jun 08 '22 at 08:20

1 Answers1

5

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.

Jonas Schäfer
  • 20,140
  • 5
  • 55
  • 69
  • I'm sorry, I'm inexperienced, could you explain more? – KeksamPC Mar 09 '17 at 20:10
  • 2
    You are not showing the code which produces the problem. You are not assigning ``ita`` or ``fob`` to ``itemselect`` when the error happens, because in that case it would work. You can confirm that by printing ``ita.__class__.__name__`` and ``fob.__class__.__name__``. You are, at some point, assigning ``fb`` to ``itemselect``. ``fb`` is a **class**, not an instance, and thus does not have the ``__class__`` attribute, because it is itself the class. – Jonas Schäfer Mar 09 '17 at 20:12
  • I am, I cut down a bit so the parts of the code shown aren't too confusing for people that don't know my code (which is everybody) I assign it with a list called itemsl `itemsl = [fob,ita] itemselect = itemsl[0]` – KeksamPC Mar 09 '17 at 20:15
  • Okay, thank you very much. Sorry, I'm still practicing and classes are still pretty confusing for me. Also thanks for the quick answers, works now! – KeksamPC Mar 09 '17 at 20:22