[Python-Dev] Type checks of instance variables (original) (raw)
Michael McLay mclay@nist.gov
Wed, 10 Oct 2001 13:35:58 -0400
- Previous message: [Python-Dev] __del__ and subclassed types
- Next message: [Python-Dev] Re: [Python-checkins] CVS: python/dist/src/Lib xmlrpclib.py,1.11,1.12
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Many applications require constraining the contents of instance variables to specific types. For instance, the attributes of an element in a validating XML Schema implementation would require constraining the types allowed in each of the instance variables of the class implementing the element definition. This testing for type constraints can be accomplished by adding an isinstance test to the setattr of the class that contains the constrained attributes.
Prior to Python 2.2 the resulting class definitions would be bloated with many lines of type checking code. The new builtin property class has simplified the task of constraint checking somewhat. There are some improvements that would make it easier and less error prone to selectively restrict the types of instance variables.
The following change to the property class adds a test for the instance variable type to the set method.
class property2(object):
def __init__(self, fget=None, fset=None, doc=None, type=None):
self.fget = fget
self.fset = fset
self._type = type
self.__doc__ = doc
def __get__(self, inst, type=None):
if self.fget is None:
raise AttributeError, "this attribute is write-only"
return self.fget(inst)
def __set__(self, inst, value):
if self.fset is None:
raise AttributeError, "this attribute is read-only"
if not isinstance(value,self._type):
raise AttributeError,"attribute not of type '%s'"%\
self._type.__name__
return self.fset(inst, value)
The following example illustrates the use of the property2 type checking capability. When the assignment of a value is made to c.b the type checks are performed as desired. Unfortunately this approach is not sufficient to guard against incorrect types being assigned to the instance variables. There are several examples demonstrating the "hidden" _b instance variable being populated with data of the wrong type.
################# demonstration of property2 ############################## from property2 import property2
class unsignedByte(int): """ An unsignedByte is a constrained integer with a range of 0-255 """ def init(self,value): if value < 0 or value > 255: raise ValueError, "unsignedByte must be between 0 and 255" self = value
class Color(object): def init(self,b= 0 ): # the following assignment does not check the type of "_b". # The default value of "0" is not of the proper type for # the instance variable b as defined by the property2 statement. self._b = b
__slots__ = ["_b"]
def setb(self, value):
self._b = value
def getb(self):
return self._b
def messItUp(self):
self._b = 2000
b = property2(getb,setb,type=unsignedByte)
c = Color()
x = unsignedByte(34)
c.b = x
print "The value of c.b is ",c.b
c.messItUp() print "A method can accidentally mess up the variable", c.b
c.b = x c._b = 4000 print "you can override the hidden value as well", c.b
Proper assignment to the instance variable does run the type check.
An exception is raised by the following incorrectly typed variable
assignment
c.b = 25
In order for the property2 type checking to work well the mechanism needs to forbid access to the "hidden" container for the value of the instance variable.
The type checking could also be combined with the new slots mechanism for declaring variable names. In this approach a TypedSlots class defines the type constraints for the slot names. The following example demonstrates the use of the TypedSlots class (a subclassed dictionary) to define the types allowed for each slot in a class definition.
class unsignedByte(int): """ An unsignedByte is a constrained integer with a range of 0-255 """ def init(self,value): if value < 0 or value > 255: raise ValueError, "unsignedByte must be between 0 and 255" self = value
class TypedSlots(dictionary): """A TypedSlot object defines an object that is assigned to a class.slot definition. The TypedSlot object constrains each slot to a specific set of types. The allowed types are associated with the name at the time the slot names are defined. """ def defineSlot(self,name,types): """Add a new slot to the TypedSlots object. All slot definitions can be added as part of the TypedSlots constructor, or they can be added one at a time using the definedSlot method """ self["_" + name] = (name,types)
build an instance of the TypedSlots object by adding the slots
one at a time.
t = TypedSlots() t.defineSlot("r",unsignedByte) t.defineSlot("g",unsignedByte) t.defineSlot("b",unsignedByte) t.defineSlot("name",str)
The following class definition uses the TypedSlot instance example
The dictionary type can be used in place of a list of
strings for defining slots because iterating over a dictionary returns
a list of strings (the keys to the dictionary).
class Color(object): slots = t
# The bytecodes generated by the following setX and getX methods and
# assignment of property to X would be automatically generated when
# an object of type TypedSlots is detected as the type for __slots__.
def setb(self,value):
if isinstance(value,self.__slots__["_b"][1]):
self._b = value
else:
raise TypeError, "b must be of type unsignedByte"
def getb(self):
if isinstance(self._b,self.__slots__["_b"][1]):
return self._b
else:
raise TypeError, "b must be set to a value of type unsignedByte"
b = property(getb,setb)
def setr(self,value):
if isinstance(value,self.__slots__["_r"][1]):
self._r = value
else:
raise TypeError, "r must be of type unsignedByte"
def getr(self):
if isinstance(self._r,self.__slots__["_r"][1]):
return self._r
else:
raise TypeError, "r must be set to a value of type unsignedByte"
r = property(getr,setr)
def setg(self,value):
if isinstance(value,self.__slots__["_g"][1]):
self._g = value
else:
raise TypeError, "g must be of type unsignedByte"
def getg(self):
if isinstance(self._g,self.__slots__["_g"][1]):
return self._g
else:
raise TypeError, "g must be set to a value of type unsignedByte"
g = property(getg,setg)
def setname(self,value):
if isinstance(value,self.__slots__["_name"][1]):
self._name = value
else:
raise TypeError, "name must be of type str"
def getname(self):
if isinstance(self._name,self.__slots__["_name"][1]):
return self._name
else:
raise TypeError, "name must be set to a value of type str"
name = property(getname,setname)
c = Color()
c.name = "aqua" print "The color name is", c.name
x = unsignedByte(254) c.b= x print "The value of the blue component is", c.b
the following statement will fail because the value is not
of type unsignedByte
c.g= 255
This implementation also cannot guard the "hidden" names for the slots from access. This creates a security hole in the type constraint system. This hole complicates any attempts to write optimizations that take advantage of the typed slots. The proposed change to the slots implementation would force type checks to be made on all accesses to the variables defined by TypedSlots. The implementation would automatically generate the bytecode for testing the instance type as defined by the TypeSlots definition.
The implementation of the example required the slot to have "hidden" names (i.e. the names _r, _b _g, and _name) in order to implement the type checks on the slots for the instance variables. The proposed TypedSlots mechanism would eliminate the "hidden" name and calculate the address of the slot directly.
The addition of this mechanism to the slots definition would add
protections against violations of a type constraint system for slots. The
mechanism would also reduce the code required to define the constraints.
Using the TypedSlots definition, the constrained type of the example would be
written as follows:
class Color(object): slots =TypedSlots({ "r":unsignedByte, "g":unsignedByte, "b":unsignedByte, "name":str})
The contents of the slot would only be accessible though the exposed instance variable name. For example:
c = Color() c.r = unsignedByte(126)
An optional default value could be defined during the creation of the slots by making the value a tuple:
class Color(object): slots =TypedSlots({ "r":(unsignedByte,unsignedByte(0), "g":(unsignedByte,unsignedByte(0), "b":(unsignedByte,unsignedByte(0), "name":str})
If default values are defined the type test on get could be skipped.
- Previous message: [Python-Dev] __del__ and subclassed types
- Next message: [Python-Dev] Re: [Python-checkins] CVS: python/dist/src/Lib xmlrpclib.py,1.11,1.12
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]