__getattr__/__setattr__ - infinite recursion

Charles G Waldman cgw at fnal.gov
Mon Mar 27 19:56:03 CEST 2000


There are several problems in the script you've posted, below find a
corrected version with the changed lines marked with ##Change

Here's an explanation of the changes:

Change 1:  You raise "Error" but don't define it anywhere.  You must
           create it either as a string, or as an Exception object.

Change 2:  You had "self.reset_to_defaults(self)", one self too many.

Change 3:  This is the main point.  You have no way to init your
           class, because setting the initial attributes - "attrs",
	   "values" and "keyname" - goes through the validation
	   mechanism, which doesn't make sense for these attributes,
	   since the validation mechanism can't be used until
	   these attributes are set up.
	   In general, if you have a setattr hook and need to set 
	   some instance attributes while avoiding this hook, you can do
	   self.__dict__[key] = value, instead of self.key = value
	   
Change 4:  I just hate to see "except:" in code... you should always
           name the exceptions you're looking for, if at all possible.
	   Otherwise debugging becomes impossible - you have a
	   mismatched paren or something, instead of getting a
	   SyntaxError you fall into the unqualified "except:" clause.
	

	

#---------------------------------------------------------------------
# Why does this blow up Python?
#---------------------------------------------------------------------

import os, sys, string, pprint

# "type objects"
int_type       = type(0)
string_type    = type("X")
float_type     = type(0.1)

# required field flags?
required = 1
optional = 0

Error = "Error"  ### Change 1

# Class with strict type checking and a single key:
class zdb_rec:
    def __init__(self, pattern_dict=None, keyname=None):
        "create an object with a strict pattern of required values to make up a type"
        self.set_valid(pattern_dict)
        self.keyname = keyname
        self.reset_to_defaults()  #####Change 2

    def set_valid(self,pattern_dict):
        self.attrs = pattern_dict

    def set_keyname(self,keyname):
        self.keyname = keyname

    def reset_to_defaults(self):
        # fill in default values for each attribute
        self.values = {}
        for x in self.attrs.keys():
            self.values[x] = self.attrs[x][2]

    def keyname(self):
        return self.keyname

    def key(self):
        return self.values[self.keyname]

      # setattr does typesafety check:
    def __setattr__(self,name,value):
        "custom __setattr_ with typesafety check" 
        if name in ('attrs','keyname','values'):  ##Change 3
            self.__dict__[name]=value             ##Change 3
            return                                ##Change 3
            
        try:
            if (value == None) and (not self.attrs[name][1]):
                self.values[name] = None
            else:
                if (type(value) == self.attrs[name][1]):
                    self.values[name] = value
                else:
                    raise Error, "Invalid type in assignment"+`type(value)`
        except KeyError: ######Change 4
            raise Error, "Invalid attribute "+`name`
            
    def __getattr__(self,name):
        try:
            return self.values[name]
        except KeyError: ######Change 4
            raise Error, "Invalid attribute "+`name`

    def attr_count(self):
        return len(self.attrs)

    def validate(self):
        "raises an exception if validation fails, ie requird field not provided"
        for x in self.attrs.keys():
            if self.attrs[x][1]:
                if self.values[x] == None:
                    raise Error, "validate() failed - required field not set"

# "Person Database Schema":
person_attributes = {
 # attribute    type          required?   initialvalue
     "name":    (string_type,    required,  ""),
     "address": (string_type,    required,  ""),
     "age":     (int_type,       required,  0 ),
}


x = zdb_rec(person_attributes,"name")

print "done"






More information about the Python-list mailing list