B
Ben
Hi,
I was recently writing a python interpreter (in python), partly to teach
myself more about the python object system, and partly to learn more about
how programming languages work in general.
I started with the getattribute/descriptor system as it seems that this is
in many ways the "core" of the interpreter, and have come across a few
questions.
Firstly, why does the default getattribute first check for data descriptors
in the type object? Is it only for symmetry with setattr, as it appears to
me that if you can't set that name in the instance dictionary then the fall
back to type lookup at the end should always "do the right thing". Of
course, you could override this by explicitly inserting into the instance
dictionary (a['foo'] = ...), but in this case it seems that the least
surprising thing to do would be to return the dictionary version anyway.
I will post my code so far at the end of this message for people to comment
on. The other changes that I have made from the python c code is that:
Object itself contains a lot of the machinery for being a type, and Type
itself does not do very much.
dset (the equiv of __set__) returns whether it actually did anything. This
is becuase I want to reimplement this in c++, and the neatest way I could
come up with for data descriptors was for the default __set__ to do nothing
and return false, which means "fall back to inserting into the instance
dictionary)
Thanks for the feedback in advance
Ben
---
Code follows:
class Object:
def __init__(self, typeval = None):
self.dict = {}
self.typeval = typeval
def getattribute(self, x):
t = self.lookup(x)
if t:
return t.dget(None, self)
t = self.typeval.lookup(x)
if t:
return t.dget(self, self.typeval)
raise AttributeError(x)
def setattribute(self, x, val):
if self.typeval:
t = self.typeval.lookup(x)
if t:
if t.dset(self, val):
return
self.dict[x] = val
def lookup(self, x):
for obj in self.mro():
t = obj.dict.get(x)
if t:
return t
return None
def mro(self):
return (self,)
def dget(self, object, typeval):
return self
def dset(self, object, val):
return False
def call(self, *args, **kwargs):
raise TypeError("Not callable")
class Value(Object):
def __init__(self, val):
Object.__init__(self)
self.val = val
class Function(Object):
def __init__(self, fun):
Object.__init__(self)
self.fun = fun
def dget(self, object, typeval):
if object:
return BoundFunction(object, self)
else:
return self
def call(self, *args, **kwargs):
return self.fun(*args, **kwargs)
class BoundFunction(Object):
def __init__(self, obj, fun):
Object.__init__(self)
self.fun = fun
self.obj = obj
def call(self, *args, **kwargs):
return self.fun.call(self.obj, *args, **kwargs)
class Type(Object):
def __init__(self):
Object.__init__(self)
def call(self, *args, **kwargs):
obj = Object(self)
return obj
class Property(Object):
def __init__(self, get, set):
Object.__init__(self)
self.get = get
self.set = set
def dget(self, object, typeval):
if object:
return self.get.call(object)
else:
return self
def dset(self, object, val):
self.set.call(object, val)
return True
if __name__ == "__main__":
def addone(self, val):
return val + 1
def getfoo(self):
return self.dict["foo"]
def setfoo(self, val):
self.dict["foo"] = val
test = Type()
test.setattribute("b", Value(2))
test.setattribute("string", Value("foobar"))
test.setattribute("fun", Function(addone))
test.setattribute("getfoo", Function(getfoo))
test.setattribute("setfoo", Function(setfoo))
test.setattribute("foobar", Property(test.getattribute("getfoo"),
test.getattribute("setfoo")))
print test.getattribute("foobar")
inst = test.call()
inst.setattribute("foobar", Value(2))
print test.getattribute("foobar")
print inst.getattribute("foobar").val
inst.setattribute("b", Value(3))
print test.getattribute("b").val
print inst.getattribute("b").val
I was recently writing a python interpreter (in python), partly to teach
myself more about the python object system, and partly to learn more about
how programming languages work in general.
I started with the getattribute/descriptor system as it seems that this is
in many ways the "core" of the interpreter, and have come across a few
questions.
Firstly, why does the default getattribute first check for data descriptors
in the type object? Is it only for symmetry with setattr, as it appears to
me that if you can't set that name in the instance dictionary then the fall
back to type lookup at the end should always "do the right thing". Of
course, you could override this by explicitly inserting into the instance
dictionary (a['foo'] = ...), but in this case it seems that the least
surprising thing to do would be to return the dictionary version anyway.
I will post my code so far at the end of this message for people to comment
on. The other changes that I have made from the python c code is that:
Object itself contains a lot of the machinery for being a type, and Type
itself does not do very much.
dset (the equiv of __set__) returns whether it actually did anything. This
is becuase I want to reimplement this in c++, and the neatest way I could
come up with for data descriptors was for the default __set__ to do nothing
and return false, which means "fall back to inserting into the instance
dictionary)
Thanks for the feedback in advance
Ben
---
Code follows:
class Object:
def __init__(self, typeval = None):
self.dict = {}
self.typeval = typeval
def getattribute(self, x):
t = self.lookup(x)
if t:
return t.dget(None, self)
t = self.typeval.lookup(x)
if t:
return t.dget(self, self.typeval)
raise AttributeError(x)
def setattribute(self, x, val):
if self.typeval:
t = self.typeval.lookup(x)
if t:
if t.dset(self, val):
return
self.dict[x] = val
def lookup(self, x):
for obj in self.mro():
t = obj.dict.get(x)
if t:
return t
return None
def mro(self):
return (self,)
def dget(self, object, typeval):
return self
def dset(self, object, val):
return False
def call(self, *args, **kwargs):
raise TypeError("Not callable")
class Value(Object):
def __init__(self, val):
Object.__init__(self)
self.val = val
class Function(Object):
def __init__(self, fun):
Object.__init__(self)
self.fun = fun
def dget(self, object, typeval):
if object:
return BoundFunction(object, self)
else:
return self
def call(self, *args, **kwargs):
return self.fun(*args, **kwargs)
class BoundFunction(Object):
def __init__(self, obj, fun):
Object.__init__(self)
self.fun = fun
self.obj = obj
def call(self, *args, **kwargs):
return self.fun.call(self.obj, *args, **kwargs)
class Type(Object):
def __init__(self):
Object.__init__(self)
def call(self, *args, **kwargs):
obj = Object(self)
return obj
class Property(Object):
def __init__(self, get, set):
Object.__init__(self)
self.get = get
self.set = set
def dget(self, object, typeval):
if object:
return self.get.call(object)
else:
return self
def dset(self, object, val):
self.set.call(object, val)
return True
if __name__ == "__main__":
def addone(self, val):
return val + 1
def getfoo(self):
return self.dict["foo"]
def setfoo(self, val):
self.dict["foo"] = val
test = Type()
test.setattribute("b", Value(2))
test.setattribute("string", Value("foobar"))
test.setattribute("fun", Function(addone))
test.setattribute("getfoo", Function(getfoo))
test.setattribute("setfoo", Function(setfoo))
test.setattribute("foobar", Property(test.getattribute("getfoo"),
test.getattribute("setfoo")))
print test.getattribute("foobar")
inst = test.call()
inst.setattribute("foobar", Value(2))
print test.getattribute("foobar")
print inst.getattribute("foobar").val
inst.setattribute("b", Value(3))
print test.getattribute("b").val
print inst.getattribute("b").val