| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- """
- Decorator module by Michele Simionato <michelesimionato@libero.it>
- Copyright Michele Simionato, distributed under the terms of the BSD License (see below).
- http://www.phyast.pitt.edu/~micheles/python/documentation.html
- Included in NLTK for its support of a nice memoization decorator.
- """
- __docformat__ = "restructuredtext en"
- ## The basic trick is to generate the source code for the decorated function
- ## with the right signature and to evaluate it.
- ## Uncomment the statement 'print >> sys.stderr, func_src' in _decorator
- ## to understand what is going on.
- __all__ = ["decorator", "new_wrapper", "getinfo"]
- import sys
- # Hack to keep NLTK's "tokenize" module from colliding with the "tokenize" in
- # the Python standard library.
- OLD_SYS_PATH = sys.path[:]
- sys.path = [p for p in sys.path if p and "nltk" not in p]
- import inspect
- sys.path = OLD_SYS_PATH
- def __legacysignature(signature):
- """
- For retrocompatibility reasons, we don't use a standard Signature.
- Instead, we use the string generated by this method.
- Basically, from a Signature we create a string and remove the default values.
- """
- listsignature = str(signature)[1:-1].split(",")
- for counter, param in enumerate(listsignature):
- if param.count("=") > 0:
- listsignature[counter] = param[0:param.index("=")].strip()
- else:
- listsignature[counter] = param.strip()
- return ", ".join(listsignature)
- def getinfo(func):
- """
- Returns an info dictionary containing:
- - name (the name of the function : str)
- - argnames (the names of the arguments : list)
- - defaults (the values of the default arguments : tuple)
- - signature (the signature : str)
- - fullsignature (the full signature : Signature)
- - doc (the docstring : str)
- - module (the module name : str)
- - dict (the function __dict__ : str)
- >>> def f(self, x=1, y=2, *args, **kw): pass
- >>> info = getinfo(f)
- >>> info["name"]
- 'f'
- >>> info["argnames"]
- ['self', 'x', 'y', 'args', 'kw']
- >>> info["defaults"]
- (1, 2)
- >>> info["signature"]
- 'self, x, y, *args, **kw'
- >>> info["fullsignature"]
- <Signature (self, x=1, y=2, *args, **kw)>
- """
- assert inspect.ismethod(func) or inspect.isfunction(func)
- argspec = inspect.getfullargspec(func)
- regargs, varargs, varkwargs = argspec[:3]
- argnames = list(regargs)
- if varargs:
- argnames.append(varargs)
- if varkwargs:
- argnames.append(varkwargs)
- fullsignature = inspect.signature(func)
- # Convert Signature to str
- signature = __legacysignature(fullsignature)
- # pypy compatibility
- if hasattr(func, "__closure__"):
- _closure = func.__closure__
- _globals = func.__globals__
- else:
- _closure = func.func_closure
- _globals = func.func_globals
- return dict(
- name=func.__name__,
- argnames=argnames,
- signature=signature,
- fullsignature=fullsignature,
- defaults=func.__defaults__,
- doc=func.__doc__,
- module=func.__module__,
- dict=func.__dict__,
- globals=_globals,
- closure=_closure,
- )
- def update_wrapper(wrapper, model, infodict=None):
- " akin to functools.update_wrapper "
- infodict = infodict or getinfo(model)
- wrapper.__name__ = infodict["name"]
- wrapper.__doc__ = infodict["doc"]
- wrapper.__module__ = infodict["module"]
- wrapper.__dict__.update(infodict["dict"])
- wrapper.__defaults__ = infodict["defaults"]
- wrapper.undecorated = model
- return wrapper
- def new_wrapper(wrapper, model):
- """
- An improvement over functools.update_wrapper. The wrapper is a generic
- callable object. It works by generating a copy of the wrapper with the
- right signature and by updating the copy, not the original.
- Moreovoer, 'model' can be a dictionary with keys 'name', 'doc', 'module',
- 'dict', 'defaults'.
- """
- if isinstance(model, dict):
- infodict = model
- else: # assume model is a function
- infodict = getinfo(model)
- assert (
- not "_wrapper_" in infodict["argnames"]
- ), '"_wrapper_" is a reserved argument name!'
- src = "lambda %(signature)s: _wrapper_(%(signature)s)" % infodict
- funcopy = eval(src, dict(_wrapper_=wrapper))
- return update_wrapper(funcopy, model, infodict)
- # helper used in decorator_factory
- def __call__(self, func):
- return new_wrapper(lambda *a, **k: self.call(func, *a, **k), func)
- def decorator_factory(cls):
- """
- Take a class with a ``.caller`` method and return a callable decorator
- object. It works by adding a suitable __call__ method to the class;
- it raises a TypeError if the class already has a nontrivial __call__
- method.
- """
- attrs = set(dir(cls))
- if "__call__" in attrs:
- raise TypeError(
- "You cannot decorate a class with a nontrivial " "__call__ method"
- )
- if "call" not in attrs:
- raise TypeError("You cannot decorate a class without a " ".call method")
- cls.__call__ = __call__
- return cls
- def decorator(caller):
- """
- General purpose decorator factory: takes a caller function as
- input and returns a decorator with the same attributes.
- A caller function is any function like this::
- def caller(func, *args, **kw):
- # do something
- return func(*args, **kw)
- Here is an example of usage:
- >>> @decorator
- ... def chatty(f, *args, **kw):
- ... print("Calling %r" % f.__name__)
- ... return f(*args, **kw)
- >>> chatty.__name__
- 'chatty'
- >>> @chatty
- ... def f(): pass
- ...
- >>> f()
- Calling 'f'
- decorator can also take in input a class with a .caller method; in this
- case it converts the class into a factory of callable decorator objects.
- See the documentation for an example.
- """
- if inspect.isclass(caller):
- return decorator_factory(caller)
- def _decorator(func): # the real meat is here
- infodict = getinfo(func)
- argnames = infodict["argnames"]
- assert not (
- "_call_" in argnames or "_func_" in argnames
- ), "You cannot use _call_ or _func_ as argument names!"
- src = "lambda %(signature)s: _call_(_func_, %(signature)s)" % infodict
- # import sys; print >> sys.stderr, src # for debugging purposes
- dec_func = eval(src, dict(_func_=func, _call_=caller))
- return update_wrapper(dec_func, func, infodict)
- return update_wrapper(_decorator, caller)
- def getattr_(obj, name, default_thunk):
- "Similar to .setdefault in dictionaries."
- try:
- return getattr(obj, name)
- except AttributeError:
- default = default_thunk()
- setattr(obj, name, default)
- return default
- @decorator
- def memoize(func, *args):
- dic = getattr_(func, "memoize_dic", dict)
- # memoize_dic is created at the first call
- if args in dic:
- return dic[args]
- result = func(*args)
- dic[args] = result
- return result
- ########################## LEGALESE ###############################
- ## Redistributions of source code must retain the above copyright
- ## notice, this list of conditions and the following disclaimer.
- ## Redistributions in bytecode form must reproduce the above copyright
- ## notice, this list of conditions and the following disclaimer in
- ## the documentation and/or other materials provided with the
- ## distribution.
- ## THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- ## "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- ## LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
- ## A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
- ## HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- ## INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- ## BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- ## OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- ## ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- ## TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
- ## USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
- ## DAMAGE.
|