4

我想在某个类的所有实例和它的所有派生类之间共享一些信息。

class Base():
    cv = "some value"  # information I want to share

    def print_cv(self, note):
        print("{}: {}".format(note, self.cv))

    @classmethod
    def modify_cv(cls, new_value):
        # do some class-specific stuff
        cls.cv = new_value

class Derived(Base):
    pass

b = Base()
d = Derived()

b.print_cv("base")
d.print_cv("derived")

输出符合预期(两个类的实例请参见正确的类属性):

base: some value
derived: some value

我可以改变这个类属性的值,一切都很好:

# Base.cv = "new value"
b.modify_cv("new value")
b.print_cv("base")       # -> base: new value
d.print_cv("derived")    # -> derived: new value

到目前为止,一切都很好。问题是,如果我通过派生类访问,则基类和派生类之间的“连接”可能会被破坏cv

# Derived.cv = "derived-specific value"
d.modify_cv("derived-specific value")
b.print_cv("base")       # -> base: new value
d.print_cv("derived")    # -> derived: derived-specific value

这种行为是意料之中的,但这不是我想要的!

我理解为什么ab看到不同的值cv- 因为它们是不同类的实例。我cv在派生类中覆盖了值,现在派生类的行为有所不同,我已经多次使用此功能。

但是对于我目前的任务,我需要a并且b总是使用相同的cv

更新

我已经更新了这个问题,现在它更好地描述了现实生活中的情况。实际上我没有cv像这样修改值:

Base.cv = "new value"

在一些类方法中进行了修改(实际上所有这些类方法都是在Base类中实现的)。

现在解决方案变得很明显,我只需要稍微修改方法:

class Base():
    @classmethod
    def modify_cv(cls, new_value):
        #cls.cv = new_value
        Base.cv = new_value

谢谢大家的讨论和想法(一开始我打算使用 getter/setter 和模块级属性)

4

3 回答 3

1

classmethod当您需要知道哪个类正在调用该方法时很有用,但如果您想要相同的行为而不管调用该方法的类如何,您可以staticmethod改用。然后,您可以简单地通过基类的名称访问类变量Base.cv

class Base:
    cv = "some value"  # information I want to share

    def print_cv(self, note):
        print("{}: {}".format(note, self.cv))

    @staticmethod
    def modify_cv(new_value):
        Base.cv = new_value

你仍然可以在任何实例或子类上调用它,但它总是会改变Base.cv

>>> b = Base()
>>> d = Derived()
>>> Base.cv == Derived.cv == b.cv == d.cv == "some value"
True
>>> d.modify_cv("new value")
>>> Base.cv == Derived.cv == b.cv == d.cv == "new value"
True

更新:

如果由于其他原因仍需要访问该类,请像以前一样使用参数,但仍通过而不是访问基类classmethod的变量:clsBase.cvcls.cv

@classmethod
def modify_cv(cls, new_value):
    do_stuff_with(cls)
    Base.cv = new_value
于 2018-05-04T11:50:42.070 回答
0

您必须覆盖__setattr__类的类,即元类:

class InheritedClassAttributesMeta(type):
    def __setattr__(self, key, value):
        cls = None

        if not hasattr(self, key):
            # The attribute doesn't exist anywhere yet,
            # so just set it here
            cls = self
        else:
            # Find the base class that's actually storing it
            for cls in self.__mro__:
                if key in cls.__dict__:
                    break
        type.__setattr__(cls, key, value)


class Base(metaclass=InheritedClassAttributesMeta):
    cv = "some value"


class Derived(Base):
    pass


print(Derived.cv)
Derived.cv = "other value"
print(Base.cv)

使用元类通常是多余的,所以直接指定Base可能会更好。

为避免此解决方案产生不必要的副作用,请考虑key在更改行为之前先检查是否在某些预定义的属性名称集中。

于 2018-05-04T09:41:48.770 回答
0

在 Python 中,在方法内部,您可以使用裸 __class__变量名来表示定义该方法的实际类。

这与 cls传递给类方法或常规方法的 arg不同self.__class__,如果在子类中调用该方法,它将引用子类。因此,cls.attr = value将在子类 class' 上设置值,__dict__并且从那时起属性值将独立于该子类。这就是你要到达的地方。

相反,您可以使用:

class MyClass:
   cv  = "value"

   @classmethod # this is actually optional
   def modify_cv(cls, new_value):
        __class__.cv = new_value

__class__在 Python 3 中通过允许编写无参数形式的机制自动创建super

于 2018-05-04T12:17:52.363 回答