Created
January 18, 2014 21:38
-
-
Save jsborjesson/8496924 to your computer and use it in GitHub Desktop.
Python descriptors made easy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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