简体   繁体   中英

Python Class Name as Class Variable

I'm working as an application with classes and subclasses. For each class, both super and sub, there is a class variable called label . I would like the label variable for the super class to default to the class name. For example:

class Super():
    label = 'Super'

class Sub(Super):
    label = 'Sub'

Rather than manually type out the variable for each class, is it possible to derive the variable from the class name in the super class and have it automatically populated for the subclasses?

class Super():
    label = # Code to get class name

class Sub(Super)
    pass
    # When inherited Sub.label == 'Sub'.

The reason for this is that this will be the default behavior. I'm also hoping that if I can get the default behavior, I can override it later by specifying an alternate label .

class SecondSub(Super):
    label = 'Pie'  # Override the default of SecondSub.label == 'SecondSub'

I've tried using __name__ , but that's not working and just gives me '__main__' .

I would like to use the class variable label in @classmethod methods. So I would like to be able to reference the value without having to actually create a Super() or Sub() object, like below:

class Super():
    label = # Magic

    @classmethod
    def do_something_with_label(cls):
        print(cls.label)

you can return self.__class__.__name__ in label as a property

class Super:
    @property
    def label(self):
        return self.__class__.__name__

class Sub(Super):
   pass

print Sub().label

alternatively you could set it in the __init__ method

def __init__(self):
    self.label = self.__class__.__name__

this will obviously only work on instantiated classes

to access the class name inside of a class method you would need to just call __name__ on the cls

class XYZ:
    @classmethod
    def my_label(cls):
        return cls.__name__

print XYZ.my_label()

this solution might work too (snagged from https://stackoverflow.com/a/13624858/541038 )

class classproperty(object):
    def __init__(self, fget):
        self.fget = fget
    def __get__(self, owner_self, owner_cls):
        return self.fget(owner_cls)

class Super(object): 
    @classproperty
    def label(cls):
        return cls.__name__

class Sub(Super):
   pass

print Sub.label  #works on class
print Sub().label #also works on an instance

class Sub2(Sub):
   @classmethod
   def some_classmethod(cls):
       print cls.label

Sub2.some_classmethod()

You can use a descriptor :

class ClassNameDescriptor(object):
    def __get__(self, obj, type_):
        return type_.__name__

class Super(object):
    label = ClassNameDescriptor()

class Sub(Super):
    pass

class SecondSub(Super):
    label = 'Foo'

Demo:

>>> Super.label
'Super'
>>> Sub.label
'Sub'
>>> SecondSub.label
'Foo'
>>> Sub().label
'Sub'
>>> SecondSub().label
'Foo'

If class ThirdSub(SecondSub) should have ThirdSub.label == 'ThirdSub' instead of ThirdSub.label == 'Foo' , you can do that with a bit more work. Assigning label at the class level will be inherited, unless you use a metaclass (which is a lot more hassle than it's worth for this), but we can have the label descriptor look for a _label attribute instead:

class ClassNameDescriptor(object):
    def __get__(self, obj, type_):
        try:
            return type_.__dict__['_label']
        except KeyError:
            return type_.__name__

Demo:

>>> class SecondSub(Super):
...     _label = 'Foo'
...
>>> class ThirdSub(SecondSub):
...     pass
...
>>> SecondSub.label
'Foo'
>>> ThirdSub.label
'ThirdSub'

A metaclass might be useful here.

class Labeller(type):
    def __new__(meta, name, bases, dct):
        dct.setdefault('label', name)
        return super(Labeller, meta).__new__(meta, name, bases, dct)

# Python 2
# class Super(object):
#    __metaclass__ = Labeller

class Super(metaclass=Labeller):
    pass

class Sub(Super):
    pass

class SecondSub(Super):
    label = 'Pie'

class ThirdSub(SecondSub):
    pass

Disclaimer: when providing a custom metaclass for your class, you need to make sure it is compatible with whatever metaclass(es) are used by any class in its ancestry. Generally, this means making sure your metaclass inherits from all the other metaclasses, but it can be nontrivial to do so. In practice, metaclasses aren't so commonly used, so it's usually just a matter of subclassing type , but it's something to be aware of.

As of Python 3.6, the cleanest way to achieve this is with __init_subclass__ hook introduced in PEP 487 . It is much simpler (and easier to manage with respect to inheritance) than using a metaclass.

class Base:
    @classmethod
    def __init_subclass__(cls, **kwargs):
        super().__init_subclass__(**kwargs)
        if 'label' not in cls.__dict__:  # Check if label has been set in the class itself, i.e. not inherited from any of its superclasses
            cls.label = cls.__name__  # If not, default to class's __name__

class Sub1(Base):
    pass
    
class Sub2(Base):
    label = 'Custom'

class SubSub(Sub2):
    pass

print(Sub1.label)    # Sub1
print(Sub2.label)    # Custom
print(SubSub.label)  # SubSub

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM