10

I have a number of classes with code like this. Each __init__ starts a thread and a logger with the name of the class. How do I get the name of the current class in its own definition, as a string, inside __init__? Note that self may not be an instance of the current class, so the following is not quite foolproof.

from threading import Thread
import logging

def myClassName(myclass):
    myclass._class_name = myclass.__name__
    return myclass

@myClassName
class SomeClass(object):
    def __init__(self):
        class_name = type(self)._class_name
        print "My class name in __init__ is", class_name
        self.thread = Thread(name=class_name)
        self.logger = logging.getLogger(class_name)

Update:

To clarify:

  • I want the name of the class being defined, not the class of the object passed in.
  • I don't want to hard code the name of the class.
  • I want to make it easy to copy/paste an example from one script to
    another, and the fewer mentions of the unique class name, the better. (Inheritance isn't really efficient, as there are enough custom differences to make it awkward. But accidentally leaving in the name of the wrong class is a hard bug to find.)
Quantum Mechanic
  • 625
  • 1
  • 6
  • 20
  • 7
    In what case would `self` of the `__init__` method *not* be an instance of the current class? – Cory Kramer Jun 22 '17 at 17:23
  • Possible duplicate of https://stackoverflow.com/questions/510972/getting-the-class-name-of-an-instance-in-python – Robᵩ Jun 22 '17 at 17:24
  • @CoryKramer for example, you defined `def __init__(potato, self='haha!'): ...` – wim Jun 22 '17 at 17:26
  • 3
    @wim I think it's fair to assume that `self` is shorthand for "first argument to the method" – juanpa.arrivillaga Jun 22 '17 at 17:27
  • Well, I agree. But I couldn't think of any other example. – wim Jun 22 '17 at 17:28
  • @wim Call it whatever you want, but the first argument to a class method (unless declared static or class method) will be a class instance. – Christian Dean Jun 22 '17 at 17:28
  • 1
    So, given your comment in Rob's answer, what is the problem with just *hard-coding* the name in the `__init__`? – juanpa.arrivillaga Jun 22 '17 at 17:39
  • To clarify, (1) I want the name of the class being defined, not the class of the object passed in; (2) I don't want to hard code the name of the class - I want to make it easy to copy/paste a template to another script, and the fewer mentions of the unique class name, the better. – Quantum Mechanic Jun 23 '17 at 08:42
  • If there were only a pronoun like Perl's `__package__`... – Quantum Mechanic Jun 23 '17 at 08:42
  • It's been almost a year now since I asked this question. Since then my shop has moved on to Python 3, definitively. So the answer is `__class__`, and the name is `__class__.__name__`. Phew! – Quantum Mechanic May 04 '18 at 09:08

2 Answers2

9

You can retrieve the name of the class of an an object thus:

obj.__class__.__name__

Example:

class SomeClass(object):
    def __init__(self):
        print("I am a %s"%self.__class__.__name__)

class Derived(SomeClass):
    pass

x = SomeClass()
y = Derived()

Result:

$ python x.py
I am a SomeClass
I am a Derived
Robᵩ
  • 163,533
  • 20
  • 239
  • 308
  • Is your point that `self` will always refer to an instance of the current class being executed? – Christian Dean Jun 22 '17 at 17:30
  • No. In fact I demonstrate the case where `self` is *not* an instance of the current class, but rather an instance of a derived class. – Robᵩ Jun 22 '17 at 17:31
  • Well that is what I mean actually when I said "current class". Yes I did word that some oddly. – Christian Dean Jun 22 '17 at 17:32
  • Yes. But this fails, because I want the `SomeClass` name in both cases. That is, I want the name of the class where the code is defined, not the class of the instance passed in. – Quantum Mechanic Jun 22 '17 at 17:37
  • 3
    @QuantumMechanic you need to disambiguate "the current class being executed" in your actual question, or else people are going to be interpreting it in various different ways. – juanpa.arrivillaga Jun 22 '17 at 17:38
  • Note that there are actually some real world use-cases where we rebind `__class__` dynamically. Whether the name should also change in this case, is not clear from the question. – wim Jun 22 '17 at 17:48
  • @QuantumMechanic - I took your question completely opposite to your intent. – Robᵩ Jun 22 '17 at 17:57
  • @juanpa.arrivillaga, Thanks, noted. – Quantum Mechanic Jun 23 '17 at 08:38
9

In Python 3 this is pretty straight forward, we can use the __class__ cell variable to get the current class.

In Python 2 we can achieve something similar by injecting class's name in functions globals scope using a metaclass and later cleaning it up.

from functools import wraps
from types import FunctionType


def decorate(func, class_name):
    @wraps(func)
    def wrapper(*args, **kwargs):
        sentinel = object()
        actual_value = func.__globals__.get('__class__', sentinel)
        func.__globals__['__class__'] = class_name
        try:
            result = func(*args, **kwargs)
        finally:
            if actual_value is sentinel:
                del func.__globals__['__class__']
            else:
                func.__globals__['__class__'] = actual_value
        return result
    return wrapper


class Meta(type):
    def __new__(cls, name, bases, attrs):
        for k, v in attrs.items():
            if isinstance(v, FunctionType):
                attrs[k] = decorate(v, name)
        return type.__new__(cls, name, bases, attrs)


class A:
    __metaclass__ = Meta
    def func(self):
        print(__class__)
        print('Inside A')


class B(A):
    def func(self):
        print(__class__)
        print('Inside B')
        super(B, self).func()


B().func()

Output:

B
Inside B
A
Inside A

To get the __class__ variable as the class object itself we can make few changes:

def decorate(func, cls):
    @wraps(func)
    def wrapper(*args, **kwargs):
        sentinel = object()
        actual_value = func.__globals__.get('__class__', sentinel)
        func.__globals__['__class__'] = cls
        try:
            result = func(*args, **kwargs)
        finally:
            if actual_value is sentinel:
                del func.__globals__['__class__']
            else:
                func.__globals__['__class__'] = actual_value
        return result
    return wrapper


class Meta(type):
    def __new__(cls, name, bases, attrs):
        cls = type.__new__(cls, name, bases, attrs)
        for k, v in attrs.items():
            if isinstance(v, FunctionType):
                setattr(cls, k, decorate(v, cls))
        return cls

Now output would be:

<class '__main__.B'>
Inside B
<class '__main__.A'>
Inside A
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504