【问题标题】:Implementing descriptors with dynamic field using metaclasses使用元类实现具有动态字段的描述符
【发布时间】:2018-09-17 06:02:06
【问题描述】:

我有实现描述符协议的用户定义类。描述符被分配给客户端类的属性。目前描述符使用通用变量名,即描述符的类名和键。

我希望根据客户端类上的名称动态定义属性。我知道我需要为此使用元类,所以这是我尝试过的:

class MyDescriptor(object):
        __counter = 0

        def __init__(self, field=None):
                self.field = field or '{}_{}'.format(self.__class__.__name__, self.__counter)

        def __get__(self, obj, owner):
                print 'getter'
                return getattr(obj, self.field)

        def __set__(self, obj, val):
                print 'setter'
                setattr(obj, self.field, val)


class MyMeta(type):
        def __new__(mcl, name, bases, nspc):
                for k,v in nspc.items():
                        if isinstance(v, MyDescriptor):
                                nspc[k] = MyDescriptor(field=k)

                return super(MyMeta, mcl).__new__(mcl, name, bases, nspc)


class User(object):

        __metaclass__ = MyMeta

        desc = MyDescriptor()

        def __init__(self, desc):
                self.desc = desc

现在这似乎可行,如果我检查我的 User 类的 dict 属性,我会看到 MyDescriptor 对象的字段值具有值“desc”:

>>> User.__dict__['desc'].__dict__
{'field': 'desc'}

但是当我创建一个User 的新对象时,我最终会得到一个递归循环,它会打印“setter”并以异常结束。

>>> User('x')
setter
setter
setter
setter
setter
...

为什么会这样?为什么在我尝试初始化对象时会递归调用__set__ 方法?

如何根据客户端类中的属性名称为 Descriptor 对象分配动态值? (在上面的示例中,我使用了 desc,但假设我还有其他内容,例如名称、位置等。)

【问题讨论】:

  • 如果你设置field等于描述符的名字,setattr(obj, self.field, val)会再次调用描述符。字段名称必须不同于描述符的名称。使用self.field = '_' + field 或其他东西。
  • 非常感谢@Aran-Fey,这解决了我的问题 :-) 如果您将此作为答案,我会接受它
  • 呃,我不知道。对我来说看起来像是一个简单的疏忽,无论如何我们可能有十几个重复的这个问题。我认为我不能证明将其发布为答案。

标签: python python-2.7 metaclass descriptor


【解决方案1】:

我正在回答我的问题以将此问题标记为已回答。

感谢 @Aran-Fey 评论,我的问题是我使用了与属性相同的值字段,因此在查找 self.field 值时递归调用了 setattr(obj, self.field, val)。将 self.field 值更改为 '_' + field 解决了我的问题。

【讨论】:

    【解决方案2】:

    关于超出递归深度的问题,另一种解决方案是直接操作对象字典,如obj.__dict__[self.field] = val。我更喜欢这种方法,因为它不需要你在描述符类中有一个 __init__ 只是为了设置一个假属性名称来解决使用 getattr()setattr() 时出现的递归问题

    现在对于属性的动态命名,这就是我解决这个问题的方法。我知道它与您的方法略有不同,但它对我有用。

    class MyMeta(type):
    
        def __new__(mcs, cls, bases, dct):
            for k, v in dct.items():
                if if isinstance(v, Descriptor):
                    dct[k].attr = k
            return super().__new__(mcs, cls, bases, dct)
    
    
    class Descriptor:
        def __get__(self, instance, owner):
            if instance is None:
                return self
            else:
                return instance.__dict__[self.attr]
    
    class Positive(Descriptor):
    
        def __set__(self, instance, value):
            if value < 1:
                raise ValueError("{}.{} can't be negative".format(
                    instance.__class__.__name__, self.attr))
            else:
                instance.__dict__[self.attr] = value
    
    
    class Rectangle(metaclass=MyMeta):
        length = Positive()
        breadth = Positive()
    
        def __init__(self, length, breadth):
            self.length = length
            self.breadth = breadth
    
        def __repr__(self):
            return "{}({!r}, {!r})".format(self.__class__.__name__, self.length,
                                           self.breadth)
    
    
    r1 = Rectangle(4, 20)
    r2 = Rectangle(100, 200)
    print(r1, r2)
    r1.breadth = 30
    r2.length = 150
    print(r1, r2)
    

    输出

    Rectangle(4, 20) Rectangle(100, 200)
    Rectangle(4, 30) Rectangle(150, 200)
    

    它的工作方式是从元类中的类字典中截取描述符实例,然后在描述符实例的字典中添加一个新属性,并将其值实际设置为描述符实例名称。

    所以在上面的例子中,MyMeta 将遍历类字典,当它找到一个属性是类 Descriptor 的实例时,lengthbreadth 是,它会添加一个属性 @987654331 @ 和 attr=breadth 分别用于 lengthbreadth 描述符实例。

    请注意,这并不要求您在描述符类中有__init__。另外,您可以看到元类不是特定于描述符的,您可以使用相同的元类,它适用于任何描述符,您唯一需要做的就是通过从 Descriptor 类继承来创建一个新的描述符类

    【讨论】:

      【解决方案3】:

      大家好,让我向您展示如何使用 Python 元类编写最先进的描述符,如何对它们执行验证。以及如何使用元类定义自己的数据类型

      __Author__ = "Soumil Nitin SHah "
      __Version__ = "0.0.1"
      __Email__ = ['soushah@my.bridgeport.edu',"shahsoumil519@gmail.com"]
      __Github__ = "https://github.com/soumilshah1995"
      
      Credits = """  Special Thanks to David Beazley for his Tutorials """
      
      from inspect import  Parameter, Signature
      import datetime
      
      
      class Descriptor(object):
      
          def __init__(self, name):
              self.name = name
      
          def __get__(self, instance, owner):
      
              print(" __get__ ")
              return instance.__dict__[self.name]
              # This will return Value
      
              # if instance is not None:
              #     return self
              # else:
              #     return instance.__dict__[self.name]
      
          def __set__(self, instance, value):
      
              print(" __set__ ")
              instance.__dict__[self.name] = value
      
          def __delete__(self, instance):
              print(" __delete__ ")
              del instance.__dict__[self.name]
      
          def __repr__(self):
              return "Object Descriptor : {}".format(self.name)
      
      
      class Typed(Descriptor):
          ty = object
      
          def __set__(self, instance, value):
              if not isinstance(value, self.ty):
                  raise TypeError('Expected %s' % self.ty)
      
              super().__set__(instance, value)
      
      
      class PositiveInterger(Descriptor):
      
          def __set__(self, instance, value):
              if value < 0:
                  raise ValueError ("Value cannot be less than 0")
      
      
      class Sized(Descriptor):
      
          def __init__(self, *args, maxlen, **kwargs):
              self.maxlen = maxlen
              super().__init__(*args, **kwargs)
      
          def __set__(self, instance, value):
              if len(value) > self.maxlen:
                  raise ValueError('Too big')
              super().__set__(instance, value)
      
      
      def make_signature(names):
          return Signature(
              Parameter(name, Parameter.POSITIONAL_OR_KEYWORD)
              for name in names)
      
      
      class Integer(Typed):
          ty = int
      
      
      class Float(Typed):
          ty = float
      
      
      class String(Typed):
          ty = str
      
      
      class StructMeta(type):
      
          def __new__(cls, clsname, base, clsdict):
      
              clsobj = super().__new__(cls, clsname, base, clsdict)
              sig = make_signature(clsobj._fields)
              # print("Sig", sig)
              setattr(clsobj, '__signature__', sig)
              return clsobj
      
      
      class Structure(metaclass=StructMeta):
      
          _fields = []
      
          def __init__(self, *args, **kwargs):
      
              # print("**kwargs", kwargs)
              # print("*Args", args)
      
              bound = self.__signature__.bind(*args, **kwargs)
              # print("bound", bound)
      
              for name, val in bound.arguments.items():
                  # print(name, val)
                  setattr(self, name, val)
      
      
      class Stock(Structure):
      
          _fields = ['name', 'shares', 'price', "number", "fullname"]
      
          name = String('name')
          shares = Integer('shares')
          price = Float('price')
          number = PositiveInterger('number')
          fullname = Sized('fullname', maxlen=8)
      
          def methodA(self):
              print("Total Shares {}".format(self.shares))
      
      
      if __name__ == "__main__":
          obj = Stock(name="Soumil", shares=12, price=12.2, number =2, fullname='aaaaaa')
          print("="*55)
          print(obj.methodA())
      

      【讨论】:

        猜你喜欢
        • 2020-12-03
        • 1970-01-01
        • 2014-10-11
        • 1970-01-01
        • 2013-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多