Decorators take an object and return a object (by convention a callable, although this isn't enforced in the slightest). Maybe they modify a function or method signature, maybe it produces an unrelated object, or maybe one augments the object by adding side effects like caching or logging.
There is a special syntax (@) legal for classes, functions, and methods that provides a shortcut for the common pattern of:
def decorator(): ...
def foo(): ...
foo = decorator(foo)
such that we can say: @decorator
def foo(): ...
Things I've seen decorators do: * add logging
* add tracing
* enable type validation
* register functions/objects for various reasons
* produce full objects out of functions
* swap out implementations for global functions/vars
* change output formats to json/xml/yaml
* take a class, inspect a database schema and bind various internal attributes to update items in a row for that schema (somewhat common in ORMs)
I don't see this attrs library as any more egregious than other uses. In particular, I don't see why mutating a type via a metaclass is preferable to using a decorator. I can offer that decorators generating objects compose much better than metaclasses do (at least they have in python 2.x, I haven't had a chance to use 3 professionally).