简体   繁体   English

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

[英]How to access a child class's overridden class property in a base class's method? '__class__' still point to the base class

I want to create a set of class to manage different configuration arguments for different experiments. 我想创建一组类来管理不同实验的不同配置参数。 I want to set a list of attributes as the class attribute for each class to check if the attributes given is exactly needed. 我想为每个类设置一个属性列表作为类属性,以检查是否确实需要给定的属性。

To save codes, I write a general __init__ and wish it could apply to derived classes and use the derived class's _attr_ to do the checking work. 为了保存代码,我编写了一个通用的__init__ ,希望它可以应用于派生类并使用派生类的_attr_进行检查工作。

I use __class__ to refer to the current class but it seems to point to the base class. 我使用__class__来引用当前类,但它似乎指向基类。

Here are some codes. 这是一些代码。 The __init__ function that BCDConfig inherent insists that _class__ should be the class where it is defined in -- ExpConfig. 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'))

Here is the output. 这是输出。 I wonder if there's a way other than __class__ that can be interpreted dynamically to the derived class. 我想知道是否有除__class__其他方法可以动态地解释为派生类。 Thank you for any help! 感谢您的任何帮助!

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

__class__ is only ever going to point to the class that you defined the method on. __class__只会指向您在其上定义方法的类。 It's purpose is not to change with subclasses. 目的是不随子类一起更改。

Use the type() function , (eg type(self) ), if you want to get the class of the current instance. 如果要获取当前实例的类,请使用type()函数 (例如type(self) )。 This returns self.__class__ in this case, but know that type() knows how to handle different types of objects, not just Python classes. 在这种情况下,它将返回self.__class__ ,但是知道type()知道如何处理不同类型的对象,而不仅仅是Python类。 It may be that you meant to use self.__class__ all along. 可能是您一直打算使用self.__class__

I'd not use __class__ unless you specifically want to access the class object on which a method was defined, ignoring inheritance, and then only with an explicit comment explaining why you do this. 除非您特别想访问定义了方法的类对象,而忽略继承,然后仅使用明确的注释来解释为什么这样做,否则我不会使用__class__ The __class__ closure is not widely known and not intended for general use. __class__闭包尚未广为人知,也不打算用于一般用途。

From the reference documentation on class creation : 有关类创建参考文档中

__class__ is an implicit closure reference created by the compiler if any methods in a class body refer to either __class__ or super . 如果类主体中的任何方法引用__class__super__class__是编译器创建的隐式闭包引用。 This allows the zero argument form of super() to correctly identify the class being defined based on lexical scoping, while the class or instance that was used to make the current call is identified based on the first argument passed to the method. 这允许super()的零参数形式根据词法作用域正确识别正在定义的类,而用于进行当前调用的类或实例是根据传递给该方法的第一个参数来识别的。

For the load_config classmethod, you already have a reference to the class: use cls there, not __class__ . 对于load_config类方法,您已经具有对该类的引用:在此处使用cls ,而不是__class__

Next, you don't actually need the class reference in __init__ . 接下来,您实际上不需要 __init__的类引用。 You could use self._attr_ instead; 您可以使用self._attr_代替; class attributes are accessible via the instance, provided there are no instance attributes shadowing them: 可以通过实例访问类属性,前提是没有实例属性遮盖它们:

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!"

The self._attr_ reference will find the _attr_ attribute on the correct class for the given instance: self._attr_引用将在给定实例的正确类上找到_attr_属性:

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

I'd actually make _attr_ a set object , not a list. 我实际上将_attr_ 设置为对象 ,而不是列表。 Attribute names must be unique, don't need a specific order, and sets are optimised for membership testing and intersections. 属性名称必须是唯一的,不需要特定的顺序,并且集已针对成员资格测试和交集进行了优化。 If you combine a set with dictionary views you can quickly test for missing and extraneous keys: 如果将集合与字典视图结合使用,则可以快速测试丢失和多余的键:

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)

I used a frozenset() object, an immutable set, because you probably don't want to alter the attribute names after creating your class. 我使用了frozenset()对象,这是一个不可变的集合,因为您可能不想在创建类后更改属性名称。 A frozenset() will protect you from accidental bugs that do so anyway. frozenset()可以保护您免受意外错误的影响。

Finally, subclasses can re-use definitions from a parent class with the set union operator | 最后,子类可以使用set Union运算符重用父类的定义| , so BCDConfig could be defined as: ,因此BCDConfig可以定义为:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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