0

I'm wanting to keep a class attribute on a base class which keeps track of all names of all its subclasses.

class SomeThing(abc.ABC):
   subclass_names = set()
   def __init_subclass__(cls):
       cls.subclass_names.add(cls.__name__)
       print(cls.subclass_names)

However, I'm afraid that because I'm creating an abstract class, users might overwrite the class attribute I am keeping.

class SomeSub(SomeThing):
    subclass_names = set()

out: {'SomeSub'}

class SomeOtherSub(SomeThing):
    pass

out: {'SomeOtherThing'} # should be {'SomeSub', 'SomeOtherThing'}
# but SomeOtherThing is registered in SomeOtherThing's, not SomeThing's subclass_names

Is there a way to refer to "own class" in a class method? It seems like the cls in __init_subclass__ ends up being whichever subclass is passed in (which makes sense).

3
  • 1
    This already exists: SomeThing.__subclasses__() Commented Feb 11, 2022 at 18:52
  • 1
    "refer to own class in python class method when subclassing" well, the answer to the exact question here is just SomeThing.subclass_names. Commented Feb 11, 2022 at 18:54
  • (Though __subclasses__ only returns direct subclasses, not all subclasses of subclasses, recursively.) Commented Feb 11, 2022 at 18:55

2 Answers 2

2

Take advantage of name mangling:

class SomeThing(abc.ABC):
   __subclass_names = set()
   def __init_subclass__(cls):
       cls.__subclass_names.add(cls.__name__)
       print(cls.__subclass_names)

As the docs say:

Since there is a valid use-case for class-private members (namely to avoid name clashes of names with names defined by subclasses), there is limited support for such a mechanism, called name mangling. Any identifier of the form __spam (at least two leading underscores, at most one trailing underscore) is textually replaced with _classname__spam, where classname is the current class name with leading underscore(s) stripped.

This means that subclasses of SomeThing won't accidentally override __subclass_names, since they will actually have to override _SomeThing__subclass_names instead.

Sign up to request clarification or add additional context in comments.

Comments

0

Use a double-underscore to designate an attribute as private to a particular class. Python will "name-mangle" it behind the scenes to ensure that child classes can't accidentally override it.

import abc

class SomeThing(abc.ABC):
   __subclass_names = set()
   def __init_subclass__(cls):
       SomeThing.__subclass_names.add(cls.__name__)
       print(SomeThing.__subclass_names)

class SomeSub(SomeThing):
    __subclass_names = set()

# {'SomeSub'}

class SomeOtherThing(SomeThing):
    pass

# {'SomeOtherSub', 'SomeSub'}

2 Comments

"to ensure that child classes can't override it." -> "to prevent accidentally overriding it" because you can always do use ._SomeThing__subclass_names
I think the key here is just to use SomeThing.__subclass_names, rather than the name mangling itself. I tried this and it works with or without the __

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.