Skip to content

Instantly share code, notes, and snippets.

@jsborjesson
Created January 18, 2014 21:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jsborjesson/8496924 to your computer and use it in GitHub Desktop.
Save jsborjesson/8496924 to your computer and use it in GitHub Desktop.
Python descriptors made easy
import re
from weakref import WeakKeyDictionary
class AdvancedDescriptor(object):
"""
Base class for descriptors, is hard to understand but works for most cases.
from https://www.youtube.com/watch?v=P92z7m-kZpc
"""
def __init__(self, name=None):
self.name = self.mangle(name)
def __get__(self, instance, owner):
if instance is None:
raise AttributeError('Can only be accessed from instance')
if self.name not in instance.__dict__:
raise AttributeError
return instance.__dict__[self.name]
def __set__(self, instance, value):
if self.name == None: self.fetchattr(instance)
instance.__dict__[self.name] = value
def __delete__(self, instance):
del instance.__dict__[self.name]
def fetchattr(self, instance):
for attr in instance.__class__.__dict__:
if attr.startswith('__'): continue
obj = instance.__class__.__dict__[attr]
if obj == self:
self.name = self.mangle(attr)
break
def mangle(self, name):
"""Mangle name to __name."""
if name != None:
if name.startswith('__'):
raise AttributeError('Name conflict')
elif name.startswith('_'):
return '_' + name
else:
return '__' + name
return name
class ValidationDescriptor(object):
"""Simple base descriptor with support for validation."""
def __init__(self, default=None):
self.default = default
self.data = WeakKeyDictionary()
def __get__(self, instance, owner):
return self.data.get(instance, self.default)
def __set__(self, instance, value):
self.data[instance] = self.validate(value)
def __delete__(self, instance):
del self.data[instance]
def validate(self, value):
"""Can either raise an exception or return a valid value."""
raise NotImplementedError('You need to implement a validate method')
class RegexValidator(ValidationDescriptor):
"""Descriptor that validates a regex when set."""
def __init__(self, regex=None):
self.regex = re.compile(regex) if isinstance(regex, str) else regex
super(RegexValidator, self).__init__()
def validate(self, value):
if self.regex.match(value) != None:
return value
else:
raise TypeError('"{}" is not valid.'.format(value))
class Person(object):
email = RegexValidator(r'^(\w*)@(\w*)\.(\w{3})$')
def __init__(self, name, email):
self.name = name
self.email = email
def __str__(self):
return '{}: {}'.format(self.name, self.email)
# demo
if __name__ == '__main__':
d = Person('Alice', 'alice@email.com')
print(d)
try:
p = Person('Wallace', 'notanemail.com')
except Exception as e:
print(e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment