Skip to content

Instantly share code, notes, and snippets.

@justanr
Last active June 15, 2016 02:34
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 justanr/d045460e318298c6f7f16534b46d9451 to your computer and use it in GitHub Desktop.
Save justanr/d045460e318298c6f7f16534b46d9451 to your computer and use it in GitHub Desktop.
Proof of Concept for adding kinda Implicits to Python
class Thing:
def r(self):
pass
class HasWhatever(metaclass=ImplicitMeta, companion=Thing):
whatever = lambda s: print(s, 1)
@property
def nine(self):
return 'nine'
@implicit(Thing)
def name(inst):
print("Getting name: ", inst.__class__.__name__)
class T:
a = 5
def wut(*args):
print("implicit wut", args)
@classmethod
def w(cls):
print(cls)
@implicit(Thing, name='huh')
@staticmethod
def huh():
print('huh?')
implicit(Thing)(T())
inst = Thing()
inst.wut()
inst.name()
inst.whatever()
print(inst.a)
print(inst.nine)
inst.w()
inst.huh()
from types import FunctionType
from functools import wraps, update_wrapper
def maybe_static(f, inst):
@wraps(f)
def tryer(*a, **k):
try:
return f(inst, *a, **k)
except TypeError:
return f(*a, **k)
return tryer
class GetAttr:
"""
Replaces the normal __getattr__ on a class with a descriptor that looks
through a series of registered implicits and attempts to pull the desired
method off of them or re-raise the AttributeError.
"""
def __init__(self, getattr):
self._getattr = getattr
self._look_at = []
def add_implicit(self, type):
self._look_at.append(type)
def __call__(self, inst, attr):
try:
return self._getattr(inst, attr)
except AttributeError:
for implicit in self._look_at:
if hasattr(implicit, attr):
attr = getattr(implicit, attr)
if hasattr(attr, '__func__'):
return update_wrapper(lambda *a, **k: attr(*a, **k), attr)
if callable(attr):
return maybe_static(attr, inst)
elif hasattr(attr, '__get__'):
return attr.__get__(inst, type(inst))
else:
return attr
else:
raise
def no_attr(inst, attr):
"Apparently __getattr__ doesn't exist until defined"
raise AttributeError("type object '{}' has no attribute '{}'".format(type(inst).__name__, attr))
def getter(getter):
"""Transforms an instance of GetAttr into a function that can be bound to a class"""
@wraps(getter, assigned=['add_implicit'])
def g(inst, attr):
return getter(inst, attr)
return g
def make_implicit(companion):
"""
Helper function to transform the __getattr__ of a class
into a GetAttr descriptor.
"""
if not hasattr(companion, '__getattr__'):
companion.__getattr__ = getter(GetAttr(no_attr))
elif not hasattr(companion.__getattr__, 'add_implicit'):
companion.__getattr__ = getter(GetAttr(companion.__getattr__))
class ImplicitMeta(type):
"""
Automatically registers a class as an implicit to another class.
"""
def __new__(mcls, name, bases, attrs, companion):
cls = super().__new__(mcls, name, bases, attrs)
make_implicit(companion)
companion.__getattr__.add_implicit(cls)
return cls
def __init__(mcls, name, bases, attrs, companion):
super().__init__(name, bases, attrs)
class implicit:
"""
Decorator/callable to register a function, class or object as an implicit
on another class.
"""
_ANON_PATTERN = 'AnonImplicit[{}]'
def __init__(self, companion, name=None):
self._companion = companion
self._name = name
def __call__(self, implicit):
if isinstance(implicit, (staticmethod, classmethod, property, FunctionType)):
self._func(implicit)
elif isinstance(implicit, type):
self._type(implicit)
else:
self._inst(implicit)
return implicit
def _func(self, implicit):
name = self._name or implicit.__name__
if name == '<lambda>':
raise ValueError("Provide name to constructor when providing lambda")
ImplicitMeta(self._ANON_PATTERN.format(name), (object,), {name: implicit}, self._companion)
def _type(self, implicit):
name = self._name or implicit.__name__
ImplicitMeta(self._ANON_PATTERN.format(name), (implicit,), {}, self._companion)
def _inst(self, implicit):
make_implicit(self._companion)
self._companion.__getattr__.add_implicit(implicit)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment