Created
July 3, 2017 06:25
-
-
Save 20chan/c2f66ac2740f8921017bbd5f47a7eb08 to your computer and use it in GitHub Desktop.
The Fun of Reinvention 최종
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
_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})' |
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
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