Skip to content

Instantly share code, notes, and snippets.

@ugovaretto
Last active August 17, 2020 15:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ugovaretto/20f85d6154d43f33c67a3fc1efd6100d to your computer and use it in GitHub Desktop.
Save ugovaretto/20f85d6154d43f33c67a3fc1efd6100d to your computer and use it in GitHub Desktop.
Python instance descriptors
### Per-instance descriptors
### Author: Ugo Varetto
### for standard basic descriptors see:
### https://www.datacamp.com/community/tutorials/python-descriptors
### Instance descriptor through overloading __getattribute__, __setattr__
def make_getattr(cls, n):
def custom_getattr(self, name):
get = lambda i, n: type(i).__bases__[0].__getattribute__(i, n)
v = get(self, name)
return get(v, n) if isinstance(v, cls) else v
return custom_getattr
def make_setattr(cls, n, validate):
def custom_setattr(self, name, value):
get = lambda i, n: type(i).__bases__[0].__getattribute__(i, n)
set = lambda i, n, v: type(i).__bases__[0].__setattr__(i, n, v)
v = get(self, name)
if isinstance(v, cls):
validate(v)
set(v, n, value)
else:
set(self, name, value)
return custom_setattr
class Descriptor(object):
def __init__(self, instance, data=None, validate=lambda v: None):
self.data = data
validate(self)
type(instance).__getattribute__=make_getattr(Descriptor, 'data')
type(instance).__setattr__=make_setattr(Descriptor, 'data', validate)
class Car:
def setattr(self, name, value):
super.__setattr__(self, name, value)
# cannot use direct assignment because will call overloaded __setattr__
def __init__(self,make,model,fuel_cap):
self.setattr('make', make)
self.setattr('model', model)
self.setattr('fuel_cap', None)
def validate(v):
if v.data <= 0:
raise ValueError(f"{self} fuel_cap <= 0")
self.setattr('fuel_cap', Descriptor(self, fuel_cap, validate))
def __str__(self):
return f"Make: {self.make}, Model: {self.model}, Fuel capacity: {self.fuel_cap}"
### Instance descriptor through instance map, value is
### indexed by id(instance)
import threading
class InstanceDescriptor(object):
def __init__(self, validate = lambda x: None):
self.dict = dict()
self.validate = validate
def __get__(self, instance, type=None):
return self.dict[id(instance)]
def __set__(self, instance, value):
self.validate(self.__name, value)
with self.lock:
self.dict[id(instance)] = value
def __delete__(self, instance):
with self.lock:
del self.dict[id(instance)]
def __set_name__(self, owner, name):
self.__name = name
def typevalidator(t):
def validate(name, v):
if not isinstance(v, t):
raise TypeError(
f"expected '{name}' of type {t} got {type(v)} instead")
return validate
def andvalidator(*args):
def validate(name, v):
for f in args:
f(name, v)
return validate
def rangevalidator(mi, mx):
def validate(name, v):
if v < mi or v > mx:
raise ValueError(
f"'{name}' = {v} out of bounds ({mi}, {mx})")
return validate
class DescContainer(object):
name = InstanceDescriptor(typevalidator(str))
age = InstanceDescriptor(andvalidator(typevalidator(int),
rangevalidator(18, 65)))
def main():
car1 = Car("Toyota", "Corolla", 40)
print(car1)
car2 = Car("Fiat", "Cinquecento", 30)
print(car2)
#print(car1)
d = DescContainer()
d.name = "ciao"
c = DescContainer()
c.name = "hello"
print(d.name)
c.age = '3'
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment