我有用户定义的类,它实现了描述符协议。描述符被分配给客户端类的属性。目前,描述符使用通用变量名,即描述符的类名和密钥。
我希望根据客户端类上的名称动态定义属性。我知道我需要使用元类,所以这就是我尝试过的:
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,但是假设我还有其他类似名称,位置等的东西..)
答案 0 :(得分:0)
我正在回答我的问题,将此问题标记为已回答。
感谢 @ Aran-Fey 评论,我的问题是我使用了与属性相同的值字段,因此在查找setattr(obj, self.field, val)
值时递归调用self.field
。将self.field
值更改为'_' + field
解决了我的问题。
答案 1 :(得分:0)
对于超出递归深度的问题,另一种解决方案是直接操作对象字典,例如obj.__dict__[self.field] = val
。我更喜欢这种方法,因为它不需要您在描述符类中包含__init__
只是为了设置一个假属性名称,以解决使用getattr()
和{{ 1}}
现在可以动态命名属性了,这就是我解决此问题的方法。我知道它与您的方法没什么不同,但对我有用。
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
和length
是,它将分别为breadth
和attr=lenght
描述符实例添加一个attr=breadth
和length
属性。
请注意,这不需要您在描述符类中具有breadth
。另外,您可以看到该元类不是特定于描述符的,可以使用相同的元类,并且它适用于任何描述符,唯一要做的就是仅继承__init__
class < / p>
答案 2 :(得分:0)
大家好,让我向您展示如何使用Python Meta类编写最新的描述符,以及如何对它们执行验证。以及如何使用元类定义自己的数据类型
__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())