Python name of class in class body
Asked Answered
P

7

12

Is it possible to get the class name within the body of a class definition?

For example,

class Foo():
    x = magic() # x should now be 'Foo'

I know that I can do this statically outside of the class body using a class method:

class Bar():
    @classmethod
    def magic(cls):
        print cls.__name__

Bar.magic()

However this isn't what I want, I want the class name in the class body

Piles answered 15/7, 2011 at 17:27 Comment(6)
That's not possible to do in Python 2, but the __prepare__ method of metaclasses in Python 3 allows you to add the name to the class dictionary before the body of the class starts executing (from where you can then use it)Abacist
Could you describe how do you want to use it? It seems a bit pointless in itself since you can write Foo.__name__ instead of magic(). Within some context, there might be a better solution.Leathern
@Rosh Oxymoron, I think it is possible, it may not be elegant or performant or what you want, but as long as you can modify the AST at runtime, you can do whatever you want...Raynor
@Rosh Oxymoron: See my proposed solution below ;)Raynor
Why do you want the class name there? Not that it isn't a valid question, I'm curious why this would be important since you could always set it at init().Principality
@phkahler: I have many subclasses which I want to statically (at import time) execute some initialization methods to setup some static variables from the super class which need the subclass name. I don't want the code duplication of giving the subclass name as input to these methods. I need this setup to be finished before I have class instances. Also, I don't want to use a @classmethod for the initialization, because then I have to call the class method somewhere.Piles
L
8

Ok - got one more solution - this one is actually not that complex!

import traceback

def magic():
   return traceback.extract_stack()[-2][2]

class Something(object):
   print magic()

It will print out "Something". I'm not sure if extracted stack format is standardised in any way, but it works for python 2.6 (and 2.7 and 3.1)

Leathern answered 15/7, 2011 at 20:26 Comment(2)
@Raynor I prefer the dynamic, stack inspecting, text parsing metaclass :) at least it's wicked and noone will try to use it. Mine looks dangerously close to "usable".Leathern
I could add this as an advantage to my solution... at least there is one.Raynor
A
4

AFAIK, the class object is not available until the class definition has been "executed", so it's not possible to get it during class definition.

If you need the class name for later use but don't use it during class definition (e.g. to compute other field names, or some such thing), then you can still automate the process using a class decorator.

def classname ( field ):
    def decorator ( klass ):
        setattr(klass, field, klass.__name__)
        return klass
    return decorator

(Caveat: not tested.)

With this definition, you can get something like:

@classname(field='x')
class Foo:
    pass

and you would get field x with the class name in it, as in:

print Foo.x
Akira answered 15/7, 2011 at 17:34 Comment(2)
Hmm, thanks for this, it isn't quite what I wanted, because I can always get the class name later with name, but knowing about class decorators could perhaps solve my problem.Piles
@drewrobb: perhaps you can ask a question about the design itself to get alternate solutions.Bannasch
R
2

Here you have a working solution for your specific case, but beware (I wrote it mainly to demonstrate that it IS indeed possible to do something like this):

  • You shouldn't use it
  • It is very specific
  • It has many limitations
  • I was just having fun with this
  • It is black magic
  • It may not work for your use case
  • It is not threadsafe
  • Do I have already said that you shouldn't use it?

Anyway, here you have the code:

import inspect

def NameAwareClassType():
    frameInfo = inspect.getouterframes(inspect.currentframe())[1]
    codeContext = frameInfo[4][0]
    className = codeContext.split(' ', 1)[1].split('(', 1)[0]

    class ClassNameGlobalRemoverType(type):
        def __new__(mcs, name, bases, dict):
            if name == className:
                del globals()['__clsname__']
            return type.__new__(mcs, name, bases, dict)

    class NameAwareClass(object):
        __metaclass__ = ClassNameGlobalRemoverType

    globals()['__clsname__'] = className

    return NameAwareClass

class A(NameAwareClassType()):

    print __clsname__

    def __init__(self):
        pass

print __clsname__

Edit: https://gist.github.com/1085475 — there you have a version which allows to use __clsname__ during method execution; makes not much sense, as self.__class__.__name__ is a better approach and the __clsname__ variable does not hold a string anymore (I'm having fun experimenting with this)

Raynor answered 15/7, 2011 at 19:55 Comment(1)
Thanks @viraport, added it... ;) I'm wondering if it is possible to overcome this... maybe by locking inside the NameAwareClassType function and unlocking in the metaclass __new__ method... even more evil, locking threads to create classes :DRaynor
S
1

I don't know of an elegant way to do this in Python 2.x -- but it's an interpreted language which means that something relatively simple along the following lines will do what you want and would be safe if you're sure of the code being executed:

classdef = """\
class %(classname)s(object):
    x = '%(classname)s'
    print x
"""
exec classdef % {'classname': 'Foo'}

foo = Foo()
print foo
Sillsby answered 15/7, 2011 at 19:31 Comment(0)
H
0

Now it could be a matter of __qualname__

class AClass:

    def __init__(self):
        print(f"Initializing an object of class {AClass.__qualname__}")

    def __repr__(self):
        return f"{AClass.__qualname__}()"

With that definition:

>>> ac = AClass()
Initializing an object of class 'AClass'
>>> ac
Body of AClass.fun

__qualname__ can be applied to methods, also. If in the body of a class a method is defined (or inner class), it is possible to get its fully qualified name, e.g.

# ...
def fun(self):
    print(f"Body of {AClass.fun.__qualname__}")

Then, after the instantiation of an object:

ac = AClass()

ac.fun() gives: Body of AClass.fun.

Helfand answered 10/4, 2024 at 8:32 Comment(0)
B
0

Since python 3.3 __qualname__ is one of the two local variables that actually available in class body and resolves this question without introducing any hacks. Though note that it wasn't made exactly for this and there are side effects if your class is defined inside function/class.

class Foo:
    x = __qualname__

print(Foo.x) # Foo


def side_effects():
    class ClassInsideFunction:
        x = __qualname__

        class ClassInsideClassInsideFunction:
            x = __qualname__

        # test.<locals>.ClassInsideFunction.ClassInsideClassInsideFunction
        print(ClassInsideClassInsideFunction.x)

    # test.<locals>.ClassInsideFunction
    print(ClassInsideFunction.x)
side_effects()
Butanone answered 27/5, 2024 at 9:46 Comment(0)
P
-2
class Bar():

    @classmethod
    def magic(cls):
        return cls.__name__

    @property
    def x(self):
        return self.magic()

    def y(self):
        return self.x

>>> a = Bar()
>>> a.x
'Bar'
>>> a.y()
'Bar'

This way you can use x as an attribute, at least within any instance and static methods. In class methods, you can just get the class name from the cls attribute anyway.

Platypus answered 15/7, 2011 at 17:37 Comment(3)
That isn't really an answer to my question, I want to get the class name from within the class definitionPiles
@drewrobb: And do what with it? Any code being executed would have to be called from a method of the class, in which case you can get the class name from either cls.__name__ or self.__class__.__name__, depending on the type of method. You could also make a metaclass for it, if you're trying to do something really really weird.Platypus
Metaclasses in Python 2 still kick in after the execution of the body, so the body of the class would not know the name of the class.Abacist

© 2022 - 2025 — McMap. All rights reserved.