From 1c4e6def8285e0740461b732c78c74ed3345f524 Mon Sep 17 00:00:00 2001 From: Stefan Israelsson Tampe Date: Mon, 26 Mar 2018 15:56:28 +0200 Subject: functools feature complete --- modules/language/python/module/functools.scm | 449 ++++++++++++++++++++++++++- modules/language/python/module/threading.scm | 36 +++ modules/language/python/module/weakref.scm | 10 + 3 files changed, 494 insertions(+), 1 deletion(-) create mode 100644 modules/language/python/module/threading.scm create mode 100644 modules/language/python/module/weakref.scm (limited to 'modules/language/python') diff --git a/modules/language/python/module/functools.scm b/modules/language/python/module/functools.scm index 0d0311a..484411b 100644 --- a/modules/language/python/module/functools.scm +++ b/modules/language/python/module/functools.scm @@ -1,5 +1,26 @@ +(define-module (language python module functools) + #:use-module (oop pf-objects) + #:use-module (language python for) + #:use-module (language python try) + #:use-module (language python def) + #:use-module (language python module threading) + #:use-module (language python module weakref) + #:export (WRAPPER_ASSIGNMENTS WRAPPER_UPDATES + update_wrapper wraps total_ordering + cmp_to_key partial partialmethod lru_cache + reduce singledispatch)) + +(def (reduce f it (= initializer None)) + (let ((it (iter it)) + (value (if (eq? initializer None) + (next it) + initializer))) + (for ((e : it)) ((value value)) + (f value e) + #:final value))) + (define WRAPPER_ASSIGNMENTS '("__module__" "__name__" "__qualname__" "__doc__" "__annotations__")) @@ -304,7 +325,7 @@ def cmp_to_key(mycmp): (aif it (ref new_func '__self__) (set! result '__self__ it)))))) (if (not result) - ((ref (ref self '_make_unbound_method) '__get__) obj cls) + ((ref ((ref self '_make_unbound_method)) '__get__) obj cls) result)))) (define __isabstractmethod__ @@ -312,3 +333,429 @@ def cmp_to_key(mycmp): (lambda (self) (ref (ref self 'func) '__isabstractmethod__ #f))))) + +(define _CacheInfo (namedtuple "CacheInfo" + '("hits", "misses", "maxsize", "currsize"))) + +(define-python-class _HashedSeq (py-list) + (define __init__ + (lam (self tup (= hash hash)) + ((ref py-list '__init__) self tup) + (set self 'hashvalue (hash tup)))) + + (define __hash__ + (lambda (self) + (ref self 'hashvalue))) + +(def (_make_key args kwds typed + (= kwd_mark (list (object))) + (= fasttypes (py-set (list int str frozenset type(None)))) + (= tuple tuple) + (= type type) + (= len len)) + + (let ((key (py-list args))) + (if (> (len kwds) 0) + (begin + (set! key (+ key kwd_mark)) + (for ((item : (py-items kwds))) () + (set! key (+ key item))))) + + (if typed + (begin + (set! key + (+ key + (for ((a : args)) (l '()) + (cons (type a) l) + #:final (reverse l)))) + (if (bool kwds) + (set! key + (+ key + (for ((v : (py-values kwds))) ((l '())) + (cons (type v) l) + #:final (reverse l)))))) + + (if (and (= (len key) 1) + (in (type (pylist-ref key 0)) fasttypes)) + (pylist-ref key 0) + (_HashedSeq key))))) + +(def (lru_cache (= maxsize 128) (= typed #f)) + "Least-recently-used cache decorator. + + If *maxsize* is set to None, the LRU features are disabled and the cache + can grow without bound. + + If *typed* is True, arguments of different types will be cached separately. + For example, f(3.0) and f(3) will be treated as distinct calls with + distinct results. + + Arguments to the cached function must be hashable. + + View the cache statistics named tuple (hits, misses, maxsize, currsize) + with f.cache_info(). Clear the cache and statistics with f.cache_clear(). + Access the underlying function with f.__wrapped__. + + See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used + + " + + (if (not (or (number? maxsize) (eq? maxsize None))) + (raise TypeError "Expected maxsize to be an integer or None")) + + (lambda (user_function) + (let ((wrapper (_lru_cache_wrapper + user_function maxsize typed _CacheInfo))) + (update_wrapper wrapper user_function)))) + +(define (_lru_cache_wrapper user_function maxsize typed _CacheInfo) + (define sentinel (object)) + (define make_key _make_key) + (define-values (PREV NEXT KEY RESULT) + (values 0 1 2 3)) + (define cache (dict)) + (define-values (hits misses) (values 0 0)) + (define full #f) + (define cache_get cache.get) + (define cache_len cache.__len__) + (define lock (RLock)) + (define root (list 0 0 0 0)) + (list-set! root 0 root) + (list-set! root 1 root) + (list-set! root 2 none) + (list-set! root 3 none) + + (let ((wrapper + (cond + ((eq? maxsize None) + (lam ((* args) (** kwds)) + (let* ((key (make_key args kwds typed)) + (result (cache_get key sentinel))) + (if (not (eq? result sentinel)) + (begin + (set! hits (+ hits 1)) + result) + (let ((result (py-apply user_function + (* args) (** kwds)))) + (pylist-set! cache key result) + (set! misses (+ misses 1)) + result))))) + + ((= maxsize 0) + (lam ((* args) (** kwds)) + (let ((result (py-apply user_function (* args) (** kwds)))) + (set! misses (+ misses 1)) + result))) + + + (else + (lam ((* args) (** kwds)) + (let/ec return + (let* ((key (make_key args kwds typed))) + (with (lock) + (let ((link (cache_get key))) + (when (not (eq? link None)) + (let ((link_prev (list-ref link 0)) + (link_next (list-ref link 1)) + (_key (list-ref link 2)) + (result (list-ref link 3))) + (list-set! link_prev NEXT link_next) + (list-set! link_next PREV link_prev) + (let ((last (list-ref root PREV))) + (list-set! last NEXT link) + (list-set! root PREV link) + (list-set! link PREV last) + (list-set! link NEXT root) + (set! hits (+ hits 1)) + (return result)))))) + + (let ((result (py-apply user_function (* args) (** kwds)))) + (with (lock) + (cond + ((in key cache) (values)) + (full + (let ((oldroot root)) + (list-set! oldroot KEY key) + (list-set! oldroot RESULT result) + (set! root (list-ref oldroot NEXT)) + (let ((oldkey (list-ref root KEY)) + (oldresult (list-ref root RESULT))) + (list-set! root KEY None) + (list-set! root RESULT None) + (pylist-delte! cache oldkey) + (pylist-set! cache key oldroot)))) + (else + (let ((last (list-ref root PREV)) + (link (list last root key result))) + (list-set! last NEXT link) + (list-set! root PREV link) + (pylist-set! cache key link) + (set! full (>= (cache_len) maxsize))))) + (set! misses (+ misses 1))) + result)))))))) + + (define (cache_info) + (with lock + (_CacheInfo hits misses maxsize (cache_len)))) + + (define (cache_clear) + (with lock + (pylist-clear! cache) + (set! root (list #f #f None None)) + (list-set! root 0 root) + (list-set! root 1 root) + (set! hits 0) + (set! misses 0) + (set! full #f))) + + (set wrapper 'cache_info cache_info) + (set! wrapper 'cache_clear cache_clear) + wrapper)) + +;; single dispatch +(define (_c3_merge sequences) + (let lp ((result '())) + (set! sequences (for ((s : sequences)) ((l '())) + (if (bool s) + (cond s l) + l) + #:final (reverse l))) + (if (bool sequences) + (let ((cand + (let lp2 ((s1 sequences)) + (if (pair? s1) + (let ((cand (pylist-ref (car s1) 0))) + (let lp3 ((s2 sequences)) + (if (pair? s2) + (if (in cand (pylist-slice! (car s2) 1 None None)) + (lp2 (cdr s1)) + (lp3 (cdr s2))) + cand))) + (raise RunTimeError "Inconsistant hierarky"))))) + + (let lp ((s sequences)) + (if (pair? s) + (begin + (if (equal? (pylist-ref (car s) +) cand) + (pylist-delete! (car s) 0)) + (lp (cdr s))))) + + (lp (cons cand result))) + + (py-list (reverse result))))) + +(def (_c3_mro cls (= abcs None)) + "Computes the method resolution order using extended C3 linearization. + + If no *abcs* are given, the algorithm works exactly like the built-in C3 + linearization used for method resolution. + + If given, *abcs* is a list of abstract base classes that should be inserted + into the resulting MRO. Unrelated ABCs are ignored and don't end up in the + result. The algorithm inserts ABCs where their functionality is introduced, + i.e. issubclass(cls, abc) returns True for the class itself but returns + False for all its direct base classes. Implicit ABCs for a given class + (either registered or inferred from the presence of a special method like + __len__) are inserted directly after the last ABC explicitly listed in the + MRO of said class. If two implicit ABCs end up next to each other in the + resulting MRO, their ordering depends on the order of types in *abcs*. + + " + (define bases (ref cls '__bases__ '())) + (define boundary + (for ((i base : (enumerate (reversed bases)))) () + (if (hasattr base '__abstractmethods__) + (break (- (len bases) i))) + #:final 0)) + + (define abcs (if (bool abcs) (py-list abcs) (py-list))) + (define explicit_bases (py-list (pylist-slice bases None boundary None))) + (define abstract_bases (py-list)) + (define other_bases (py-list (pylist-slice bases boundary None None))) + + (for ((base : abcs)) () + (if (and (issubclass cls base) + (not (any (map (lambda (b) (issubclass b base)) bases)))) + (pylist-append! abstract_bases base))) + + (for ((base : abstract_bases)) + (pylist-remove! abcs base)) + + (let* ((f (lambda (bases) + (for ((base : bases)) ((l '())) + (cons (_c3_mro base #:abcs abcs) l) + #:final (reverse l)))) + + (explicit_c3_mros (f explicit_bases)) + (abstract_c3_mros (f abstract_bases)) + (other_c3_mros (f other_bases))) + + (_c3_merge + (+ (py-list (py-list cls)) + explicit_c3_mros + abstract_c3_mros + other_c3_mros + (py-list explicit_bases) + (py-lit abstract_bases) + (py-list other_bases))))) + +(define (_compose_mro cls types) + "Calculates the method resolution order for a given class *cls*. + + Includes relevant abstract base classes (with their respective bases) from + the *types* iterable. Uses a modified C3 linearization algorithm. + + " + (define bases (py-set (ref cls '__mro__))) + + ;; Remove entries which are already present in the __mro__ or unrelated. + (define (is_related typ) + (and (not (in typ bases)) + (ref typ '__mro__) + (issubclass cls typ))) + + (define types (for ((n : types)) ((l '())) + (if (is_related n) + (cons n l) + l) + #final (reverse l))) + + ;; Remove entries which are strict bases of other entries (they will end up + ;; in the MRO anyway. + (define (is_strict_base typ) + (for ((other : types)) () + (if (and (not (equal? typ other)) + (in typ (ref other '__mro__ '()))) + (break #t)) + #:final #f)) + + (define types2 (for ((n : types)) ((l '())) + (if (is_strict_base n) + (cons n l) + l) + #final (reverse l))) + + ; Subclasses of the ABCs in *types* which are also implemented by + ; *cls* can be used to stabilize ABC ordering. + (define type_set (py-set types)) + (define mro (py-list)) + + (for ((typ : types)) () + (let ((found (py-list))) + (for ((sub in ((ref typ '__subclasses__ (lambda () '()))))) () + (if (and (not (in sub bases)) + (issubclass cls sub)) + (pylist-append found + (for ((s in (ref sub '__mro__ '()))) + ((l '())) + (if (in s type_set) + (cons s l) + l) + #:final (py-list (reverse l)))))) + (f (not (bool found)) + (begin + (pylist-append! mro typ) + (pylist-sort! found #:key len #:reverse #t) + (for ((sub : found)) () + (for ((subcls : sub)) () + (if (not (in subcls mro)) + (pylist-append! mro subcls)))))))) + + (_c3_mro cls #:abcs mro)) + +(define (_find_impl cls registry) + "Returns the best matching implementation from *registry* for type *cls*. + + Where there is no registered implementation for a specific type, its method + resolution order is used to find a more generic implementation. + + Note: if *registry* does not contain an implementation for the base + *object* type, this function may return None. + + " + (define mro (_compose_mro cls (py-keys registry))) + (define match None) + (define clsmro (ref cls '__mro__ '())) + (for ((t : mro)) () + (if (not (eq? match None)) + (begin + ;; If *match* is an implicit ABC but there is another unrelated, + ;; equally matching implicit ABC, refuse the temptation to guess. + (if (and (in t registry) + (not (in t clsmro)) + (not (in match clsmro)) + (not (issubclass match t))) + (raise RuntimeError + (format #f "Ambiguous dispatch: ~a or ~a" + match t))) + (break))) + (if (in t registry) + (set! match t))) + + (py-get registry match)) + +(define (singledispatch func) + "Single-dispatch generic function decorator. + + Transforms a function into a generic function, which can have different + behaviours depending upon the type of its first argument. The decorated + function acts as the default implementation, and additional + implementations can be registered using the register() attribute of the + generic function. + + " + (define registry (py-set)) + (define dispatch_cache (weak-key-dict)) + (define cache_token None) + + (define (dispatch cls) + "generic_func.dispatch(cls) -> + + Runs the dispatch algorithm to return the best available implementation + for the given *cls* registered on *generic_func*. + + " + + (if (not (eq? cache_token None)) + (let ((current_token (get_cache_token))) + (if (not (equal? cache_token current_token)) + (begin + (pylist-clear! dispatch_cache) + (set! cache_token current_token))))) + + (let ((impl (try + (lambda () (pylist-ref dispatch_cache cls)) + (#:except KeyError => + (lambda x + (_find_impl cls registry)))))) + (pylist-set! dispatch_cache cls impl) + impl)) + + (def (register cls (= func None)) + "generic_func.register(cls, func) -> func + + Registers a new implementation for the given *cls* on a *generic_func*. + + " + (if (eq? func None) + (lambda (f) (register cls f)) + (begin + (pylist-set! registry cls func) + (if (and (eq? cache_token None) + (ref cls '__abstractmethods__)) + (set! cache_token (get_cache_token))) + (pylist-clear! dispatch_cache) + func))) + + (def (wrapper (* args) (** kw)) + (py-apply (dispatch (ref (pylist-ref args 0) '__class__)) + (* args) (** kw))) + + (pylist-set! registry object func) + (set wrapper 'register register) + (set wrapper 'dispatch dispatch) + (set wrapper 'registry registry) + (set wrapper '_clear_cache (ref dispatch_cache 'clear)) + (update_wrapper wrapper func) + + wrapper) diff --git a/modules/language/python/module/threading.scm b/modules/language/python/module/threading.scm new file mode 100644 index 0000000..b4d43c2 --- /dev/null +++ b/modules/language/python/module/threading.scm @@ -0,0 +1,36 @@ +(define-module (language python module threading) + #:use-module (ice-9 threads) + #:use-module (oop pf-objects) + #:use-module (language python def) + #:export (RLock)) + +(define-python-class RLock + (define __init__ + (lambda (self) + (set self '_lock (make-mutex 'recursive)))) + + (define __enter__ + (lambda (self) + (lock-mutex (ref self '_lock)))) + + (define __leave__ + (lambda (self) + (unlock-mutex (ref self '_lock)))) + + + (define acquire + (lam (self (= blocking #t) (timeout -1)) + (if blocking + (if (< timeout 0) + (lock-mutex (ref self '_lock)) + (let* ((cur (gettimeofday)) + (x (+ (car cur) (/ (cdr cur) 1000000.0))) + (y (+ x timeout)) + (s (floor y)) + (us (floor (* (- y s) 1000000)))) + (lock-mutex (ref self '_lock) (cons s us)))) + (try-lock (ref self '_lock))))) + + (define release __leave__)) + + diff --git a/modules/language/python/module/weakref.scm b/modules/language/python/module/weakref.scm new file mode 100644 index 0000000..61f845a --- /dev/null +++ b/modules/language/python/module/weakref.scm @@ -0,0 +1,10 @@ +(define-module (language python module weakref) + #:use-module (language python dict) + #:export (WeakKeyDictionary WeakValueDictionary)) + +(define WeakKeyDictionary weak-key-dict) +(define WeakValueDictionary weak-value-dict) + + + + -- cgit v1.2.3