【问题标题】:Python Class Name as Class VariablePython 类名作为类变量
【发布时间】:2015-08-03 14:37:13
【问题描述】:

我正在作为具有类和子类的应用程序工作。对于每个类,无论是超级类还是子类,都有一个名为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__'

我想在@classmethod 方法中使用类变量label。所以我希望能够引用该值,而不必实际创建 Super() 或 Sub() 对象,如下所示:

class Super():
    label = # Magic

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

【问题讨论】:

  • 您应该指定您需要它来处理未实例化的类。你会很快得到一个更正确的解决方案
  • 你说得对,我应该有。现在会解决这个问题。

标签: python python-3.x class-variables


【解决方案1】:

从 Python 3.6 开始,实现此目的的最简单方法是使用 PEP 487 中引入的 __init_subclass__ 钩子。它比使用元类更简单(并且在继承方面更易于管理)。

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

【讨论】:

    【解决方案2】:

    元类在这里可能有用。

    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 进行子类化的问题,但需要注意这一点。

    【讨论】:

    • 您能否解释一下为什么必须使用super(Labeller, meta) 而不仅仅是super()
    • Python 3 引入了super 无需显式参数即可使用的功能;我更习惯 Python 2,并出于习惯添加了参数。
    【解决方案3】:

    您可以在标签中将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__
    

    这显然只适用于实例化的类

    要访问类方法中的类名,您只需在 cls 上调用 __name__

    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()
    

    【讨论】:

    • 当您添加答案时,我只是跑到 ipython 尝试类中的 dir(self)。太棒了!我学到了一些新东西!
    • 我能否在@classmethod 中再次引用该实例属性?
    • @user2004245 self.__class__ 只能在类被完全定义后评估,因此您可以在运行时获取标签,但不能在类的设计时获取(所以你可以在方法的内部使用它,但不能作为类属性的值)。
    • 使用__init__ 会将其确定为实例变量,我特别需要将其作为类变量,以便可以在@classmethod 中引用它。
    • 您可能希望将其设为自定义描述符而不是属性,因此当您执行 Super.label 而不是仅处理实例时,它会正确显示。根据SecondSub 中的label = whatever 是否应由class ThirdSub(SecondSub) 继承,可能还值得将其设为数据描述符并自己检查类dict 是否被覆盖。
    【解决方案4】:

    您可以使用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'
    

    演示:

    >>> 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'
    

    【讨论】:

    • 很好的答案,你也击败了我的编辑(+1 来自我的好先生 :))
    • 这里有一定的不对称性。 SecondSub.label 是一个实际的字符串,不再是描述符。诸如class ThirdSub(SecondSub): pass 之类的类的标签是“Foo”,而不是“ThirdSub”。
    • @chepner:正在努力。
    • 不错。元类更优雅,但如果另一个元类(如ABCMeta)已在类层次结构中使用,则更易于使用。
    • PEP-487 旨在提供一种更简单的自定义类的方法,使用新的魔法方法而不是元类。一旦被接受,它将对此类问题很有用。
    猜你喜欢
    • 1970-01-01
    • 2010-09-18
    • 1970-01-01
    • 1970-01-01
    • 2017-09-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-06
    • 2016-11-29
    相关资源
    最近更新 更多