Various articles I have written in the past about programming languages, including Python and Scheme.
micheles / decorator Goto Github PK
View Code? Open in Web Editor NEWdecorator
License: BSD 2-Clause "Simplified" License
decorator
License: BSD 2-Clause "Simplified" License
Various articles I have written in the past about programming languages, including Python and Scheme.
getfullargspec
and formatargspec
are used in decorator.py but will be deprecated in Python 3.5.
It looks like the kw array is not being preserved or passed at all in 3.6. I have not tested in previous versions, but based on the documentation it is a different behavior.
Python 3.6.0 |Anaconda 4.3.1 (64-bit)| (default, Dec 23 2016, 11:57:41) [MSC v.1900 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> from decorator import decorator
>>> @decorator
... def trace(f, *args, **kw):
... kwstr = ', '.join('%r: %r' % (k, kw[k]) for k in sorted(kw))
... print("calling %s with args %s, {%s}" % (f.__name__, args, kwstr))
...
>>> @trace
... def samplefn(x, a="a1"):
... pass
...
>>> samplefn(1)
calling samplefn with args (1, 'a1'), {}
>>> samplefn(1, "inline")
calling samplefn with args (1, 'inline'), {}
>>> samplefn(1, a="askeyword") # notice kwstr is coming back blank
calling samplefn with args (1, 'askeyword'), {}
It appears the use of __file__
here is unnecessary, or could at least be __name__ + 'py'
as a fallback. See indygreg/PyOxidizer#69
Hi, I was wondering what the reason was behind removing the append of the newline in this commit. As far as I can tell, without a trailing newline python 2.7.x will raise a SyntaxError: unexpected EOF while parsing
for compile("def foo(): return bar", ..)
(and since its a SyntaxError, it can be pretty difficult to trace with the pdb family). Thanks
First off, glad you are updating the project again!
I was actually trying to fix this issue at your old repo and saw you have closed the issue as "not an issue".
To reiterate the problem, here is a test that shows the issue:
@decorator
def test_decorator(f, *args, **kwargs):
assert args == (1, 2) # <---- failed here, args is (1,2,3,4) instead
assert kwargs == dict(c=3, d=4, e=5) # <---- kwargs is dict(e=5)
return f(*args, **kwargs)
@test_decorator
def foo(a, b, c=-1, d=-2, **kwargs):
assert kwargs['e'] == 5
return a, b, c, d
assert foo(1, 2, c=3, d=4, e=5) == (1, 2, 3, 4)
In this case, args
giving (1,2,3,4)
is the surprising behaviour. In fact, if we use vanilla python, we would have args = (1, 2)
and kwargs = dict(c=3, d=4, e=5)
:
def decor(f):
def wrapper(*args, **kwargs):
print args, kwargs
return f(*args,**kwargs)
return wrapper
@decor
def foo(a, b, c=-1,d=-2, **kwargs):
pass
foo(1,2,c=3,d=4,e=5)
> (1, 2) {'c': 3, 'e': 5, 'd': 4}
Of foo
's parameters, I believe a
, b
, c
, and d
are called positional-or-keyword parameters. IMHO we should do the least surprising behaviour here, and put parameters that has default to be placed in kwargs
.
First thanks for the great work.
I've been reading your whole documentation and I am certainly missing the point, I am struggling with a syntax issue, cannot find the proper one.
Suppose I want to do the following dumb thing:
import decorator
def printer(text):
def _printer(func, *args, **kwargs):
print(text)
func(*args, **kwargs)
return decorator.decorator(_printer)
@printer("Hello")
def world():
print("World")
When executing, I get the following result:
>>>world()
Hello
World
And that's totally fine !
Suppose now that I want my text to be optional. To do so I give a default value of None
to the text
parameter, check whether there is something to print or not in my _printer
function and omit the argument when decorating. Resulting in the following code:
import decorator
def printer(text=None):
def _printer(func, *args, **kwargs):
if text:
print(text)
func(*args, **kwargs)
return decorator.decorator(_printer)
@printer()
def world():
print("World")
Execution is now:
>>>world()
World
And once again that's fine but a detail bothers me, I am forced to write @printer()
instead of @printer
which I would prefer. But I cannot do so because if I do this leads to the following error:
>>>world()
Traceback (most recent call last):
File "toto.py", line 14, in <module>
world()
TypeError: _printer() missing 1 required positional argument: 'func'
I have based my code on your blocking
example in the documentation but that last detail beats me.
Is there a way to modify this code to achieve what I want or must I stay with the @printer()
syntax ?
Thanks very much.
Neimad
The docs (https://github.com/micheles/decorator/blob/master/docs/documentation.md#decoratordecorator) say:
The decorator function can be used as a signature-changing decorator, just like classmethod and staticmethod.
But then goes on to say:
But classmethod and staticmethod return generic objects which are not callable. Instead, decorator returns signature-preserving decorators (i.e. functions with a single argument).
And the following trivial example doesn't appear to change the signature at all?
What I want to do is create a decorator that will add a few optional keyword arguments to the decorated function. At first I thought I should be able to do something like:
@decorator.decorator
def mydec(func, new_kw=None, another_new_kw=None, *args, **kwargs):
if new_kw:
# do something
return func(*args, **kwargs)
But this returns a decorator factory (also useful, but not for me in this case).
Then, I thought I could do it with decorate
, like this:
def _mydec(func, new_kw=None, another_new_kw=None, *args, **kwargs):
if new_kw:
# do something else when the decorated function is executed
return func(*args, **kwargs)
def mydec(func):
newfunc = decorator.decorate(func, _mydec)
# do something else with func when the decorator is created
return newfunc
But this doesn't work. The decorated function still only accepts the original keyword arguments.
I guess the two methods are functionally equivalent, but decorator.decorate
allows to do something with the original function at the time the decorator is created (vs at the time runtime, with decorator.decorator
.
I also found #55 and #58 which seem to imply that signature-changing decorators are not supported at all and go against the entire philosophy of decorator.py
.
But this seems to be a direct contradiction of the quoted docs:
The decorator function can be used as a signature-changing decorator, just like classmethod and staticmethod.
Can I use decorator.py
to create signature-changing decorators, with new required or optional args or keyword args?
The tarball which I use to package decorator in Arch Linux has the file x.py in it (which can not be found in the Git repo)
The file itself is not compatible with Python 3, which makes python3 setup.py test fail.
I've created a patch for it in our svn repo but I rather see a fix in the tarball (so I don't have to patch it all the time)
Thanks!
Currently https://pypi.org/project/decorator/ shows raw ReStructuredText and looks like this:
This is typically caused by some ReST error, although I'm currently having trouble seeing what it might be.
When using the decorator dynamically the function signature if ok, but an exception occurs:
THIS WORKS:
def args_decorator(*args):
def funcval_wt_args(func):
def wrapper(value):
return func(value), args
return wrapper
return funcval_wt_args
@args_decorator(1, 2, 3)
def func(x):
return x
print(func(10)) # -> (10, (1, 2, 3))
THIS DOES NOT WORK:
from decorator import decorator
def args_decorator(*args):
@decorator
def funcval_wt_args(func):
def wrapper(value):
return func(value), args
return wrapper
return funcval_wt_args
@args_decorator(1, 2, 3)
def func(x):
return x
print(func(10)) # -> TypeError: funcval_wt_args() takes 1 positional argument but 2 were given
Hello,
It's there any way to place a sdist (legacy egg) for decorator 4.0.8 in pypi. We have legacy buildout code that requires it.
Thanks a lot
Carlos
In Python 3.5, many such limitations have been removed, but I still think that it is cleaner and safer to decorate only functions. If you want to decorate things like classmethods/staticmethods and general callables - which I will never support in the decorator module - I suggest you to look at the wrapt project by Graeme Dumpleton.
from decorator import decorator
import asyncio
import time
@decorator
async def log_start_stop(func, *args, **kwargs):
t0 = int(time.time())
print(t0, f'core={func}, args={args},kwargs={kwargs}')
result = await func(*args, **kwargs)
t1 = int(time.time())
print(t1, f'result={result}')
class A:
@staticmethod
@log_start_stop
async def make_task(n):
for i in range(n):
await asyncio.sleep(1)
return 100
a = A()
print(a)
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(A.make_task(5))
finally:
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()
I'm opening this as an issue because it is hindrance to it's usability. Even thought there are plenty of examples and explanation, the documentation is ineffective, in my subjective opinion. As a user I want to see/access the library API as fast as possible to see if it does what I need, without having to scan through Introduction, What’s New in version 4, Usefulness of decorators, Definitions, Statement of the problem and The solution.
Suggestions:
I tried to run the tests from the distribution on pypi but file test.py
is missing.
Looks like BSD 3-clause. Is that correct?
The version 4.3.0 removes getargspec
from the module, which broke some scripts that worked with 4.2.1. We've resolved our issues by relying on a specific snapshot instead of 4.x, but I imagine there are others who might be affected as well.
Was this change intentional? Semantic versioning dictates that minor versions are back-compatible, so it might be best to add it back until a 5.x version.
Running python2 setup.py test
multiple times yields multiple results. I could observe 3 different error outputs and one success output. Each variant seems to be equally likely.
running test
running egg_info
writing src/decorator.egg-info/PKG-INFO
writing top-level names to src/decorator.egg-info/top_level.txt
writing dependency_links to src/decorator.egg-info/dependency_links.txt
reading manifest file 'src/decorator.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'documentation.pdf'
writing manifest file 'src/decorator.egg-info/SOURCES.txt'
running build_ext
test (tests.test.DocumentationTestCase) ... **********************************************************************
File "/tmp/decorator-4.4.1/src/tests/documentation.py", line 50, in tests.documentation.operation1
Failed example:
operation1()
Expected:
operation1 is slow
Got nothing
**********************************************************************
File "/tmp/decorator-4.4.1/src/tests/documentation.py", line 50, in tests.documentation.operation2
Failed example:
operation2()
Expected:
operation2 is slow
Got nothing
**********************************************************************
2 items had failures:
1 of 1 in tests.documentation.operation1
1 of 1 in tests.documentation.operation2
***Test Failed*** 2 failures.
FAIL
test_singledispatch1 (tests.test.DocumentationTestCase) ... ok
test_singledispatch2 (tests.test.DocumentationTestCase) ... ok
test_add1 (tests.test.ExtraTestCase) ... ok
test_decorator_factory (tests.test.ExtraTestCase) ... ok
test_no_first_arg (tests.test.ExtraTestCase) ... ok
test_qualname (tests.test.ExtraTestCase) ... ok
test_signature (tests.test.ExtraTestCase) ... ok
test_unique_filenames (tests.test.ExtraTestCase) ... ok
test_gen123 (tests.test.GeneratorCallerTestCase) ... ok
test_c_classes (tests.test.TestSingleDispatch) ... ok
test_mro (tests.test.TestSingleDispatch) ... ok
test_mro_conflicts (tests.test.TestSingleDispatch) ... ok
test_register_abc (tests.test.TestSingleDispatch) ... ok
test_register_decorator (tests.test.TestSingleDispatch) ... ok
test_register_error (tests.test.TestSingleDispatch) ... ok
test_simple_overloads (tests.test.TestSingleDispatch) ... ok
test_wrapping_attributes (tests.test.TestSingleDispatch) ... ok
======================================================================
FAIL: test (tests.test.DocumentationTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/decorator-4.4.1/src/tests/test.py", line 81, in test
self.assertEqual(err, 0)
AssertionError: 2 != 0
----------------------------------------------------------------------
Ran 18 tests in 5.631s
FAILED (failures=1)
Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>
error: Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>
running test
running egg_info
writing src/decorator.egg-info/PKG-INFO
writing top-level names to src/decorator.egg-info/top_level.txt
writing dependency_links to src/decorator.egg-info/dependency_links.txt
reading manifest file 'src/decorator.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'documentation.pdf'
writing manifest file 'src/decorator.egg-info/SOURCES.txt'
running build_ext
test (tests.test.DocumentationTestCase) ... **********************************************************************
File "/tmp/decorator-4.4.1/src/tests/documentation.py", line 50, in tests.documentation.operation1
Failed example:
operation1()
Expected:
operation1 is slow
Got nothing
**********************************************************************
1 items had failures:
1 of 1 in tests.documentation.operation1
***Test Failed*** 1 failures.
FAIL
test_singledispatch1 (tests.test.DocumentationTestCase) ... ok
test_singledispatch2 (tests.test.DocumentationTestCase) ... ok
test_add1 (tests.test.ExtraTestCase) ... ok
test_decorator_factory (tests.test.ExtraTestCase) ... ok
test_no_first_arg (tests.test.ExtraTestCase) ... ok
test_qualname (tests.test.ExtraTestCase) ... ok
test_signature (tests.test.ExtraTestCase) ... ok
test_unique_filenames (tests.test.ExtraTestCase) ... ok
test_gen123 (tests.test.GeneratorCallerTestCase) ... ok
test_c_classes (tests.test.TestSingleDispatch) ... ok
test_mro (tests.test.TestSingleDispatch) ... ok
test_mro_conflicts (tests.test.TestSingleDispatch) ... ok
test_register_abc (tests.test.TestSingleDispatch) ... ok
test_register_decorator (tests.test.TestSingleDispatch) ... ok
test_register_error (tests.test.TestSingleDispatch) ... ok
test_simple_overloads (tests.test.TestSingleDispatch) ... ok
test_wrapping_attributes (tests.test.TestSingleDispatch) ... ok
======================================================================
FAIL: test (tests.test.DocumentationTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/decorator-4.4.1/src/tests/test.py", line 81, in test
self.assertEqual(err, 0)
AssertionError: 1 != 0
----------------------------------------------------------------------
Ran 18 tests in 5.634s
FAILED (failures=1)
Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>
error: Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>
running test
running egg_info
writing src/decorator.egg-info/PKG-INFO
writing top-level names to src/decorator.egg-info/top_level.txt
writing dependency_links to src/decorator.egg-info/dependency_links.txt
reading manifest file 'src/decorator.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'documentation.pdf'
writing manifest file 'src/decorator.egg-info/SOURCES.txt'
running build_ext
test (tests.test.DocumentationTestCase) ... **********************************************************************
File "/tmp/decorator-4.4.1/src/tests/documentation.py", line 50, in tests.documentation.operation2
Failed example:
operation2()
Expected:
operation2 is slow
Got nothing
**********************************************************************
1 items had failures:
1 of 1 in tests.documentation.operation2
***Test Failed*** 1 failures.
FAIL
test_singledispatch1 (tests.test.DocumentationTestCase) ... ok
test_singledispatch2 (tests.test.DocumentationTestCase) ... ok
test_add1 (tests.test.ExtraTestCase) ... ok
test_decorator_factory (tests.test.ExtraTestCase) ... ok
test_no_first_arg (tests.test.ExtraTestCase) ... ok
test_qualname (tests.test.ExtraTestCase) ... ok
test_signature (tests.test.ExtraTestCase) ... ok
test_unique_filenames (tests.test.ExtraTestCase) ... ok
test_gen123 (tests.test.GeneratorCallerTestCase) ... ok
test_c_classes (tests.test.TestSingleDispatch) ... ok
test_mro (tests.test.TestSingleDispatch) ... ok
test_mro_conflicts (tests.test.TestSingleDispatch) ... ok
test_register_abc (tests.test.TestSingleDispatch) ... ok
test_register_decorator (tests.test.TestSingleDispatch) ... ok
test_register_error (tests.test.TestSingleDispatch) ... ok
test_simple_overloads (tests.test.TestSingleDispatch) ... ok
test_wrapping_attributes (tests.test.TestSingleDispatch) ... ok
======================================================================
FAIL: test (tests.test.DocumentationTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/decorator-4.4.1/src/tests/test.py", line 81, in test
self.assertEqual(err, 0)
AssertionError: 1 != 0
----------------------------------------------------------------------
Ran 18 tests in 5.632s
FAILED (failures=1)
Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>
error: Test failed: <unittest.runner.TextTestResult run=18 errors=0 failures=1>
running test
running egg_info
writing src/decorator.egg-info/PKG-INFO
writing top-level names to src/decorator.egg-info/top_level.txt
writing dependency_links to src/decorator.egg-info/dependency_links.txt
reading manifest file 'src/decorator.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'documentation.pdf'
writing manifest file 'src/decorator.egg-info/SOURCES.txt'
running build_ext
test (tests.test.DocumentationTestCase) ... ok
test_singledispatch1 (tests.test.DocumentationTestCase) ... ok
test_singledispatch2 (tests.test.DocumentationTestCase) ... ok
test_add1 (tests.test.ExtraTestCase) ... ok
test_decorator_factory (tests.test.ExtraTestCase) ... ok
test_no_first_arg (tests.test.ExtraTestCase) ... ok
test_qualname (tests.test.ExtraTestCase) ... ok
test_signature (tests.test.ExtraTestCase) ... ok
test_unique_filenames (tests.test.ExtraTestCase) ... ok
test_gen123 (tests.test.GeneratorCallerTestCase) ... ok
test_c_classes (tests.test.TestSingleDispatch) ... ok
test_mro (tests.test.TestSingleDispatch) ... ok
test_mro_conflicts (tests.test.TestSingleDispatch) ... ok
test_register_abc (tests.test.TestSingleDispatch) ... ok
test_register_decorator (tests.test.TestSingleDispatch) ... ok
test_register_error (tests.test.TestSingleDispatch) ... ok
test_simple_overloads (tests.test.TestSingleDispatch) ... ok
test_wrapping_attributes (tests.test.TestSingleDispatch) ... ok
----------------------------------------------------------------------
Ran 18 tests in 5.635s
OK
Dockerfile
FROM debian:buster
RUN apt-get update \
&& apt-get install -y python2 python-setuptools wget \
&& cd /tmp \
&& wget https://files.pythonhosted.org/packages/dc/c3/9d378af09f5737cfd524b844cd2fbb0d2263a35c11d712043daab290144d/decorator-4.4.1.tar.gz \
&& tar xf decorator-4.4.1.tar.gz \
&& rm decorator-4.4.1.tar.gz \
&& apt-get clean \
&& rm -R /var/lib/apt/lists
WORKDIR /tmp/decorator-4.4.1
ENTRYPOINT [ "/usr/bin/python2", "setup.py", "test" ]
for i in {1..10}; do docker run --rm -it <image>; done
On PyPI it seems the 4.0.7 release has nothing uploaded, so installing it fails:
$ pip install decorator==4.0.7
Collecting decorator==4.0.7
Could not find a version that satisfies the requirement decorator==4.0.7 (from versions: 3.3.1, 3.3.2, 3.3.3, 3.4.0, 3.4.2, 4.0.0, 4.0.1, 4.0.2, 4.0.3, 4.0.4, 4.0.6)
No matching distribution found for decorator==4.0.7
Hi,
I'm learning Python. I installed my first python program (Chatterbot 0.4.6) that uses NLTK. I installed last version of nltk (3.2.1), but it doesn't work for inspect.getargspec in decorators.py
How to solve this problem? I tried to install decorator.py and put it in nltk fold but nltk has a module named as decorators (not decorator).
Can you help me, please?
The memoize decorator as shown on https://pythonhosted.org/decorator/documentation.html#the-solution will cause a memory leak when decorating an instancemethod
, as the instance (self) is considered part of the cache key and cached results are not cleaned up as the instance is deleted. In fact, the instance is never deleted as the cache key still holds a reference to it.
While this does not necessarily need fixing (as it's just an example), there should be a big warning on the code. Or rather, maybe you can come up with a better example that can be implemented without such issues. The decorator as-is is currently copied to several projects or used as a base for extensions. See https://www.google.com/search?q="frozenset+is+used+to+ensure+hashability"
At the moment using @decorator
with the mypy option --disallow-untyped-decorators
results in a mypy error since the library isn't type-annotated.
via email, Michele okay'd me adding the library to the typeshed (https://github.com/python/typeshed) -- posting here for visibility.
There are no git tags. It'd be great if there were git tags.
Upgrading to 4.0.1 (latest as of this moment) gives the following error:
$ pip install decorator==4.0.1
Downloading/unpacking decorator==4.0.1
Running setup.py egg_info for package decorator
Traceback (most recent call last):
File "<string>", line 16, in <module>
File "XXX/build/decorator/setup.py", line 12, in <module>
long_description=open('docs/README.rst').read(),
IOError: [Errno 2] No such file or directory: 'docs/README.rst'
Complete output from command python setup.py egg_info:
Traceback (most recent call last):
File "<string>", line 16, in <module>
File "XXX/build/decorator/setup.py", line 12, in <module>
long_description=open('docs/README.rst').read(),
IOError: [Errno 2] No such file or directory: 'docs/README.rst'
----------------------------------------
Command python setup.py egg_info failed with error code 1 in XXX/build/decorator
Storing complete log in /home/ubuntu/.pip/pip.log
I see the fix in 18dd1e5, but it doesn't seem to have done the job.
4.0.0 works as expected.
It would be great if @decorator
could be used to create decorators which produce coroutine functions. Is there already such functionality and I just didn't find it? If not, would this be feasible? If so, are there any plans for this? Would you accept PRs?
Example:
from inspect import iscoroutinefunction
from decorator import decorator
@decorator
async def ensure_coro(function, *args, **kwargs):
ret = function(*args, **kwargs)
if iscoroutinefunction(function):
ret = await ret
return ret
@ensure_coro
def foo():
return 42
assert iscoroutinefunction(foo)
Traceback (most recent call last):
File "/usr/local/bin/ipython", line 7, in
from IPython import start_ipython
File "/usr/local/lib/python2.7/site-packages/IPython/init.py", line 48, in
from .core.application import Application
File "/usr/local/lib/python2.7/site-packages/IPython/core/application.py", line 23, in
from traitlets.config.application import Application, catch_config_error
File "/usr/local/lib/python2.7/site-packages/traitlets/config/init.py", line 6, in
from .application import *
File "/usr/local/lib/python2.7/site-packages/traitlets/config/application.py", line 120, in
class Application(SingletonConfigurable):
File "/usr/local/lib/python2.7/site-packages/traitlets/config/application.py", line 291, in Application
def initialize(self, argv=None):
TypeError: catch_config_error() takes exactly 2 arguments (1 given)
catch_config_error is a decorator decorated with this package
3.4.2 was pushed to PyPi but was not documented in the changelog
There is no support for adding arguments to a decorated function inside a decorator.
A simple example will show the issue.
import decorator
@decorator.decorator
def foo(f, *args, **kwargs):
return f(1, *args, **kwargs)
def bar(a):
print "bar got {}".format(a)
bar()
The following code results in the following exception:
python crap.py
Traceback (most recent call last):
File "crap.py", line 10, in <module>
bar()
TypeError: bar() takes exactly 1 argument (0 given)
I understand that this error occurs since the decorator function copies the signature of the decorated function. Is there a way to make this work? Or is this not considered a valid use case for the decorator library?
Python : 2.7, decorator (4.0.4)
Test with the following code:
import decorator
@decorator.decorator
def test_decorator(f, *args, **kwargs):
print('check args:')
print(args)
print('check kwargs')
print(kwargs)
return f(*args, **kwargs)
class TestObject(object):
@test_decorator
def test(self, x, y, z):
return x + y + z
test = TestObject()
test.test(x=2,y=2,z=4)
The output is:
check args:
(<__main__.TestObject object at 0x7f33a0b4ec50>, 2, 2, 4)
check kwargs
{}
Would be cool if decorator includes a @memoized_property
decorator for class methods.
eg: https://github.com/estebistec/python-memoized-property/blob/master/memoized_property.py
For the code below, I can use handle()
with default name="name"
or handle(name="h name")
. It extends a keyword parameter "name" of my handle
function.
def get(url: str):
"""Get it."""
def _decorator(func):
"""Decorate func."""
res = f"Got url: {url}"
@wraps(func)
def wrapper(*, name: str = "name"):
"""Wrap func."""
return func(f"Name: {name}\nThe url: {url}\n{res}")
return wrapper
return _decorator
@get("(xxx.com)")
def handle(res):
print(res)
>>> handle()
Name: name
The url: (xxx.com)
Get url: (xxx.com)
>>>handle(name = "h name")
Name: h name
The url: (xxx.com)
Get url: (xxx.com)
When I used the decorator, is there a way for me to extend the keyword parameters of handle
function as above?
I read somewhere today that "In regards to PyPI, the "decorator" package is ~300 lines in total, and is one of the top most downloaded packages from pypi.". So I was curious and set off to read up on what the library is and what it does.
Your README.rst does not explain what this library does, it just explains how to install it, or run the test suite, etc. The "For the impatient" is the closest to explaining what this does, by providing an example of using it for something. So it might be possible to extract what it does from that context, although I couldn't on first attempt.
Following the link to the full docs, https://decorator.readthedocs.io/en/latest/, Nothing on the front page explains what this is or does, but fair enough, it's just a ToC. However, the "introduction" section does not explain what this or does, either. The next section, "Whats new", is not relevant if you don't know what it is or does (yet), and the section after that, "Usefulness of decorators", seems to explain why the libraries does what it does, without explaining what it does.
I'm not trying to be too pedantic here. I've written a lot of Python, although I do not consider myself to be a Python programmer, in particular; and I suspect I've used decorators and probably even this library before (by virtue of hacking on something that used it). But despite that I genuinely do not know what this library does despite having read all of the above.
Hi,
great module !!
Little Issue: FunctionMaker does drop defaults in the sign. string:
(Pdb) sig = ('foo', 'bar')
(Pdb) fsig = 'f1(%s=None)' % '=None, '.join(sig)
(Pdb) fsig
'f1(foo=None, bar=None)'
(Pdb) f = decorator.FunctionMaker.create(fsig, 'pass', {}, addsource=True)
(Pdb) f(1)
*** TypeError: f1() takes exactly 2 arguments (1 given)
(Pdb) inspect.getargspec(f)
ArgSpec(args=['foo', 'bar'], varargs=None, keywords=None, defaults=())
Hi there,
I'm a big fan of your library and I used it in many of mine :).
I recently came in pytest-steps with the need to
The use-case is that the decorated function is a pytest test function, that can or can not already have the "request" argument in its signature, but I need the wrapped function to have it always. I came up with the following hack, maybe this is something you would be interested in ?
class MyFunctionMaker(FunctionMaker):
"""
Overrides FunctionMaker so that additional arguments can be inserted in the resulting signature.
"""
@classmethod
def create(cls, obj, body, evaldict, defaults=None,
doc=None, module=None, addsource=True, add_args=None, **attrs):
"""
Create a function from the strings name, signature and body.
evaldict is the evaluation dictionary. If addsource is true an
attribute __source__ is added to the result. The attributes attrs
are added, if any.
"""
if isinstance(obj, str): # "name(signature)"
name, rest = obj.strip().split('(', 1)
signature = rest[:-1] # strip a right parens
func = None
else: # a function
name = None
signature = None
func = obj
self = cls(func, name, signature, defaults, doc, module)
ibody = '\n'.join(' ' + line for line in body.splitlines())
caller = evaldict.get('_call_') # when called from `decorate`
if caller and iscoroutinefunction(caller):
body = ('async def %(name)s(%(signature)s):\n' + ibody).replace(
'return', 'return await')
else:
body = 'def %(name)s(%(signature)s):\n' + ibody
# --- HACK part 1 -----
if add_args is not None:
for arg in add_args:
if arg not in self.args:
self.args = [arg] + self.args
else:
# the argument already exists in the wrapped function, no problem.
pass
# update signatures (this is a copy of the init code)
allargs = list(self.args)
allshortargs = list(self.args)
if self.varargs:
allargs.append('*' + self.varargs)
allshortargs.append('*' + self.varargs)
elif self.kwonlyargs:
allargs.append('*') # single star syntax
for a in self.kwonlyargs:
allargs.append('%s=None' % a)
allshortargs.append('%s=%s' % (a, a))
if self.varkw:
allargs.append('**' + self.varkw)
allshortargs.append('**' + self.varkw)
self.signature = ', '.join(allargs)
self.shortsignature = ', '.join(allshortargs)
# ---------------------------
func = self.make(body, evaldict, addsource, **attrs)
# ----- HACK part 2
if add_args is not None:
# delete this annotation otherwise the inspect.signature method relies on the wrapped object's signature
del func.__wrapped__
return func
def my_decorate(func, caller, extras=(), additional_args=None):
"""
A clone of 'decorate' with the possibility to add additional args to the function signature.
Additional arguments will be positional and will sit at the beginning of
"""
evaldict = dict(_call_=caller, _func_=func)
es = ''
for i, extra in enumerate(extras):
ex = '_e%d_' % i
evaldict[ex] = extra
es += ex + ', '
fun = MyFunctionMaker.create(
func, "return _call_(_func_, %s%%(shortsignature)s)" % es,
evaldict, add_args=reversed(additional_args or ()), __wrapped__=func)
if hasattr(func, '__qualname__'):
fun.__qualname__ = func.__qualname__
return fun
Here is how I use it in a complex case. One additional argument ________step_name_
is guaranteed to not be in the function signature, while the 'request' argument may or may not be there
# -- first create the logic
def _execute_step_with_monitor(step_name, request, *args, **kwargs):
# ...
# -- then create the appropriate function signature according to wrapped function signature
if 'request' not in f_sig.parameters:
# easy: we can add it explicitly in our signature
def step_function_wrapper(f, ________step_name_, request, *args, **kwargs):
"""Executes a step with the execution monitor for this pytest node"""
_execute_step_with_monitor(________step_name_, request, *args, **kwargs)
else:
# harder: we have to retrieve the value for request. Thanks, inspect package !
def step_function_wrapper(f, ________step_name_, *args, **kwargs):
"""Executes a step with the execution monitor for this pytest node"""
request = f_sig.bind(*args, **kwargs).arguments['request']
_execute_step_with_monitor(________step_name_, request, *args, **kwargs)
# decorate it so that its signature is the same than test_func, with just an additional argument for test step
# and if needed an additional argument for request
wrapped_test_function = my_decorate(test_func, step_function_wrapper,
additional_args=['________step_name_', 'request'])
If you think that this feature is interesting, I can make a Pull Request.
the release https://pypi.python.org/pypi/decorator/4.0.7 is broken. setuptools is not able to install it. reason: the files are all 4.0.8, but the pypi version number is 4.0.7.
The repo has a 4.3.1 release tagged from 6 months ago but the latest release on PyPI ist 4.3.0: https://pypi.org/project/decorator/#history
Is this on purpose?
In commit 24d5f15 you moved README.rst to a different place. However, because MANIFEST.in was not updated to reflect the new path, the README.txt file was not included in the source distribution for the 4.0.1 release, and now setup.py fails when trying to generate the description.
Getting distribution for 'decorator'.
error: docs/README.rst: No such file or directory
An error occurred when trying to install decorator 4.0.1. Look above this message for any errors that were output by easy_install.
While:
Updating test.
Getting distribution for 'decorator'.
Error: Couldn't install: decorator 4.0.1
Attempt 1 of "bin/buildout -n -t 5" failed.
I have a context manager that creates some resources and cleans them up on exit. I would like, if possible, to use it as a decorator, too. But when I do, I also need to have access to the name of the decorated function. I managed to do this by subclassing decorator.ContextManager
and overriding __call__
:
def __call__(self, func):
"""Context manager decorator"""
self.__func__ = func # this is the line I added
return decorator.FunctionMaker.create(
func, "with _self_: return _func_(%(shortsignature)s)",
dict(_self_=self, _func_=func), __wrapped__=func)
decorator.ContextManager
?In a library for pytest I need to wrap a fixture written by the user. Pytest supports both generator and normal functions as fixture. I would like to preserve this nature in the decorated function.
Surprisingly, decorator
does not seem to support this case. Below the corresponding test. I will give it a try because I need the feature, and if it seems elegant/interesting enough, I might propose a PR.
from inspect import isgeneratorfunction
from decorator import decorate
# A my_normal function
def my_normal():
for i in range(10):
print(i)
return
assert not isgeneratorfunction(my_normal)
# A generator
def my_generator():
for i in range(10):
print(i)
yield i
assert isgeneratorfunction(my_generator)
# A my_normal wrapper around a normal function
def normal_around_normal(f, *args, **kwargs):
return my_normal()
assert not isgeneratorfunction(decorate(my_generator, normal_around_normal))
# A my_normal wrapper around a generator function
def normal_around_gen(f, *args, **kwargs):
for res in my_generator():
# do not yield !
pass
return 15
assert not isgeneratorfunction(decorate(my_generator, normal_around_gen))
# A generator wrapper around a generator function
def gen_around_gen(f, *args, **kwargs):
for res in my_generator():
yield res
assert isgeneratorfunction(decorate(my_generator, gen_around_gen))
# A generator wrapper around a my_normal function
def gen_around_normal(f, *args, **kwargs):
yield my_normal()
assert isgeneratorfunction(decorate(my_normal, gen_around_normal))
Python has many "XX for Humans" third-party libs, for example-- Python HTTP Requests for Humans, SQL for Humans;
Your decorator is an awesome and easy to use lib compared to Python official decorator, why not modify Description to “decorator for Humans” ?
import decorator
@decorator.decorator
def first(f, *args, **kwargs):
print('Print this first')
return f(*args, **kwargs)
@first
@decorator.decorator
def second(f, *args, **kwargs):
print('Print this second')
return f(*args, **kwargs)
@second
def main():
return
Hi, I was expecting the code to print out
Print this first
Print this second
but it's only printing out
Print this first
Am I doing anything wrong?
I had a suggestion and wasn't sure where to put it, the basic idea is could the decorate function support multiple decorators in a single call so that:
def my_decorator(func):
return decorate(decorate(func, _caller), _precondition_caller)
could be rewritten as:
def my_decorator(func):
return decorate(func, _precondition_caller, _caller)
Maybe I'm just approaching decorator composition wrong, if so I'd love to see an alternative way.
Hello micheles,
Great work
How do I covert below using your decorator module
def decorator_name(f):
@another_decorator_name
def wrapper(*args, **kwargs):
return f(*args, **kwargs)
return wrapper
I've developed a handy utility which looks like @decorator, but pre-resolves arguments before passing them to the wrapper function (using https://docs.python.org/2/library/inspect.html#inspect.getcallargs).
It's very useful when you need to decorate many functions, without knowing it advanced if they'll be passed arguments by position (<args>) or by keywords (<*kwargs>). Since incoming arguments are normalized in a simple dict, you can inspect and modify them freely.
I used it to automatically inject or commit/rollback SQL sessions which travelled through program, for example.
Below is an example of use.
Would you be OK for me to prepare a pull request including that utility next to the standard "decorator".
@resolving_decorator
def inject_session(func, **all_kwargs):
if not all_kwargs["session"]:
all_kwargs["session"] = "<SESSION>"
return func(**all_kwargs)
@inject_session
def myfunc(session):
return session
assert myfunc(None) == myfunc(session=None) == "<SESSION>"
assert myfunc("<stuff>") == myfunc(session="<stuff>") == "<stuff>"
% pip install -v decorator==3.4.1
Collecting decorator==3.4.1
Getting page https://pypi.python.org/simple/decorator/
Starting new HTTPS connection (1): pypi.python.org
"GET /simple/decorator/ HTTP/1.1" 200 488
URLs to search for versions for decorator==3.4.1:
* https://pypi.python.org/simple/decorator/
Getting page https://pypi.python.org/simple/decorator/
Analyzing links from page https://pypi.python.org/simple/decorator/
Found link https://pypi.python.org/packages/source/d/decorator/decorator-3.3.1.tar.gz#md5=a8fc62acd705f487a71bc406e19e0cc6 (from https://pypi.python.org/simple/decorator/), version: 3.3.1
Found link https://pypi.python.org/packages/source/d/decorator/decorator-3.3.2.tar.gz#md5=446f5165af67eb0fcd8fd28abd259e86 (from https://pypi.python.org/simple/decorator/), version: 3.3.2
Found link https://pypi.python.org/packages/source/d/decorator/decorator-3.3.3.tar.gz#md5=f5a0227cb1c34a0e7d5b7f9cd2ae3135 (from https://pypi.python.org/simple/decorator/), version: 3.3.3
Found link https://pypi.python.org/packages/source/d/decorator/decorator-3.4.0.tar.gz#md5=1e8756f719d746e2fc0dd28b41251356 (from https://pypi.python.org/simple/decorator/), version: 3.4.0
Skipping link http://code.google.com/p/micheles/source/browse/#hg%2Fdecorator (from https://pypi.python.org/simple/decorator/); not a file
Skipping link http://micheles.googlecode.com/hg/decorator/documentation.html (from https://pypi.python.org/simple/decorator/); unknown archive format: .html
Skipping link http://micheles.googlecode.com/hg/decorator/documentation.pdf (from https://pypi.python.org/simple/decorator/); unknown archive format: .pdf
Skipping link http://micheles.googlecode.com/hg/decorator/documentation3.html (from https://pypi.python.org/simple/decorator/); unknown archive format: .html
Skipping link http://micheles.googlecode.com/hg/decorator/documentation3.pdf (from https://pypi.python.org/simple/decorator/); unknown archive format: .pdf
Skipping link http://packages.python.org/distribute/ (from https://pypi.python.org/simple/decorator/); not a file
Skipping link http://science.webhostinggeeks.com/dekorater-modula (from https://pypi.python.org/simple/decorator/); not a file
Could not find a version that satisfies the requirement decorator==3.4.1 (from versions: 3.3.1, 3.3.2, 3.3.3, 3.4.0)
Cleaning up...
No distributions matching the version for decorator==3.4.1
Hello, we are already testing packages in Fedora with Python 3.9.0a2. The tests of decorator fail with:
+ /usr/bin/python3 setup.py test
running test
WARNING: Testing via this command is deprecated and will be removed in a future version. Users looking for a generic test entry point independent of test runner are encouraged to use tox.
running egg_info
writing src/decorator.egg-info/PKG-INFO
writing dependency_links to src/decorator.egg-info/dependency_links.txt
writing top-level names to src/decorator.egg-info/top_level.txt
reading manifest file 'src/decorator.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
warning: no files found matching 'README.rst'
warning: no files found matching 'documentation.pdf'
writing manifest file 'src/decorator.egg-info/SOURCES.txt'
running build_ext
test_before_after (tests.test.CoroutineTestCase) ... ok
test_coro_to_func (tests.test.CoroutineTestCase) ... ok
test (tests.test.DocumentationTestCase) ... **********************************************************************
File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 531, in tests.documentation
Failed example:
print(read_data()) # data is not available yet
Exception raised:
Traceback (most recent call last):
File "/usr/lib64/python3.9/doctest.py", line 1329, in __run
exec(compile(example.source, filename, "single",
File "<doctest tests.documentation[33]>", line 1, in <module>
print(read_data()) # data is not available yet
File "</builddir/build/BUILD/decorator-4.4.0/src/decorator.py:decorator-gen-26>", line 2, in read_data
File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 1532, in blocking
elif f.thread.isAlive():
AttributeError: 'Thread' object has no attribute 'isAlive'
**********************************************************************
File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 535, in tests.documentation
Failed example:
print(read_data()) # data is not available yet
Exception raised:
Traceback (most recent call last):
File "/usr/lib64/python3.9/doctest.py", line 1329, in __run
exec(compile(example.source, filename, "single",
File "<doctest tests.documentation[35]>", line 1, in <module>
print(read_data()) # data is not available yet
File "</builddir/build/BUILD/decorator-4.4.0/src/decorator.py:decorator-gen-26>", line 2, in read_data
File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 1532, in blocking
elif f.thread.isAlive():
AttributeError: 'Thread' object has no attribute 'isAlive'
**********************************************************************
File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 539, in tests.documentation
Failed example:
print(read_data())
Exception raised:
Traceback (most recent call last):
File "/usr/lib64/python3.9/doctest.py", line 1329, in __run
exec(compile(example.source, filename, "single",
File "<doctest tests.documentation[37]>", line 1, in <module>
print(read_data())
File "</builddir/build/BUILD/decorator-4.4.0/src/decorator.py:decorator-gen-26>", line 2, in read_data
File "/builddir/build/BUILD/decorator-4.4.0/src/tests/documentation.py", line 1532, in blocking
elif f.thread.isAlive():
AttributeError: 'Thread' object has no attribute 'isAlive'
**********************************************************************
1 items had failures:
3 of 88 in tests.documentation
***Test Failed*** 3 failures.
FAIL
test_singledispatch1 (tests.test.DocumentationTestCase) ... ok
test_singledispatch2 (tests.test.DocumentationTestCase) ... ok
test_add1 (tests.test.ExtraTestCase) ... ok
test_decorator_factory (tests.test.ExtraTestCase) ... ok
test_no_first_arg (tests.test.ExtraTestCase) ... ok
test_qualname (tests.test.ExtraTestCase) ... ok
test_signature (tests.test.ExtraTestCase) ... ok
test_unique_filenames (tests.test.ExtraTestCase) ... ok
test_gen123 (tests.test.GeneratorCallerTestCase) ... ok
test_c_classes (tests.test.TestSingleDispatch) ... ok
test_mro (tests.test.TestSingleDispatch) ... ok
test_mro_conflicts (tests.test.TestSingleDispatch) ... ok
test_register_abc (tests.test.TestSingleDispatch) ... ok
test_register_decorator (tests.test.TestSingleDispatch) ... ok
test_register_error (tests.test.TestSingleDispatch) ... ok
test_simple_overloads (tests.test.TestSingleDispatch) ... ok
test_wrapping_attributes (tests.test.TestSingleDispatch) ... ok
======================================================================
FAIL: test (tests.test.DocumentationTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/builddir/build/BUILD/decorator-4.4.0/src/tests/test.py", line 81, in test
self.assertEqual(err, 0)
AssertionError: 3 != 0
----------------------------------------------------------------------
Ran 20 tests in 5.660s
FAILED (failures=1)
Test failed: <unittest.runner.TextTestResult run=20 errors=0 failures=1>
Indeed, isAlive()
is deprecated, use is_alive()
instead:
$ python3.8 -c 'import threading; t = threading.Thread(); t.isAlive()'
<string>:1: DeprecationWarning: isAlive() is deprecated, use is_alive() instead
$ python3.9 -c 'import threading; t = threading.Thread(); t.isAlive()'
Traceback (most recent call last):
File "<string>", line 1, in <module>
AttributeError: 'Thread' object has no attribute 'isAlive'
Will submit a PR.
functools.update_wrapper
sets wrapper.__qualname__
to wrapped.__qualname__
, is there a reason that decorator.decorator
doesn't do the same?
I tried searching for a reason this is the case, but came up blank.
On Python 3, functools.wraps
nominally preserves function signatures, e.g.,
import functools
def original(x):
return x
@functools.wraps(original)
def wrapper(*args, **kwargs):
return original(*args, **kwargs)
The function has the right associated signature object (so introspection works properly) but abstraction is still a little leaky, e.g., for handling invalid function arguments:
In [11]: wrapper(y=1)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-11-1e0b778ae20b> in <module>()
----> 1 wrapper(y=1)
<ipython-input-4-878d38897098> in wrapper(*args, **kwargs)
6 @functools.wraps(original)
7 def wrapper(*args, **kwargs):
----> 8 return original(*args, **kwargs)
TypeError: original() got an unexpected keyword argument 'y'
I think the decorator library can produce fully compatible function signatures, but the interface is a little different:
@functools.partial(decorate, original)
def wrapper(original, *args, **kwargs):
return original(*args, **kwargs)
In particular, it's awkward that I need to include the original function in the wrapper signature (e.g., def wrapper(original, *args, **kwargs)
rather than def wrapper(*args, **kwargs)
.
Is there an easy way to write a drop-in replacement for functools.wraps
with the decorator library?
The distfiles for decorator 4.0.9 and 4.0.10 contain a backup file, src/decorator.egg-info/SOURCES.txt~
, that is installed. Please remove it for the next release.
At the moment, the qualname attribute is set as None if the decorated function does not have this attribute. This can be considered unexpected behavior and can cause problems in some cases.
This attribute should either not be set if the decorated function does not have it, either use the name value instead.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.