如何在基类的方法中访问子类的重写的类属性? '__class__'仍然指向基类

时间:2019-08-10 16:54:41

标签: python

我想创建一组类来管理不同实验的不同配置参数。我想为每个类设置一个属性列表作为类属性,以检查是否确实需要给定的属性。

为了保存代码,我编写了一个通用的__init__,希望它可以应用于派生类并使用派生类的_attr_进行检查。

我使用__class__来引用当前类,但它似乎指向基类。

这是一些代码。 BCDConfig固有的__init__函数坚持认为_class__应该是在-ExpConfig中定义的类。

import json

class ExpConfig:
    _attr_ = ['attr1', 'attr2']   # list of string

    def __init__(self, config):
        self.config = {}

        # only take defined attributes
        for key in config:
            if key in __class__._attr_:
                self.config[key] = config[key]
            else:
                raise ValueError

        # check if there is anything undefined
        for key in __class__._attr_:
            assert key in self.config, "Missing arguments!"

    def save_config(self, path):
        with open(path, 'w') as f:
            json.dump(self.config, f)

    @classmethod
    def load_config(cls, path):
        with open(path, 'r') as f:
            config = json.load(f)
        exp_config = __class__(config)
        return exp_config


class BCDConfig(ExpConfig):

    _attr_ = ['attr1', 'attr2', 'attr3']

    def __init__(self, config):
        super(BCDConfig, self).__init__(config)


if __name__ == '__main__':
    bcd_config1 = BCDConfig({'attr1':123, 'attr2':444})
    bcd_config1.save_config('./bcd1.cfg')
    print(BCDConfig.load_config('./bcd1.cfg').config)

    bcd_config2 = BCDConfig({'attr1':1253, 'attr2':4344, 'attr3':1})
    bcd_config2.save_config('./bcd2.cfg')
    print(BCDConfig.load_config('./bcd2.cfg'))

这是输出。我想知道是否存在__class__之外的其他方法可以动态地解释为派生类。谢谢您的帮助!

{'attr1': 123, 'attr2': 444}
Traceback (most recent call last):
  File "C:/Users/MysriO/Documents/Local Codes/DEAP_Ricotta/exp_config.py", line 46, in <module>
    bcd_config2 = BCDConfig({'attr1':1253, 'attr2':4344, 'attr3':1})
  File "C:/Users/MysriO/Documents/Local Codes/DEAP_Ricotta/exp_config.py", line 37, in __init__
    super(BCDConfig, self).__init__(config)
  File "C:/Users/MysriO/Documents/Local Codes/DEAP_Ricotta/exp_config.py", line 14, in __init__
    raise ValueError
ValueError

1 个答案:

答案 0 :(得分:4)

__class__只会指向您在其上定义方法的类。 目的是不随子类更改的。

如果要获取当前实例的类,请使用type() function(例如type(self))。在这种情况下,它返回self.__class__,但知道type()知道如何处理不同类型的对象,而不仅仅是Python类。可能是您一直打算使用self.__class__

除非您特别想访问定义了方法的类对象,而忽略继承,然后仅使用明确的注释来解释为什么这样做,否则我不会使用__class____class__闭包尚未广为人知,也不打算用于一般用途。

来自reference documentation on class creation

  

__class__是由编译器创建的隐式闭包引用,如果类主体中的任何方法引用__class__super。这允许super()的零参数形式根据词法作用域正确地标识正在定义的类,而用于进行当前调用的类或实例是根据传递给该方法的第一个参数来标识的。 / p>

对于load_config类方法,您已经有对该类的引用:在此处使用cls,而不是__class__

接下来,您实际上不需要 __init__中的类引用。您可以改为使用self._attr_;可以通过实例访问类属性,条件是没有实例属性可以遮盖它们:

class ExpConfig:
    _attr_ = ['attr1', 'attr2']   # list of string

    def __init__(self, config):
        self.config = {}

        # only take defined attributes
        for key in config:
            if key in self._attr_:
                self.config[key] = config[key]
            else:
                raise ValueError

        # check if there is anything undefined
        for key in self._attr_:
            assert key in self.config, "Missing arguments!"

self._attr_引用将在给定实例的正确类上找到_attr_属性:

>>> class ExpConfig:
...     _attr_ = ['attr1', 'attr2']
...
>>> class BCDConfig(ExpConfig):
...     _attr_ = ['attr1', 'attr2', 'attr3']
...
>>> ExpConfig()._attr_
['attr1', 'attr2']
>>> BCDConfig()._attr_
['attr1', 'attr2', 'attr3']

我实际上会将_attr_设为set object,而不是列表。属性名称必须是唯一的,不需要特定的顺序,并且集已针对成员资格测试和交集进行了优化。如果将集合与dictionary views结合使用,则可以快速测试丢失和无关的密钥:

class ExpConfig:
    _attr_ = frozenset(('attr1', 'attr2'))   # immutable set of strings

    def __init__(self, config):
        extra = config.keys() - self.attrs
        if extra:
            raise ValueError(f"Invalid config keys: {', '.join(extra)}")
        missing = self.attrs - config.keys()
        if missing:
            raise ValueError(f"Missing config keys: {', '.join(missing)}")

        self.config = config.copy()

    def save_config(self, path):
        with open(path, 'w') as f:
            json.dump(self.config, f)

    @classmethod
    def load_config(cls, path):
        with open(path, 'r') as f:
            config = json.load(f)
        return cls(config)

我使用了frozenset()对象,这是一个不可变的集合,因为创建类后您可能不想更改属性名称。 frozenset()可以保护您免受意外错误的侵害。

最后,子类可以使用set union operator |重用父类的定义,因此BCDConfig可以定义为:

class BCDConfig(ExpConfig):
    _attr_ = ExpConfig._attr_ | frozenset(('attr3',))