Last active
October 8, 2016 05:29
-
-
Save lozybean/431a8829e09bf5050c02a436360e6567 to your computer and use it in GitHub Desktop.
py3meta from David pycon2013.
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
# execly.py | |
# | |
# Example of generating code and executing it with exec() | |
# in the context of descriptors/metaclasses | |
from inspect import Parameter, Signature | |
import re | |
import time | |
from collections import OrderedDict | |
# Utility functions | |
def _make_init(fields): | |
''' | |
Give a list of field names, make an __init__ method | |
''' | |
code = 'def __init__(self, %s):\n' % \ | |
','.join(fields) | |
for name in fields: | |
code += ' self.%s = %s\n' % (name, name) | |
return code | |
def _make_setter(dcls): | |
code = 'def __set__(self, instance, value):\n' | |
for d in dcls.__mro__: | |
if 'set_code' in d.__dict__: | |
for line in d.set_code(): | |
code += ' ' + line + '\n' | |
return code | |
class DescriptorMeta(type): | |
def __init__(self, clsname, bases, clsdict): | |
if '__set__' not in clsdict: | |
code = _make_setter(self) | |
exec(code, globals(), clsdict) | |
setattr(self, '__set__', clsdict['__set__']) | |
else: | |
raise TypeError('Define set_code(), not __set__()') | |
class Descriptor(metaclass=DescriptorMeta): | |
def __init__(self, name=None): | |
self.name = name | |
@staticmethod | |
def set_code(): | |
return [ | |
'instance.__dict__[self.name] = value' | |
] | |
def __delete__(self, instance): | |
raise AttributeError("Can't delete") | |
class Typed(Descriptor): | |
ty = object | |
@staticmethod | |
def set_code(): | |
return [ | |
'if not isinstance(value, self.ty):', | |
' raise TypeError("Expected %s" % self.ty)' | |
] | |
# Specialized types | |
class Integer(Typed): | |
ty = int | |
class Float(Typed): | |
ty = float | |
class String(Typed): | |
ty = str | |
# Value checking | |
class Positive(Descriptor): | |
@staticmethod | |
def set_code(): | |
return [ | |
'if value < 0:', | |
' raise ValueError("Expected >= 0")', | |
] | |
super().__set__(instance, value) | |
# More specialized types | |
class PosInteger(Integer, Positive): | |
pass | |
class PosFloat(Float, Positive): | |
pass | |
# Length checking | |
class Sized(Descriptor): | |
def __init__(self, *args, maxlen, **kwargs): | |
self.maxlen = maxlen | |
super().__init__(*args, **kwargs) | |
@staticmethod | |
def set_code(): | |
return [ | |
'if len(value) > self.maxlen:', | |
' raise ValueError("Too big")', | |
] | |
class SizedString(String, Sized): | |
pass | |
# Pattern matching | |
class Regex(Descriptor): | |
def __init__(self, *args, pat, **kwargs): | |
self.pat = re.compile(pat) | |
super().__init__(*args, **kwargs) | |
@staticmethod | |
def set_code(): | |
return [ | |
'if not self.pat.match(value):', | |
' raise ValueError("Invalid string")', | |
] | |
class SizedRegexString(SizedString, Regex): | |
pass | |
# Structure definition code | |
class StructMeta(type): | |
@classmethod | |
def __prepare__(cls, name, bases): | |
return OrderedDict() | |
def __new__(cls, clsname, bases, clsdict): | |
fields = [key for key, val in clsdict.items() | |
if isinstance(val, Descriptor) ] | |
for name in fields: | |
clsdict[name].name = name | |
# Make the init function | |
if fields: | |
exec(_make_init(fields), globals(), clsdict) | |
clsobj = super().__new__(cls, clsname, bases, dict(clsdict)) | |
setattr(clsobj, '_fields', fields) | |
return clsobj | |
class Structure(metaclass=StructMeta): | |
pass | |
class Stock(Structure): | |
name = SizedRegexString(maxlen=8, pat='[A-Z]+$') | |
shares = PosInteger() | |
price = PosFloat() | |
@profile | |
def test(): | |
s = Stock('ACME', 50, 91.1) | |
s.price | |
s.price = 10.0 | |
s.name = 'ACME' | |
if __name__ == '__main__': | |
test() |
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
# importly.py | |
# | |
# Addition of an import hook to make it possible to | |
# import XML-structure specifications directly | |
from inspect import Parameter, Signature | |
import re | |
from collections import OrderedDict | |
from xml.etree.ElementTree import parse | |
import os | |
# Utility functions | |
def _make_init(fields): | |
''' | |
Give a list of field names, make an __init__ method | |
''' | |
code = 'def __init__(self, %s):\n' % \ | |
','.join(fields) | |
for name in fields: | |
code += ' self.%s = %s\n' % (name, name) | |
return code | |
def _make_setter(dcls): | |
code = 'def __set__(self, instance, value):\n' | |
for d in dcls.__mro__: | |
if 'set_code' in d.__dict__: | |
for line in d.set_code(): | |
code += ' ' + line + '\n' | |
return code | |
class DescriptorMeta(type): | |
def __init__(self, clsname, bases, clsdict): | |
if '__set__' not in clsdict: | |
code = _make_setter(self) | |
exec(code, globals(), clsdict) | |
setattr(self, '__set__', clsdict['__set__']) | |
else: | |
raise TypeError('Define set_code(), not __set__()') | |
class Descriptor(metaclass=DescriptorMeta): | |
def __init__(self, name=None): | |
self.name = name | |
@staticmethod | |
def set_code(): | |
return [ | |
'instance.__dict__[self.name] = value' | |
] | |
def __delete__(self, instance): | |
raise AttributeError("Can't delete") | |
class Typed(Descriptor): | |
ty = object | |
@staticmethod | |
def set_code(): | |
return [ | |
'if not isinstance(value, self.ty):', | |
' raise TypeError("Expected %s" % self.ty)' | |
] | |
# Specialized types | |
class Integer(Typed): | |
ty = int | |
class Float(Typed): | |
ty = float | |
class String(Typed): | |
ty = str | |
# Value checking | |
class Positive(Descriptor): | |
@staticmethod | |
def set_code(): | |
return [ | |
'if value < 0:', | |
' raise ValueError("Expected >= 0")', | |
] | |
super().__set__(instance, value) | |
# More specialized types | |
class PosInteger(Integer, Positive): | |
pass | |
class PosFloat(Float, Positive): | |
pass | |
# Length checking | |
class Sized(Descriptor): | |
def __init__(self, *args, maxlen, **kwargs): | |
self.maxlen = maxlen | |
super().__init__(*args, **kwargs) | |
@staticmethod | |
def set_code(): | |
return [ | |
'if len(value) > self.maxlen:', | |
' raise ValueError("Too big")', | |
] | |
class SizedString(String, Sized): | |
pass | |
# Pattern matching | |
class Regex(Descriptor): | |
def __init__(self, *args, pat, **kwargs): | |
self.pat = re.compile(pat) | |
super().__init__(*args, **kwargs) | |
@staticmethod | |
def set_code(): | |
return [ | |
'if not self.pat.match(value):', | |
' raise ValueError("Invalid string")', | |
] | |
class SizedRegexString(SizedString, Regex): | |
pass | |
# Structure definition code | |
class StructMeta(type): | |
@classmethod | |
def __prepare__(cls, name, bases): | |
return OrderedDict() | |
def __new__(cls, clsname, bases, clsdict): | |
fields = [key for key, val in clsdict.items() | |
if isinstance(val, Descriptor) ] | |
for name in fields: | |
clsdict[name].name = name | |
# Make the init function | |
if fields: | |
exec(_make_init(fields), globals(), clsdict) | |
clsobj = super().__new__(cls, clsname, bases, dict(clsdict)) | |
setattr(clsobj, '_fields', fields) | |
return clsobj | |
class Structure(metaclass=StructMeta): | |
pass | |
# Import hooks | |
def _xml_to_code(filename): | |
doc = parse(filename) | |
code = 'import importly as _i\n' | |
for st in doc.findall('structure'): | |
code += _xml_struct_code(st) | |
return code | |
def _xml_struct_code(st): | |
stname = st.get('name') | |
code = 'class %s(_i.Structure):\n' % stname | |
for field in st.findall('field'): | |
name = field.text.strip() | |
dtype = '_i.' + field.get('type') | |
kwargs = ', '.join('%s=%s' % (key, val) | |
for key, val in field.items() | |
if key != 'type') | |
code += ' %s = %s(%s)\n' % (name, dtype, kwargs) | |
return code | |
class StructImporter: | |
def __init__(self, path): | |
self._path = path | |
def find_module(self, fullname, path=None): | |
name = fullname.rpartition('.')[-1] | |
if path is None: | |
path = self._path | |
for dn in path: | |
filename = os.path.join(dn, name+'.xml') | |
if os.path.exists(filename): | |
return StructXMLLoader(filename) | |
return None | |
import imp | |
class StructXMLLoader: | |
def __init__(self, filename): | |
self._filename = filename | |
def load_module(self, fullname): | |
mod = sys.modules.setdefault(fullname, | |
imp.new_module(fullname)) | |
mod.__file__ = self._filename | |
mod.__loader__ = self | |
code = _xml_to_code(self._filename) | |
exec(code, mod.__dict__, mod.__dict__) | |
return mod | |
import sys | |
def install_importer(path=sys.path): | |
sys.meta_path.append(StructImporter(path)) | |
if __name__ == '__main__': | |
install_importer() | |
import datadefs | |
s = datadefs.Stock('GOOG', 100, 490.1) | |
p = datadefs.Point(2,3) | |
h = datadefs.Address('www.python.org', 80) |
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
# typely.py | |
# | |
# Example of defining descriptors to customize attribute access. | |
from inspect import Parameter, Signature | |
import re | |
import time | |
from collections import OrderedDict | |
class Descriptor: | |
def __init__(self, name=None): | |
self.name = name | |
def __set__(self, instance, value): | |
instance.__dict__[self.name] = value | |
def __delete__(self, instance): | |
raise AttributeError("Can't delete") | |
class Typed(Descriptor): | |
ty = object | |
def __set__(self, instance, value): | |
if not isinstance(value, self.ty): | |
raise TypeError('Expected %s' % self.ty) | |
super().__set__(instance, value) | |
# Specialized types | |
class Integer(Typed): | |
ty = int | |
class Float(Typed): | |
ty = float | |
class String(Typed): | |
ty = str | |
# Value checking | |
class Positive(Descriptor): | |
def __set__(self, instance, value): | |
if value < 0: | |
raise ValueError('Expected >= 0') | |
super().__set__(instance, value) | |
# More specialized types | |
class PosInteger(Integer, Positive): | |
pass | |
class PosFloat(Float, Positive): | |
pass | |
# Length checking | |
class Sized(Descriptor): | |
def __init__(self, *args, maxlen, **kwargs): | |
self.maxlen = maxlen | |
super().__init__(*args, **kwargs) | |
def __set__(self, instance, value): | |
if len(value) > self.maxlen: | |
raise ValueError('Too big') | |
super().__set__(instance, value) | |
class SizedString(String, Sized): | |
pass | |
# Pattern matching | |
class Regex(Descriptor): | |
def __init__(self, *args, pat, **kwargs): | |
self.pat = re.compile(pat) | |
super().__init__(*args, **kwargs) | |
def __set__(self, instance, value): | |
if not self.pat.match(value): | |
raise ValueError('Invalid string') | |
super().__set__(instance, value) | |
class SizedRegexString(SizedString, Regex): | |
pass | |
# Structure definition code | |
def make_signature(names): | |
return Signature( | |
Parameter(name, Parameter.POSITIONAL_OR_KEYWORD) | |
for name in names) | |
class StructMeta(type): | |
@classmethod | |
def __prepare__(cls, name, bases): | |
return OrderedDict() | |
def __new__(cls, clsname, bases, clsdict): | |
fields = [key for key, val in clsdict.items() | |
if isinstance(val, Descriptor) ] | |
for name in fields: | |
clsdict[name].name = name | |
clsobj = super().__new__(cls, clsname, bases, dict(clsdict)) | |
sig = make_signature(fields) | |
setattr(clsobj, '__signature__', sig) | |
return clsobj | |
class Structure(metaclass=StructMeta): | |
def __init__(self, *args, **kwargs): | |
bound = self.__signature__.bind(*args, **kwargs) | |
for name, val in bound.arguments.items(): | |
setattr(self, name, val) | |
class stock(structure): | |
name = sizedregexstring(maxlen=8, pat='[a-z]+$') | |
shares = posinteger() | |
price = posfloat() | |
@profile | |
def test(): | |
s = stock('acme', 50, 91.1) | |
s.price | |
s.price = 10.0 | |
s.name = 'acme' | |
if __name__ == '__main__': | |
test() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment