I need some help with descriptors in python. I wrote an automatic translator from perl to python (Pythonizer) and I'm trying to implement tied scalars, which is basically an object that acts as a scalar, but has FETCH and STORE operations that are called appropriately. I'm using a dynamic class namespace 'main' to store the variable values. I'm attempting to define __get__
and __set__
operations for the object, but they are not working. Any help will be appreciated!
main = type('main', tuple(), dict())
class TiedScalar(dict): # Generated code - can't be changed
FETCH_CALLED_v = 0
STORE_CALLED_v = 0
def STORE(*_args):
_args = list(_args)
self = _args.pop(0) if _args else None
TiedScalar.STORE_CALLED_v = TiedScalar.STORE_CALLED_v + 1
self["value"] = _args.pop(0) if _args else None
return self.get("value")
#TiedScalar.STORE = lambda *_args, **_kwargs: perllib.tie_call(STORE, _args, _kwargs)
def FETCH(*_args):
_args = list(_args)
self = _args.pop(0) if _args else None
TiedScalar.FETCH_CALLED_v = TiedScalar.FETCH_CALLED_v + 1
return self.get("value")
#TiedScalar.FETCH = lambda *_args, **_kwargs: perllib.tie_call(FETCH, _args, _kwargs)
@classmethod
def TIESCALAR(*_args):
_args = list(_args)
class_ = _args.pop(0) if _args else None
self = {"value": (_args.pop(0) if _args else None)}
#self = perllib.bless(self, class_)
#return perllib.add_tie_methods(self)
return add_tie_methods(class_(self))
def __init__(self, d):
for k, v in d.items():
self[k] = v
#setattr(self, k, v)
def add_tie_methods(obj): # This code is part of perllib and can be changed
cls = obj.__class__
classname = cls.__name__
result = type(classname, (cls,), dict())
cls.__TIE_subclass__ = result
def __get__(self, obj, objtype=None):
return self.FETCH()
result.__get__ = __get__
def __set__(self, obj, value):
return self.STORE(value)
result.__set__ = __set__
obj.__class__ = result
return obj
main.tied_scalar_v = TiedScalar.TIESCALAR(42) # Generated code
assert main.tied_scalar_v == 42
main.tied_scalar_v = 100
assert main.tied_scalar_v == 100
print(TiedScalar.FETCH_CALLED_v)
print(TiedScalar.STORE_CALLED_v)
Here the 2 print statements print 1 and 0, so esp STORE is not being called, and fetch is not being called enough times based on the code. Note that 'main' stores all of the user's variables, also the TiedScalar class is (mostly) generated from the user’s perl code.
Ok - with your help, I was able to figure out how to do it. Basically I have to create a metaclass for my main
, then use that class to store the initial object for tied scalars. Here is my updated code with changes marked # new
:
meta = type('mainmeta', (type,), {
'__init__': lambda cls, name, bases, attrs: type.__init__(cls, name, bases, attrs)
}) # new
#main = type('main', tuple(), dict())
main = meta('main', tuple(), dict()) # new
class TiedScalar(dict): # Generated code - can't be changed
FETCH_CALLED_v = 0
STORE_CALLED_v = 0
def STORE(*_args):
_args = list(_args)
self = _args.pop(0) if _args else None
TiedScalar.STORE_CALLED_v = TiedScalar.STORE_CALLED_v + 1
self["value"] = _args.pop(0) if _args else None
return self.get("value")
#TiedScalar.STORE = lambda *_args, **_kwargs: perllib.tie_call(STORE, _args, _kwargs)
def FETCH(*_args):
_args = list(_args)
self = _args.pop(0) if _args else None
TiedScalar.FETCH_CALLED_v = TiedScalar.FETCH_CALLED_v + 1
return self.get("value")
#TiedScalar.FETCH = lambda *_args, **_kwargs: perllib.tie_call(FETCH, _args, _kwargs)
@classmethod
def TIESCALAR(*_args):
_args = list(_args)
class_ = _args.pop(0) if _args else None
self = {"value": (_args.pop(0) if _args else None)}
#self = perllib.bless(self, class_)
#return perllib.add_tie_methods(self)
return add_tie_methods(class_(self))
def __init__(self, d):
for k, v in d.items():
self[k] = v
#setattr(self, k, v)
def add_tie_methods(obj): # This code is part of perllib and can be changed
cls = obj.__class__
classname = cls.__name__
result = type(classname, (cls,), dict())
cls.__TIE_subclass__ = result
def __get__(self, obj, objtype=None):
#print(f'__get__({self}, {obj}, {objtype})')
return self.FETCH()
result.__get__ = __get__
def __set__(self, obj, value):
#print(f'__set__({self}, {obj}, {value})')
return self.STORE(value)
result.__set__ = __set__
obj.__class__ = result
return obj
def assign_meta(cls, var, value): # new
meta = cls.__class__
setattr(meta, var, value)
return value
# main.tied_scalar_v = TiedScalar.TIESCALAR(42)
assign_meta(main, 'tied_scalar_v', TiedScalar.TIESCALAR(42)) # new
assert main.tied_scalar_v == 42
main.tied_scalar_v = 100
assert main.tied_scalar_v == 100
print(TiedScalar.FETCH_CALLED_v)
print(TiedScalar.STORE_CALLED_v)