Skip to content

Instantly share code, notes, and snippets.

@20chan
Created July 3, 2017 06:25
Show Gist options
  • Save 20chan/c2f66ac2740f8921017bbd5f47a7eb08 to your computer and use it in GitHub Desktop.
Save 20chan/c2f66ac2740f8921017bbd5f47a7eb08 to your computer and use it in GitHub Desktop.
The Fun of Reinvention 최종
_contracts = {}
class Contract:
@classmethod
def __init_subclass__(cls):
_contracts[cls.__name__] = cls
def __set__(self, instance, value):
self.check(value)
instance.__dict__[self.name] = value
def __set_name__(self, owner, name):
self.name = name
@classmethod
def check(cls, value):
pass
class Typed(Contract):
@classmethod
def check(cls, value):
assert isinstance(value, cls.type), f'Expected {cls.type}'
super().check(value)
i = 1
class Integer(Typed):
type = int
class Float(Typed):
type = float
class String(Typed):
type = str
class Positive(Contract):
@classmethod
def check(cls, value):
assert value > 0, 'Must be > 0'
super().check(value)
class PositiveInteger(Integer, Positive):
pass
class NonEmpty(Contract):
@classmethod
def check(cls, value):
assert len(value) > 0, 'Must be nonempty'
super().check(value)
class NonEmptyString(String, NonEmpty):
pass
from functools import wraps
from inspect import signature
def checked(func):
sig = signature(func)
ann = ChainMap(
func.__annotations__,
func.__globals__.get('__annotations__'), {}
)
@wraps(func)
def wrapper(*args, **kwargs):
bound = sig.bind(*args, **kwargs)
for name, val in bound.arguments.items():
if name in ann:
ann[name].check(val)
return func(*args, **kwargs)
return wrapper
from collections import ChainMap
class BaseMeata(type):
def __prepare__(cls, *args):
return ChainMap({}, _contracts)
def __new__(meta, name, bases, methods):
methods = methods.maps[0]
return super().__new__(meta, name, bases, methods)
class Base(metaclass=BaseMeata):
@classmethod
def __init_subclass__(cls):
for name, val in cls.__dict__.items():
if callable(val):
setattr(cls, name, checked(val))
for name, val in cls.__annotations__.items():
contract = val()
contract.__set_name__(cls, name)
setattr(cls, name, contract)
def __init__(self, *args):
ann = self.__annotations__
assert len(ann) == len(args), f'Expected {len(ann)} arguments'
for name, val in zip(ann, args):
setattr(self, name, val)
def __repr__(self):
args = ', '.join(repr(getattr(self, name)) for name in self.__annotations__)
return f'{type(self).__name__}({args})'
from contract import Base, PositiveInteger
dx: PositiveInteger
class Player(Base):
name: NonEmptyString
x: Integer
y: Integer
def left(self, dx):
self.x -= dx
def right(self, dx):
self.x += dx
p = Player('아드', 0, 0)
p.left(-1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment