Имя класса Python как переменная класса

Я работаю как приложение с классами и подклассами. Для каждого класса, супер и суб, есть переменная класса с именем label. Я хотел бы, чтобы переменная label для суперкласса по умолчанию использовала имя класса. Например:

class Super():
    label = 'Super'

class Sub(Super):
    label = 'Sub'

Вместо того, чтобы вручную вводить переменную для каждого класса, можно ли получить переменную из имени класса в суперклассе и автоматически заполнить ее для подклассов?

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

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

Причина этого в том, что это будет поведение по умолчанию. Я также надеюсь, что если я смогу получить поведение по умолчанию, я смогу переопределить его позже, указав альтернативный label.

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

Я пытался использовать __name__, но это не работает и дает мне только '__main__'.

Я хотел бы использовать переменную класса label в методах @classmethod. Поэтому я хотел бы иметь возможность ссылаться на значение без фактического создания объекта Super() или Sub(), как показано ниже:

class Super():
    label = # Magic

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

person user2004245    schedule 21.05.2015    source источник
comment
вы должны были указать, что вам это нужно для работы с классами без экземпляров. вы бы быстрее получили более правильное решение   -  person Joran Beasley    schedule 21.05.2015
comment
Вы правы, я должен был. Сейчас исправлю.   -  person user2004245    schedule 21.05.2015


Ответы (4)


Здесь может быть полезен метакласс.

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

Отказ от ответственности: при предоставлении пользовательского метакласса для вашего класса вы должны убедиться, что он совместим с любым метаклассом (ами), используемыми любым классом в его родословной. Как правило, это означает, что ваш метакласс наследуется от всех других метаклассов, но это может быть нетривиально. На практике метаклассы используются не так часто, поэтому обычно это просто вопрос создания подкласса type, но об этом следует знать.

person chepner    schedule 21.05.2015
comment
Не могли бы вы указать какое-то объяснение того, почему нужно использовать super(Labeller, meta), а не просто super()? - person mkrieger1; 11.01.2017
comment
В Python 3 появилась возможность использовать super без явных аргументов; Я больше привык к Python 2 и добавил аргументы по привычке. - person chepner; 11.01.2017

вы можете вернуть self.__class__.__name__ в метку как свойство

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

class Sub(Super):
   pass

print Sub().label

в качестве альтернативы вы можете установить его в методе __init__

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

это, очевидно, будет работать только с экземплярами классов

чтобы получить доступ к имени класса внутри метода класса, вам нужно просто вызвать __name__ на cls

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

print XYZ.my_label()

это решение тоже может работать (взято с 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()
person Joran Beasley    schedule 21.05.2015
comment
Я просто бегал к ipython, чтобы попробовать dir (self) внутри класса, когда вы добавили ответ. Браво! Я узнал кое-что новое! - person nivix zixer; 21.05.2015
comment
Смогу ли я снова ссылаться на это свойство экземпляра внутри @classmethod? - person user2004245; 21.05.2015
comment
@user2004245 self.__class__ можно оценить только после определения класса, поэтому вы можете получить метку во время выполнения, но не во время разработки класса (поэтому вы можете использовать ее внутри метода, а не как значение атрибута класса). - person poke; 21.05.2015
comment
Использование __init__ установит, что в качестве переменной экземпляра она мне особенно нужна как переменная класса, чтобы на нее можно было ссылаться в @classmethod. - person user2004245; 21.05.2015
comment
Вы, вероятно, захотите сделать это настраиваемым дескриптором вместо свойства, чтобы он правильно отображался, когда вы выполняете Super.label, а не просто работаете с экземплярами. В зависимости от того, должен ли label = whatever в SecondSub быть унаследован class ThirdSub(SecondSub), возможно, также стоит сделать его дескриптором данных и самостоятельно проверить класс dict на наличие переопределений. - person user2357112 supports Monica; 21.05.2015
comment
вы никогда не указывали, что вам это нужно в методе @class (не знаю, зачем вам это нужно в методе @class... поскольку вы можете просто получить доступ к cls.__name__ - person Joran Beasley; 21.05.2015
comment
добавлено решение, которое делает его доступным на уровне classmethod - person Joran Beasley; 21.05.2015
comment
Не обращайте внимания на дескриптор данных - я забыл, что они переопределяют только словарь экземпляра, а не словарь подкласса. - person user2357112 supports Monica; 21.05.2015

Вы можете использовать дескриптор:

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'

Демо:

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

Если class ThirdSub(SecondSub) должно иметь ThirdSub.label == 'ThirdSub' вместо ThirdSub.label == 'Foo', вы можете сделать это, немного поработав. Назначение label на уровне класса будет унаследовано, если только вы не используете метакласс (что намного больше хлопот, чем оно того стоит), но вместо этого мы можем заставить дескриптор label искать атрибут _label:

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

Демо:

>>> class SecondSub(Super):
...     _label = 'Foo'
...
>>> class ThirdSub(SecondSub):
...     pass
...
>>> SecondSub.label
'Foo'
>>> ThirdSub.label
'ThirdSub'
person user2357112 supports Monica    schedule 21.05.2015
comment
отличный ответ, и вы превзошли мое редактирование, которое также делает это (+1 от меня, добрый сэр :)) - person Joran Beasley; 21.05.2015
comment
Хотя здесь есть определенная асимметрия. SecondSub.label — это настоящая строка, а не дескриптор. Такой класс, как class ThirdSub(SecondSub): pass, будет иметь метку Foo, а не ThirdSub. - person chepner; 21.05.2015
comment
@chepner: работаю над этим. - person user2357112 supports Monica; 21.05.2015
comment
Хороший. Метакласс более элегантен, но его проще использовать, если другой метакласс (например, ABCMeta) уже используется в иерархии классов. - person chepner; 21.05.2015
comment
@chepner: Это то, что заставляет меня считать метакласс более хлопотным, чем оно того стоит. Два несвязанных дескриптора отлично сочетаются друг с другом; два несвязанных метакласса производят TypeError. Если кто-то хочет смешать collections.Sequence или что-то подобное в подклассе, я бы предпочел позволить им. - person user2357112 supports Monica; 21.05.2015
comment
PEP-487 направлен на предоставление более простого способа настройки классов с использованием нового магического метода, а не метакласса. После принятия это будет полезно для такого типа проблем. - person chepner; 21.05.2015