简体   繁体   English

Python中的描述符类设计(带有继承)

[英]Descriptor class design in Python (with inheritance)

I'm trying to design a descriptor class which I can use through other class which is a subclass of a class which is a subclass of a class. 我正在尝试设计一个描述符类,我可以通过其他类使用该描述符类,该其他类是一个类的子类,而该类是一个类的子类。

class MyDescriptorClass(object):
    def __init__(self, owner, name, activates = 0):
        self.value = None
        self.name = name
        self.owner = owner
        self.activates = 0
        self.connects = []

    def __set__(self, obj, val):
        self.set(val)
    def __get__(self, instance, owner):
        return self.value

    def set(self, value):
        if self.value  == value:
            return 0
        self.value = value
        if self.activates:
            self.owner.evaluate()

    def connect(self, inputs):
        if not isinstance(inputs, list): 
            inputs = list(inputs)
        for input in inputs: 
            self.connects.append(input)

class ParentClass(object):
    def __init__(self, name):
        self.states = {}
        self.name = name
        self.A = MyDescriptorClass(self, name, activates = 1)    
        self.B = MyDescriptorClass(self, name, activates = 1)
        self.states.setDefault('A', self.A)
        self.states.setDefault('B', self.B)

class ChildClass1(ParentClass):
    def __init__(self, name)
        super(ChildClass1, self).__init__(name)
        self.ans = None
    def evaluate(self):
        self.ans = self.A.value + self.B.value

class ChildClass2(ParentClass):
    def __init__(self, name)
        super(ChildClass1, self).__init__(name)
        self.ans = None
    def evaluate(self):
        self.ans = self.A.value * self.B.value

self.A = MyDescriptorClass() will not work according to the python docs 根据python文档 self.A = MyDescriptorClass()将不起作用

so the only way is that I declate A = MyDescriptorClass() in the ParentClass as 所以唯一的方法是我将ParentClass中的A = MyDescriptorClass()

class ParentClass(object):
    A = MyDescriptorClass() # here I am unable to pass the owner

And since, I'm using a child class, super call skips this part and starts directly with __init__ 并且由于我使用的是子类,因此super call跳过了这一部分,直接以__init__开始

Is there any way in which I can modify the design so as to set the value of ChildClass1.A instance directly? 有什么方法可以修改设计以直接设置ChildClass1.A实例的值?

  c = ChildClass1("c1")
  c.A = 10 # I directly want to set this value instead of using c.A.set(10)
  c.B = 20
  c.evaluate()
  print c.ans # 30
  c.B = 40
  print c.ans # 50

Try not to put information which is specific to instances in the descriptor. 尽量不要将特定于实例的信息放在描述符中。 Keep information specific to instances in instance attributes, and keep information specific to the descriptor (like activates ) in the descriptor: 将特定于实例的信息保留在实例属性中,并将特定于描述符的信息保留在描述符中(例如activates ):

class MyDescriptorClass(object):
    def __init__(self, activates = 0):
        self.value = None
        self.activates = activates
        self.connects = []

    def __set__(self, instance, val):      # 1
        if self.value == val:
            return 0
        self.value = val
        if self.activates:
            instance.evaluate()
    def __get__(self, instance, instcls):  # 1 
        return self.value    
  1. Note that the __set__ and __get__ methods are passed the instance which is accessing the descriptor. 注意, __set____get__方法传递给正在访问描述符的instance Therefore, you do not need to store the owner in MyDescriptor. 因此,您不需要将owner存储在MyDescriptor中。 The instance is the owner . instanceowner

Given the clarification of the problem in the comments below, here is how I would implement the descriptor. 鉴于以下注释中对问题的澄清,这就是我将如何实现描述符。

class GateInput(object):
    def __init__(self, index):
        self.index = index                    # 4

    def __get__(self, inst, instcls):
        return inst.inputs[self.index].ans    # 5

    def __set__(self, inst, val):
        if isinstance(val, (float, int)):
            inst.inputs[self.index] = Constant(val)
        else:
            inst.inputs[self.index] = val


class Constant(object):
    def __init__(self, val):
        self.ans = val


class Gate(object):
    A = GateInput(0)               # 1  
    B = GateInput(1)               # 1

    def __init__(self, name):
        self.name = name
        self.inputs = [Constant(0), Constant(0)]    # 2


class Adder(Gate):
    @property
    def ans(self):
        result = 0
        for gate in self.inputs:
            result += gate.ans                      # 3
        return result


class Multiplier(Gate):
    @property
    def ans(self):
        result = 1
        for gate in self.inputs:
            result *= gate.ans
        return result

b = Multiplier('b1')
b.A = 2
b.B = 3
print(b.A)
# 2
print(b.ans)
# 6

c = Adder('c1')
c.A = 10
print(c.ans)
# 10

# This connects output of b to an input of c
c.B = b
print(c.ans)
# 16
  1. Descriptors have to be defined as class attributes, not instance attributes. 描述符必须定义为类属性,而不是实例属性。 Since the descriptor is accessed by all instances, you probably do not want the descriptor to change merely because an instance is being created. 由于描述符被所有实例访问,因此您可能不希望仅由于创建实例而更改描述符。 Therefore, do not instantiate the descriptor in __init__ . 因此,请勿在__init__实例化描述符。
  2. Each instance of Gate has a list of inputs. Gate的每个实例都有一个输入列表。 The items self.inputs are instances of Constant or Gate. self.inputs项目是Constant或Gate的实例。
  3. Here we see the purpose of the Constant class. 在这里,我们看到了常量类的目的。 For every gate , gate.ans needs to return a value. 对于每个gategate.ans需要返回一个值。
  4. The index records which item in inst.inputs the GateInput is connected to. 索引记录GateInput连接到inst.inputs中的哪个项目。
  5. inst is an instance of Gate. inst是Gate的一个实例。 For example, cA causes Python to call GateInput.__get__(self, c, type(c)) . 例如, cA使Python调用GateInput.__get__(self, c, type(c)) Thus, inst is c here. 因此,这里instc

As it is int he comments: descriptors must be class attributes, not instance attributes in order to work - so, to start with: 正如他所说的那样:描述符必须是类属性,而不是实例属性才能起作用-因此,从以下内容开始:

class ParentClass(object):
    A = MyDescriptorClass()
    B = MyDescriptorClass()
    def __init__(self, name):
        self.states = {}
        self.name = name
        self.A.configure(self, name, activates = 1)    
        self.B.configure(self, name, activates = 1)
        self.states.setDefault('A', self.A)
        self.states.setDefault('B', self.B)

And then you fix your Descriptor class accordingly: either have then keeping all data refering to an instance in the instance itself (that is why __get__ and __set__ receive the object itself) - or have each descriptor instance have a dictionary where they can annotate data related to the instances of the class they belong too, by, for example, object ID. 然后相应地修复Descriptor类:要么保留所有数据引用实例本身中的一个实例(这就是__get____set__接收对象本身的原因),要么让每个描述符实例都有一个字典,在其中可以注释与数据相关的数据它们也属于类的实例,例如通过对象ID。

Your descriptor class could be more or less along these lines: 您的描述符类或多或少地遵循以下原则:

class MyDescriptorClass(object):
    def __init__(self): 
        self.data = defaultDict(dict)
    def configure(self, owner, name, activates = 0):
        container = self.data(id(owner))
        container["value"] = None
        container["name"] = name
        ...

    def __set__(self, owner, value):
        # implemnt your previous "set" method straight here
        ...  
        ...

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

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