问题
I created a class String()
with __get__()
, __set__()
, and a method to_db()
; however, when I do name = String()
, I can't do self.name.to_db()
because it's calling to_db()
on the value returned by __get__()
, not the object "name
".
class String(object):
def __init__(self, value=None):
if value:
self.value = str(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = str(value)
def to_db(self):
return {'type':'string', 'value': self.value}
class Example(object):
name = String()
age = Integer()
def __init__(self,name,age):
self.name = name
self.age = age
def save():
data = dict(name=self.name.to_db(), age=self.age.to_db())
db.save(data)
One way to deal with this is to not call self.name.to_db()
directly and instead set a flag in instance
and create a conditional in __get__()
to check for it and call to_db()
if it's True
, but this seems kludgy. Is there a better way?
Also, I'm new to descriptors -- what are the pros/cons of using instance
and/or instance.__dict__
to store state vs storing it in self
?
回答1:
It's pretty easy - just have your descriptor return a subclass of string with the extra method(s) you want.
def __get__(self, instance, owner):
class TaggedString(str):
def to_db(self):
return {'type':'string', 'value': self}
return TaggedString(self.value)`
回答2:
Here's a solution that allows you to bypass any descriptors defined in the class:
class BypassableDescriptor(object):
pass
class String(BypassableDescriptor):
def __init__(self, value=None):
if value:
self.value = str(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = str(value)
def to_db(self):
return {'type': 'string', 'value': self.value}
class Integer(BypassableDescriptor):
def __init__(self, value=None):
if value:
self.value = str(value)
def __get__(self, instance, owner):
return self.value
def __set__(self, instance, value):
self.value = int(value)
def to_db(self):
return {'type': 'integer', 'value': self.value}
class BypassDescriptor(object):
def __init__(self, descriptor):
self.descriptor = descriptor
def __getattr__(self, name):
return getattr(self.descriptor, name)
class AllowBypassableDescriptors(type):
def __new__(cls, name, bases, members):
new_members = {}
for name, value in members.iteritems():
if isinstance(value, BypassableDescriptor):
new_members['real_' + name] = BypassDescriptor(value)
members.update(new_members)
return type.__new__(cls, name, bases, members)
class Example(object):
__metaclass__ = AllowBypassableDescriptors
name = String()
age = Integer()
def __init__(self,name,age):
self.name = name
self.age = age
def save(self):
data = dict(name = self.real_name.to_db(), age = self.real_age.to_db())
Note that it's not perfect - you'll always have to call real_fieldname.method()
instead of fieldname.method()
and you'll have to use the metaclass AllowBypassableDescriptors for all your classes which need to access this field. Then again, it's a pretty compatible solution that avoids monkey-patching the object wrapped by the descriptor.
That said, I'm not sure that descriptors are the best solution for what you're trying to do (writing to a database?).
回答3:
Descriptors are used to describe "what is it" or "how it works".
For example, we can put some restriction in the __get__()
or the __set__()
.
According to your question, I think you want to add your own method into type<str>
, not to describe how to set or get the instance.
So you may use thee code below to express what you want to do.
class String(str):
def __init__(self, value):
self.value = value
def to_db(self):
return {'type':'string', 'value': self.value}
ss = String('123')
print ss #123
print ss.to_db() #{'type':'string', 'value': '123'}
回答4:
Inside method to_db you may access directly the value via
self.__dict__['value'] # value as key is not ideal, but that's what OP used
or, if you are using new style classes only,
object.__set__(self, name, value)
Since you are using magic attributes, accessing the magic __dict__
is perfectly reasonable.
This is also referred in the documentation for __setattr__
[1] (sorry, there is no direct reference to __dict__
in __set__
but it's the same problem domain)
If __setattr__() wants to assign to an instance attribute, it should not
simply execute self.name = value — this would cause a recursive call to itself.
Instead, it should insert the value in the dictionary of instance attributes, e.g.,
self.__dict__[name] = value. For new-style classes, rather than accessing the instance
dictionary, it should call the base class method with the same name, for example,
object.__setattr__(self, name, value).
[1] http://docs.python.org/2/reference/datamodel.html#customizing-attribute-access
来源:https://stackoverflow.com/questions/6023595/how-to-call-methods-on-python-class-descriptor-objects