Over my head with descriptors

S

Sarcastic Zombie

Code included below.

Basically, I've created a series of "question" descriptors, which each
hold a managed value. This is so I can implement validation, and render
each field into html automatically for forms.

My problem is this: every instance of my "wizard" class has unique self
values, but they share the exact same descriptor values.

Meaning, if

t = Test("ar")
y = Test("ar")

t is y
False

t.age is y.age
True

t.age = 9
y.age
9

Code below. What am I not understanding?
-----------------------------------------

import datetime, re

class Question(object):
def __init__(self, qtext, name, default=None, required=False,
max_length=None, choices=None):
self._name = name
self._qtext = qtext
self._value = default
self._error = None
self._max_length = max_length
self._required = required
self._choices = choices

def __get__(self, instance, owner):
return self

def __set__(self, instance, value):
error = self.validate(value)
if not error:
self._value = self.cast(value)
self._error = None
else:
self._value = value
self._error = error
print error

def __str__(self):
return str(self._value)

def __repr__(self):
return str(self._value)

def cast(self, value):
return value

def validate(self, value):
return True

def html(self):
#ugly html renderer removed; irrelevant to problem
return html

def error(self):
if self._error:
return True


class Q_Integer(Question):
def validate(self, value):
if self._required and not value:
return "Field is required."
elif not value:
return None
try:
int(value)
return None
except:
return "Answer must be a whole number."

def cast(self, value):
if value:
return int(value)

class Q_Float(Question):
def validate(self, value):
if self._required and not value:
return "Field is required."
elif not value:
return None
try:
float(value)
return None
except:
return "Answer must be a decimal number."

def cast(self, value):
if value:
return float(value)

class Q_Chars(Question):
def validate(self, value):
try:
if self._required and not value:
return "Field is required."
elif not value:
return None
if self._max_length:
if len(value) > self._max_length:
return "Too many characters; max of %s allowed." %
self._max_length
return None
except:
return "Invalid entry."

def cast(self, value):
if value:
return str(value)

class Q_Long(Question):
def validate(self, value):
try:
if self._required and not value:
return "Field is required."
elif not value:
return None
except:
return "Invalid entry."

def cast(self, value):
if value:
return str(value)

def html(self):
#ugly html renderer removed; irrelevant to problem
return html

class Q_Bool(Question):
def validate(self, value):
return None

def cast(self, value):
return bool(value)

def html(self):
#ugly html renderer removed; irrelevant to problem
return html

class Q_Phone(Question):
def validate(self, value):
try:
if self._required and not value:
return "Field is required."
elif not value:
return None

pieces = value.split("-")
if len(pieces[0]) == 3 and len(pieces[1]) == 3 and len(pieces[2]) ==
4:
int(pieces[0])
int(pieces[1])
int(pieces[2])
return None
else:
return "Requires Valid Phone Number in XXX-XXX-XXXX format."
except:
return "Requires Valid Phone Number in XXX-XXX-XXXX format."


class Q_Date(Question):
def validate(self, value):
try:
if self._required and not value:
return "Field is required."
elif not value:
return None

r = re.compile(r"\d{1,2}[-/.]\d{1,2}[-/.]\d{1,4}")
month, day, year =
r.findall(value)[0].replace("/","-").replace(".","-").split("-")
date = datetime.date(year=int(year), month=int(month), day=int(day)
)
return None
except:
return "Requires valid date in mm-dd-yy format."

def cast(self, value):
if value:
r = re.compile(r"\d{1,2}[-/.]\d{1,2}[-/.]\d{1,4}")
month, day, year =
r.findall(value)[0].replace("/","-").replace(".","-").split("-")

year = int(year)
if year < 70:
year += 2000
elif year < 100:
year += 1000

date = datetime.date(year=int(year), month=int(month), day=int(day)
)

return date

def __str__(self):
date = self._value
return "%s/%s/%s" % (date.month, date.day, date.year)

## Wizard Base Object

class Wizard(object):
def __init__(self, action):
self.action = action
self.init_time = datetime.datetime.now()

pagediv = "box"
title = "A Dynamic Wizard"
instructions = "There are no real instructions here. Sorry."

grouping = [ ]

def render_form(self):
#ugly html renderer removed; irrelevant to problem
return form

def flatten(self, post):
for key in post:
if not key == "command":
errors = ""
cblock = "self.%s = '%s'\n" % (key, post[key])
ab_save = compile( cblock, errors, 'exec')
exec(ab_save)

def errorcheck(self):
error = 0
for section in self.grouping:
for question in section:
t = eval( "self.%s.validate(self.%s._value)" % (question, question)
)
if t:
error = 1
return error



C_CHOICES = (
("red", "Red"),
("blue", "Blue"),
("green", "Green"),
)

class Test(Wizard):
grouping = [
[ 'age', 'weight' ],
[ 'feet', 'inches' ],
['name', 'cash', 'fav_color', 'happy', 'birthday'] ]

def __new__(self):
age = Q_Integer("Your Age:", "age", 99)
weight = Q_Integer("Your Weight:", "weight", 200)
feet = Q_Integer("Feet tall:", "feet", 6)
inches = Q_Integer("Inches Tall:", "inches", 0)
name = Q_Chars("Your Name:", "name", max_length=15, required=True)
cash = Q_Float("Money in hand?", "cash", required=True,
default=55.50)
fav_color = Q_Chars("Your favorite color?", "fav_color",
required=True, max_length=50, choices=C_CHOICES)
homezip = Q_Zip("Your zip code?", "homezip", required=True, )
happy = Q_Bool("Are you happy?", "happy", default=False)
birthday = Q_Date("Your Birthday:", "birthday")
 
T

Tim Roberts

Sarcastic Zombie said:
Code included below.

Basically, I've created a series of "question" descriptors, which each
hold a managed value. This is so I can implement validation, and render
each field into html automatically for forms.

My problem is this: every instance of my "wizard" class has unique self
values, but they share the exact same descriptor values.

...
class Test(Wizard):
grouping = [
[ 'age', 'weight' ],
[ 'feet', 'inches' ],
['name', 'cash', 'fav_color', 'happy', 'birthday'] ]

def __new__(self):
age = Q_Integer("Your Age:", "age", 99)
weight = Q_Integer("Your Weight:", "weight", 200)
feet = Q_Integer("Feet tall:", "feet", 6)
inches = Q_Integer("Inches Tall:", "inches", 0)
name = Q_Chars("Your Name:", "name", max_length=15, required=True)
cash = Q_Float("Money in hand?", "cash", required=True,
default=55.50)
fav_color = Q_Chars("Your favorite color?", "fav_color",
required=True, max_length=50, choices=C_CHOICES)
homezip = Q_Zip("Your zip code?", "homezip", required=True, )
happy = Q_Bool("Are you happy?", "happy", default=False)
birthday = Q_Date("Your Birthday:", "birthday")

The __new__ method is called with the CLASS as its first argument, not the
new instance. __new__ is supposed to RETURN the new instance. So, when
you set "age", you are setting a CLASS attribute that will be shared by all
instances.

Is there a reason you don't just use __init__ instead of __new__, and use
"self.age" and "self.weight" and so on?
 
C

Christian Kastner

Sarcastic said:
Code included below.

Basically, I've created a series of "question" descriptors, which each
hold a managed value. This is so I can implement validation, and render
each field into html automatically for forms.

My problem is this: every instance of my "wizard" class has unique self
values, but they share the exact same descriptor values.

That's because descriptors only work as class attributes, not instance
attributes.
Meaning, if

t = Test("ar")
y = Test("ar")

t is y
False

t.age is y.age
True

As an attribute of the owner class, the descriptor is instantiated once
for the class itself, and not for every instance of the owner class:

class Foo(object):

# Class attribute
desc = Descriptor()

def __init__(self, value):
# Instance attributes go here
self.value = somevalue
Code below. What am I not understanding?
----------------------------------------- ....
class Question(object): ....
def __get__(self, instance, owner):
return self

If you want to do per-instance stuff, use the "instance" argument:

# Warning: simplified
class Descriptor(object):
def __get__(self, instance, owner):
return instance._desc

def __set__(self, instance, value):
instance._desc = value

Chris
 
C

Christian Kastner

Tim said:
Sarcastic Zombie said:
Code included below.

Basically, I've created a series of "question" descriptors, which each
hold a managed value. This is so I can implement validation, and render
each field into html automatically for forms.

My problem is this: every instance of my "wizard" class has unique self
values, but they share the exact same descriptor values.

...
class Test(Wizard):
grouping = [
[ 'age', 'weight' ],
[ 'feet', 'inches' ],
['name', 'cash', 'fav_color', 'happy', 'birthday'] ]

def __new__(self):
age = Q_Integer("Your Age:", "age", 99)
weight = Q_Integer("Your Weight:", "weight", 200)
feet = Q_Integer("Feet tall:", "feet", 6)
inches = Q_Integer("Inches Tall:", "inches", 0)
name = Q_Chars("Your Name:", "name", max_length=15, required=True)
cash = Q_Float("Money in hand?", "cash", required=True,
default=55.50)
fav_color = Q_Chars("Your favorite color?", "fav_color",
required=True, max_length=50, choices=C_CHOICES)
homezip = Q_Zip("Your zip code?", "homezip", required=True, )
happy = Q_Bool("Are you happy?", "happy", default=False)
birthday = Q_Date("Your Birthday:", "birthday")

The __new__ method is called with the CLASS as its first argument, not the
new instance. __new__ is supposed to RETURN the new instance. So, when
you set "age", you are setting a CLASS attribute that will be shared by all
instances.

As long as "age" really is set on the class. In the code above, "age" is
just a local variable.
Is there a reason you don't just use __init__ instead of __new__, and use
"self.age" and "self.weight" and so on?

I was asking myself the same thing...

Chris
 
S

Sarcastic Zombie

Is there a reason you don't just use __init__ instead of __new__, and use

"A lack of understanding," he answered sheepishly.

There are attributes (ie, question._qtext) that I do want to be the
same for every instance and thus don't want the extra memory overhead
of storing it anew for each instance.

Using the "instance" reference as you guys suggested, I'm now storing
the instance-specific information that the descriptors validate by
cramming it into instance._foo. Works great now!

Thanks for the additional understanding. It's a lot to wrap one's head
around at first.

-SZ
 
D

Dennis Lee Bieber

There are attributes (ie, question._qtext) that I do want to be the
same for every instance and thus don't want the extra memory overhead
of storing it anew for each instance.
Class-wide attributes are accessed as just that... by Class (though,
by the nature of Python, all those instances are storing, in most cases,
is a reference to the object value)
.... common_1 = "Some nonsense for example"
.... blueberry_pie = math.pi * 2.78
.... def __init__(self):
.... self.uncommon = id(self)
.... def setPie(self, newPie):
.... Wide.blueberry_pie = newPie
.... --
Wulfraed Dennis Lee Bieber KD6MOG
(e-mail address removed) (e-mail address removed)
HTTP://wlfraed.home.netcom.com/
(Bestiaria Support Staff: (e-mail address removed))
HTTP://www.bestiaria.com/
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

Forum statistics

Threads
473,962
Messages
2,570,134
Members
46,690
Latest member
MacGyver

Latest Threads

Top