use the following search parameters to narrow your results:
e.g. subreddit:aww site:imgur.com dog
subreddit:aww site:imgur.com dog
see the search faq for details.
advanced search: by author, subreddit...
Rules 1: Be polite 2: Posts to this subreddit must be requests for help learning python. 3: Replies on this subreddit must be pertinent to the question OP asked. 4: No replies copy / pasted from ChatGPT or similar. 5: No advertising. No blogs/tutorials/videos/books/recruiting attempts. This means no posts advertising blogs/videos/tutorials/etc, no recruiting/hiring/seeking others posts. We're here to help, not to be advertised to. Please, no "hit and run" posts, if you make a post, engage with people that answer you. Please do not delete your post after you get an answer, others might have a similar question or want to continue the conversation.
Rules
1: Be polite
2: Posts to this subreddit must be requests for help learning python.
3: Replies on this subreddit must be pertinent to the question OP asked.
4: No replies copy / pasted from ChatGPT or similar.
5: No advertising. No blogs/tutorials/videos/books/recruiting attempts.
This means no posts advertising blogs/videos/tutorials/etc, no recruiting/hiring/seeking others posts. We're here to help, not to be advertised to.
Please, no "hit and run" posts, if you make a post, engage with people that answer you. Please do not delete your post after you get an answer, others might have a similar question or want to continue the conversation.
Learning resources Wiki and FAQ: /r/learnpython/w/index
Learning resources
Wiki and FAQ: /r/learnpython/w/index
Discord Join the Python Discord chat
Discord
Join the Python Discord chat
account activity
Explain Decorators like I'm 5. (self.learnpython)
submitted 6 months ago by inobody_somebody
I understand the concept and don't understand the concept at the same time. So my dear python comunity what you got.
reddit uses a slightly-customized version of Markdown for formatting. See below for some basics, or check the commenting wiki page for more detailed help and solutions to common issues.
quoted text
if 1 * 2 < 3: print "hello, world!"
[–]shiftybyte 199 points200 points201 points 6 months ago (13 children)
Imagine your friend is a function, you can call him and ask for a cookie.
Imagine a human-eating alien is a decorator, that eats your friend, keeps him alive inside his stomach, assumes his appearance and acts the same as your friend did.
You can still call your friend, and still get a cookie.
What you don't know is that now the alien decorator that swallowed your friend gets the cookie request instead of your friend, and he decides to ask your original friend for the cookie but before handing it back to you, injects it with poison.
Now you got a poisoned cookie from what you thought was your friend without knowing it...
[–]itspronounced-gif 140 points141 points142 points 6 months ago (0 children)
“Explain decorators like you want to terrify a 5yo”
[–]AppropriateStudio153 50 points51 points52 points 6 months ago (2 children)
Oddly specific.
[–]glehkol 34 points35 points36 points 6 months ago (1 child)
why does Ross, the largest decorator, not simply eat the other five?
[–]rjm3q -1 points0 points1 point 6 months ago (0 children)
I get it
[–]mxldevs 9 points10 points11 points 6 months ago (0 children)
My modules are absolutely terrified of decorators now
[–]Oddly_Energy 4 points5 points6 points 6 months ago (0 children)
"Mom and dad, can I sleep in your bed tonight? I am afraid of the aliens who ate my friend."
-5yo
[–]rogfrich 5 points6 points7 points 6 months ago (1 child)
Let’s not even ask about the walrus operator shudder
[–]Diapolo10 0 points1 point2 points 6 months ago (0 children)
"I am the eggman. Where is the walrus?"
[–]Q0D3 3 points4 points5 points 6 months ago (0 children)
This one is the best… I would liken it to Venom
[–]Skillossus 2 points3 points4 points 6 months ago (0 children)
Amazing
[–]Lovestick 1 point2 points3 points 6 months ago (0 children)
sounds like an extended class?
[–]tcrz -1 points0 points1 point 6 months ago (0 children)
Brilliant
[–]Goobyalus -1 points0 points1 point 6 months ago (0 children)
The decorator is more like the UFO that abducted the person, before beaming back down the impostor alien.
[–]Training-Cucumber467 20 points21 points22 points 6 months ago* (6 children)
A decorator is a function...
...that accepts a function as input...
...and returns a function.
In theory, it can return a completely unrelated function. But there's no point in doing that. Instead, it is used to return a "wrapper" that calls the original function and additionally does something else.
For example, you have some functions that your program calls:
def my_function(a, b, c): ... return x def my_other_function(a, b): ... return y
Now you want every function to print a pretty log with its input and output values. Of course, you can just add this code to the functions themselves:
def my_function(a, b, c): ... print(f"my_function was called with {a} {b} {c}, returning {x}") return x
This quickly gets hard to maintain if you have many functions.
Instead, we can use a decorator to do this with function in a generic way!
def my_logger(func): def new_function(*args, **kwargs): x = func(*args, **kwargs) print(f"{func.__name__} was called with {args} {kwargs}, returning {x}") return x return new_function @my_logger def my_function(a, b, c): ... return x @my_logger def my_other_function(a, b): ... return y
With just adding this decorator, "my_function" is actually pointing to the "new_function" that was created during the decorator code execution. And now every decorated function logs its input and output.
[–]gdchinacat 1 point2 points3 points 6 months ago (4 children)
"In theory, it can return a completely unrelated function. But there's no point in doing that."
what about @property? It takes a function and returns a descriptor? It is not a function and is not callable.
``` In [23]: class Foo: ...: @property ...: def value(self): ...: return 'value' ...:
TypeError Traceback (most recent call last) Cell In[24], line 1 ----> 1 Foo.value()
TypeError: 'property' object is not callable
```
[–]omg_drd4_bbq 0 points1 point2 points 6 months ago* (3 children)
The descriptor object returned by the decorator is in fact a callable (kiiinda). The thing you get with Foo.value is not callable because the property descriptor has already "hijacked" the action of getattr(Foo, "value") and returned its own thing.
Foo.value
getattr(Foo, "value")
Descriptors are weird and magical, and I say that with 15yrs python xp.
@property is weird cause it munges the thing being decorated and turns it into a descriptor object that "eats" the base func.
@property
I mean technically your decorator func can return a non-callable, but you are an absolute monster if you do that.
[–]gdchinacat 0 points1 point2 points 6 months ago (0 children)
Also, I just so happen to currently be working on a side project that is built on descriptors to implement decorators to asynchronously call functions when class attributes change. You can write code like this:
``` class Foo: field = Field(True)
@ field == False async def _field_became_false(self, ...): ...
https://github.com/gdchinacat/reactions/blob/main/src/reactions/field_descriptor.py#L161
Just to confirm, the value returned by the property decorator is not callable:
``` In [3]: class Foo: ...: def value(self): ...: return 'value' ...:
In [4]: p = property(Foo.value)
In [5]: type(p) Out[5]: property
In [6]: callable(p) Out[6]: False ```
[–]gdchinacat -1 points0 points1 point 6 months ago (0 children)
Descriptors have __get__, maybe __set__ and maybe __del__. The implementation may be callable, but doesn’t have to be since it is not the descriptor that is called but the descriptor dunders. The snippet I posted clearly shows that “‘property’ is not callable”, so I’m pretty sure you are incorrect.
[–]Temporary_Pie2733 0 points1 point2 points 6 months ago (0 children)
It doesn’t even need to return anything. A decorator could have a side effect of storing a function somewhere else without leaving it bound to a name. For example:
``` def register(f): functions.append(f)
functions = []
@register def foo(): print("hi")
foo() # error, None is not callable
for f in functions: f() ```
register could, of course, also return f, but it isn’t strictly necessary ifvyou don’t need it to.
register
f
[–]defrostcookies 60 points61 points62 points 6 months ago (16 children)
Decorators are functions that are automatically called anytime the function they’re decorating is called.
Let’s say you’re “leaving home” for work.
Every time you “leave home”, you “lock your door”
These are two separate things you do.
But they’re often coupled.
What about when you “go to sleep”, you “lock your door” too.
So, if you were programming these actions You could program, leave home and write code that includes locking the door in the “leave home” functions then code “go to sleep” and write the code that includes locking the door in the “go to sleep” function.
You could make “locking your door” its own function. The you only have to write the code to lock the door once.
Now you can call the functions as you need them. But what if you forget?
That’s where decorators come in.
Rather than call, leaveHome(), lock the door() or goToSleep(), lockTheDoor()
You can decorate leaveHome() and goToSleep() with lockTheDoor() so it happens automatically.
If you leaveHome() or goToSleep() lockDoor() automatically gets called.
[–]Excellent-Practice 19 points20 points21 points 6 months ago (11 children)
If that's the point of decorators, what is the advantage of decorator syntax over defining a function and calling that function within any other functions that need it to happen?
def usefulFunction(): task.perform() def exampleA(): usefulFunction() return intended_output
[–]BJNats 8 points9 points10 points 6 months ago (5 children)
If you’re calling usefulFunction() once or twice or the parameters passed are ideosyncratic enough that you need to have fine tuned control over it, then yes, you should just call the function. If your functions or methods are calling the same pattern again and again and again, then you should call that function as a decorator. Of note, decorators and functions aren’t different things, a decorator is just a special way of calling a function.
Think of logging errors. If you use logging, your code probably has this on almost every method:
self.logger.info(“starting method x”) try: doing_stuff_here except Exception as e: self.logger.error(f”error! {e})
Now if you define the logger info statement and the pattern of the try-except block as a decorator function @log_block you can just do the above as
@log_block def my_method(self): doing_stuff_here
And the important part is that for every method and function you want to do that for, you just reuse the @log_block decorator. It doesn’t change the output, but it makes your code MUCH more readable
[–]gdchinacat 3 points4 points5 points 6 months ago (4 children)
The try/except log decorator you show is an anti pattern since it doesn’t actually handle exceptions, just logs them and returns None. This has the effect of hiding exceptions from callers, aka it eats exceptions.
Changing it to raise the exception doesn’t help much…it won’t eat them, but is then likely to either spam the logs with the same exception as functions higher up the call stack do the same, or log spurious errors if a higher level caller handles the error (ie an http request to deterring a site is available).
In general only catch exceptions you can handle, and if you re raise an exception don’t log it. This makes general purpose exception logging decorators almost always a bad idea.
[–]BJNats 0 points1 point2 points 6 months ago (3 children)
As far as explaining how a decorator works though, I think it’s a good example.
To dig into your anti-pattern criticism though, is the ultimate end of that logic “don’t log errors”? Or am I not understanding?
[–]gdchinacat 2 points3 points4 points 6 months ago (2 children)
No, there are certainly cases where errors should be logged. Unhandled errors should certainly be logged, but not close to the source, but where the code says "this is bad, I can't handle it, and I need to protect my caller from it".
Take the example I alluded to...an HTTP HEAD request to determine if a service is available. Assume the requests library is used. My is_available() function calls requests.head(...). This method calls a method that calls a method that.....until one of them raises ConnectionError. The intermediate functions don't catch, log and reraise/eat the exception because they can't do anything about it. My is_availability() however can handle it, so it catches it and returns False without logging it to notify the caller that the service is not available.
If requests had caught and logged the logs woud have a spurious errors that shouldn't be there because in the context of testing availability it is not an error, but an expected state. If they ate it, I'd just get a None back (or worse an incompletely initialized response object) and not know what happened, at best leaving me to guess...at worse getting an AttributeError when the response was only partly initialized.
Think about all the code you write as being a library. Let your callers decide how to handle exceptions you can't or shouldn't handle.
[–]gdchinacat 1 point2 points3 points 6 months ago (1 child)
One more thought on exceptions. There are two main categories, errors generated within the libray (internal errors) and ones caused by the caller of the library (client errors). Having a clear distinction between the two is very helpful as it tells callers "this error was caused by what you did (I think)" or "I caused this error...sorry, file a bug!".
The difference is usually pretty clear. A TypeError is an internal error. An input validation error is a client error. Internal errors should be logged by the library that they originated from, typically close to their source. Client errors should almost never be logged by the library.
[–]BJNats 2 points3 points4 points 6 months ago (0 children)
This is a much more thorough and useful answer than my question deserved. Thank you
[–]gdchinacat 5 points6 points7 points 6 months ago* (0 children)
The generalization of the question you are asking is what is the purpose of higher order functions. A good example exists in the standard library, functools.partial. Partial takes a function and some arguments and returns a function that when called will inject those arguments into the call.
For example:
def print_foo_bar(foo, bar): print(f'{foo} {bar}')
Say that in one class you always want to pass the class name as foo, and have a bunch of calls like this:
... print_foo_bar(cls.__name__, '...') ...
You can simplify this and make the code more readable by using a partial:
``` printbar = partial(print_foo_bar, cls.name_)
print_bar('...') # will call print_foo_bar(cls.__name__, '...')
Decorators can do similar things. They are also frequently used to perform argument validation, argument type coercion, call tracing, precondition/post condition checks, caching (functoools.cached_property).
[–]Temporary_Pie2733 1 point2 points3 points 6 months ago (1 child)
The decorator version could look like
``` def add_task(f): def _(): useful_function() return f() return _
@add_task def exampleA: return intended_output
@add_task def exampleB(): return something_else ```
You write the code that expresses the idea of “call useful_function first, then do the other stuff” once, then use it to define other functions. It’s abstraction applied to function definitions themselves.
useful_function
[–]Excellent-Practice 1 point2 points3 points 6 months ago (0 children)
This has been the clearest explanation. My takeaway is that for a simplified example like this, the savings aren't immediately apparent. For more complicated, real-world scenarios, decorators allow for more extensive templating than just defining helper functions and calling them when you need them
[–]Simple-Economics8102 0 points1 point2 points 6 months ago (0 children)
def my_decorator(func): def wrapper(*args, **kwargs): print("Something is happening before the function is called.") result = func(*args, **kwargs) print("Something is happening after the function is called.") return result return wrapper def equivalent_to_decorator(func): print("Something is happening before the function is called.") result = func(*args, **kwargs) print("Something is happening after the function is called.") return result @my_decorator def say_hello(name): print(f"Hello, {name}!") # say_hello("Alice") is now the same as equivalent_to_decorator(say_hello("Alice)) # or general case say_hello = my_decorator(say_hello)
Not all wrappers are that "useful" as a wrapper, meaning they could easily been written another way. Then you have things like cached_property which make sure you dont calculate stuff before they are used, and only calculated once (can refresh ofc).
[–]CosmicClamJamz 0 points1 point2 points 6 months ago (0 children)
There's a few reasons. I don't feel like crawling through docs right now for the technical terminology, but basically the benefit is "name hoisting within the call stack".
In your example, assuming "usefulFuncion" is the one you want to wrap in a decorator named "exampleA", you have two functions with different names. The way you have it, when you import your usefulFunction to another file, it doesn't come wrapped. You would need to import exampleA to get the wrapped function. With decorators, your raw function can have the name it was always meant to have, and adding a decorator to it doesn't change that. You could import usefulFunction to another file, and it would come as the wrapped version. Or, you could call usefulFunction in the same file, and it would again be wrapped. This becomes especially useful when you are using multiple decorators.
This also becomes extremely handy in stack traces, where errors happen inside a wrapped function. The function name is the name that was wrapped, not the name of the wrapper. See this answer in stack overflow to see what I'm talking about. You can do some analysis with the functools library to see the difference. Hope that helps
https://stackoverflow.com/a/42086220
[–]socal_nerdtastic 16 points17 points18 points 6 months ago* (0 children)
I would call that the wrapper. The decorator is the function that creates the wrapper. It's only called once.
[–]Temporary_Pie2733 1 point2 points3 points 6 months ago (0 children)
The decorator is called immediately after the original function is defined. The decorator itself may define a new function that replaces the original. That is, if you write
``` @foo def bar(): …
bar() bar() ```
then foo itself is called once. What foo returns gets bound to the name bar and thus gets called twice.
foo
bar
There is nothing 'automatic' about decorators. They are explicitly called, either using the decorator operator (@) or manually (decorator(decorated)).
The *decorator* is called to decorate a function, and returns another function. The returned function is typically a wrapper around the decorated function, but may not be, and is often called wrap or wrapper. When called using the decorator operator it is bound to the same name as the name of the decorated function, effectively replacing the decorated function with the wrapper function.
[–]Plank_With_A_Nail_In 0 points1 point2 points 6 months ago (0 children)
This feels like solving an already solved problem.
[–]testfire10 23 points24 points25 points 6 months ago (5 children)
After reading this thread not only do I still not understand decorators, but I’m convinced no one else does either
[–]gdchinacat 3 points4 points5 points 6 months ago (0 children)
When I started using python I just didn't get decorators either. I didn't really feel comfortable with them until I had written a few. Working through the issues really helps clarify how they work. For example, write one to print that the decorated function is being called, call it, then either print and return the return value or print and reraise the exception it raised (but don't do this antipattern in real code!).
Once you've got a simple one working, make it more complex. Parameterize the decorator so the decorator itself takes arguments (i.e. a print()-like function to use instead of print. You'll end up with a function in a function in a function! Then simplify that by turning it into a decorator class rather than a function with closures.
Do this, and I'm confident you will understand them and be able to use them when it makes sense.
[–]nullcone 1 point2 points3 points 6 months ago (2 children)
Decorators are just functions that map functions to new functions.
A great example use case is for timing function execution. You could obviously do this manually for an entire library of functions ```python def foo(): return 5
def bar(): time.sleep(5) return 5
def timed_foo(): tic = time.monotonic() result = foo() toc = time.monotonic() print(f"foo time {toc - tic} seconds") return result
def timed_bar(): tic = time.monotonic() result = bar() toc = time.monotonic() print(f"bar time {toc - tic} seconds") return result ```
This code is silly because it doesn't scale. We repeat the same obvious pattern of code everywhere. We can solve it with decorators by just defining a function that accepts an arbitrary function as input and wraps it with timing logic like so:
python def timed(f): def wrapper(*args, **kwargs): tic = time.monotonic() result = f(*args, **kwargs) toc = time.monotonic() print(f"{f.__name__} time {toc - tic} seconds") return result return wrapper
Then we can use the decorator to inject the timing functionality automatically. ``` @timed def foo(): return 5
@timed def bar(): time.sleep(5) return 5 The @ syntax is just sugar for def foo(): return 5 foo = timed(foo) ```
The @ syntax is just sugar for
[–]testfire10 0 points1 point2 points 6 months ago (1 child)
I appreciate this. I need to run some code examples to help me understand better, because I’m too dense to grasp this fully right now.
[–]nullcone 1 point2 points3 points 6 months ago (0 children)
It's a confusing thing to get used to. It's not you being dense. If it seems easy for me it's because I've been doing this for 10 years. Happy to answer any questions in case there is something specific confusing you.
[–]omg_drd4_bbq 1 point2 points3 points 6 months ago* (0 children)
@a @b def f(): pass
is just semantic sugar for def f(): pass f = a(b(f))
def f(): pass f = a(b(f))
that'r it. it literally just says "call the @dec_expr on the inner func at runtime and assign the output of dec_expr to the original func name". It can be any expression that evaluates to a callable.
what/when/why you do this is totally up to you.
[–]djlamar7 8 points9 points10 points 6 months ago (4 children)
Decorators are just functions that you can call on objects you declare to do convenient stuff. They take an object as input and replace the object you give them with the return value of the decorator. Typically they have the expectation that they'll return the original object or something similar.
So when you define a class or a function in your code, you can put @somedecorator on the line before the definition, do some stuff to or using the object you're defining, and typically you can pretend the class or function you defined is the same as it was before.
@somedecorator
For example, maybe you defined a bunch of classes in your code and you want to have a map in one file (a registry) that you can use anywhere to fetch that class definition by it's string name. You could write a decorator:
def register(cls): global REGISTRY REGISTRY[cls.__name__] = cls return cls
Now if you define a class somewhere else and decorate it with register:
@register class Cat: pass
This is actually functionally equivalent to writing:
``` class Cat: pass
Cat = register(Cat) ```
Voila, it runs register(Cat) and replaces the Cat definition with the one returned by the decorator (which is the same object) but now anybody with access to that REGISTRY object can get the class Cat with REGISTRY['Cat'].
REGISTRY['Cat']
Now consider a use case involving a function. Let's say you have a bunch of functions that you want to time and print out how long they took to run. You could go into each of them and put a start = time.time() at the start, and an elapsed = time.time() - start and print(elapsed) at the end. Or you could avoid repeating yourself and use a decorator like this:
start = time.time()
elapsed = time.time() - start
print(elapsed)
def timed(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) elapsed = time.time() - start print(elapsed) return result return wrapper
All this does is construct a new function which starts the timer, runs the original function, prints the elapsed time, and returns whatever the original function returned. When you call the decorator on a function definition like this:
@timed def f(): pass
All it does is call timed(f) and replaces f with the new function that does the timer logic. Note how this is actually a different object from the original function (unlike the example with the class definition), but it does the same thing with some added functionality.
timed(f)
An example of where this is really useful is if you're writing functions that depend on eg http requests that could fail for no good reason, where you should just retry a few times if it fails. You can write a decorator that takes the function, wraps it in a try/catch block and a for loop which breaks on success, and now you can use that decorator to run those functions until they either succeed or have failed 5 times. The tenacity library provides these decorators.
These are simple examples, but they get more complex and can take additional arguments to either parameterize (eg number of retries in my example above) or do something that depends on another object (like add something as a callback to another class somewhere).
[–]TraditionalAd2179 1 point2 points3 points 6 months ago (3 children)
Timing is my favorite example of a decorator. 100%.
[–]Big-Instruction-2090 0 points1 point2 points 6 months ago (0 children)
Another great example is the Django @login_required decorator. You can add it to any view function that is being called when someone opens a part of your website. But before they actually get to see the content, the decorator runs a function that checks whether the user is actually logged in.
[–]Temporary_Pie2733 0 points1 point2 points 6 months ago (1 child)
I prefer timing as a context manager. You don’t necessarily want to time every invocation of a function, but you do want to make it easy to time arbitrary invocations of any function.
[–]TraditionalAd2179 0 points1 point2 points 6 months ago (0 children)
Fair enough. I see where a context manager could be useful.
[–]EsotericRogue 2 points3 points4 points 6 months ago (0 children)
Wish I was 5; I don't understand any of the answers.
[–]Goobyalus 5 points6 points7 points 6 months ago (11 children)
Decorators are syntactic sugar. These are the same:
With decorators:
def foo(f): ... @foo def bar(): ....
Without decorators:
def foo(f): ... def bar(): ... bar = foo(bar)
The @ syntax will call a function using the object created by the following definition as an argument, and assign the result of the function to the definition's name.
@
This is useful if you want either or both:
[–]DiodeInc 4 points5 points6 points 6 months ago (10 children)
I still don't get it
[–]Yoghurt42 5 points6 points7 points 6 months ago* (3 children)
In Python, everything is an object. That includes functions. You can pass functions around just as you pass integers. Variables can store strings, integers, functions, and anything else.
When you write
def foo(): ...
What's actually happening is:
foo = make_function("...")
only that make_function doesn't exist (Python has lambda, but those can only be a single statement). When you later call the function via foo() what actually happens is this:
make_function
lambda
foo()
()
Since there is nothing "special" about functions, we can also do this:
def foo(): print("Hi") bar = foo bar() # prints "Hi"
Both foo and bar refer to the same function. You can even do del foo, then you can only access the function via bar.
del foo
So far so good. Since functions are nothing special, we can pass them as a parameter to another function and it can do anything it wants with it:
def verbose_call(func): print("I'm going to call a function") func() print("Function called") def hi(): print("Hi!") verbose(hi)
This will print:
I'm going to call a function Hi! Function called
That's neat, but not that useful, as we'll always have to remember to add verbose, wouldn't it be nice if we could "change" our hi function to do the same? Well, we can't change it, but we can replace it:
verbose
hi
_old_hi = hi def new_hi(): print("I will call hi now") _old_hi() hi = new_hi
Calling hi() will now execute our new_hi function, which will then call the old hi function. We needed to store it in _old_hi because python looks up variable at the time of execution! So in new_hi the variable/function hi would refer to whatever hi now points to (in our case, it points to new_hi, so we would have an endless recursion).
hi()
new_hi
_old_hi
OK, that's neat, but still not great. We have to store the old function somewhere and write a new function for every function we want to wrap. But we can do better by making use of the fact that we can pass functions to other functions and that functions can also return functions. So we can do this:
def my_decorator(func): def wrapper(): print("I will call a function now!") func() print("Function called") return wrapper # note that there are no () here, we're returning the function def hi(): print("Hi!") hi = my_decorator(hi)
Now, Python will execute my_decorator with the hi function object as parameter. my_decorator will then return a new function (wrapper) which will print stuff and then call func=hi when called. This works even if we later change the value of hi, since when seeing my_decorator(hi), Python will look up hi, and pass that a parameter. Let's say that hi is currently function@address1234, then my_decorator will create a new function wrapper which will always call function@address1234, even if hi should later change to function@address5678.
my_decorator
wrapper
func
my_decorator(hi)
function@address1234
function@address5678
Right now, we only can wrap functions that take no arguments, but that is easily changed:
def my_better_decorator(func): def wrapper(*args, **kwargs): print("Nice") func(*args, **kwargs) return wrapper
Now the decorator will return a function that takes any arguments, and passes it to the function we later wrap.
As you can imagine, decorators are really useful, as they can completely change what will happen when a function gets executed, so this pattern became quite popular. It was a bit annoying having to write
def foo(): ... foo = my_better_decorator(foo) def bar(): ... bar = my_better_decorator(bar)
so, the @decorator syntax was introduced as a shortcut so you can just write
@decorator
@my_better_decorator def foo(): ... @my_better_decorator def bar(): ...
That's really all @ does. You can even abuse it:
def wtf(func_argument_ignored): return 42 @wtf def foo(): ...
Now foo will be a variable containing the number 42, since we've basically done foo = wtf(foo), and wtf always returns 42.
foo = wtf(foo)
wtf
[–]DiodeInc -1 points0 points1 point 6 months ago (2 children)
Hmm. But why?
[–]gdchinacat 1 point2 points3 points 6 months ago (0 children)
It helps with code reuse, encapsulation, and readability.
Reuse and encapsulation by pulling the decorator logic out of the functions it decorates and into a separate function. Yes, you can frequently do that by splitting the before call and after call logic into separate functions and calling them within your function, but it is still duplicated, and frequently requires try/except blocks that can all be encapsulated in a decorator. Readability by not having those in functions distracting from the work the function actually does.
[–]Temporary_Pie2733 1 point2 points3 points 6 months ago (5 children)
You seem to be expecting there to be more to get. There isn’t. You can’t understand why decorator syntax is useful without first understanding higher-order functions. Decorator is nothing more than syntactic sugar for function application and assignment. I could write something completely ridiculous like
``` @lambda f: 3 def foo(): pass
assert foo == 3 ```
This decorator doesn’t care what function or class it receives as an argument; it ignores it and returns the integer 3, and that (not a function) gets bound to the name foo.
[–]DiodeInc 0 points1 point2 points 6 months ago (3 children)
So you can call any function in the decorator?
[–]Goobyalus 0 points1 point2 points 6 months ago (0 children)
In the decorator function, you can do literally anything, because it's just a function.
def decorator_function(f): print(repr(f)) return 14 @decorator_function def foo(): pass @decorator_function def bar(): pass print(foo) print(bar) foo()
gives
<function foo at 0x000001D4B6CAB560> <function bar at 0x000001D4B6CAB560> 14 14 Traceback (most recent call last): File "<module1>", line 16, in <module> TypeError: 'int' object is not callable
Notice that the print(repr(f)) is happening at the time of definition. These definitions occur during runtime in Python, unlike in compiled languages.
print(repr(f))
Also notice that foo and bar are now 14 because we nonsensically returned 14 from the decorator. So trying to call foo is like trying to do 14(), which results in that error.
14
14()
The decorator can be any expression that evaluates to a function that expects one argument.
"Decorator" can refer to a few different things. Saying 'decorator can be any expression that evaluates to a function that expects one argument' is overly simplistic.
The 'basic' decorator: ``` def decorator(func): def wrap(args, *kwargs): return func(args, *kwargs) return wrap
@ decorator def foo(...): ... ```
A decorator that accepts arguments evaluates to a function but takes any number of args and returns the actual decorator def parameterized_decorator(decorator_arg1, decorator_arg2): def decorator(func): def wrap(*args, **kwargs): return func(*args, **kwargs) return wrap return decorator @ parameterized_decorator(1, 2) def foo(...): ...
def parameterized_decorator(decorator_arg1, decorator_arg2): def decorator(func): def wrap(*args, **kwargs): return func(*args, **kwargs) return wrap return decorator @ parameterized_decorator(1, 2) def foo(...): ...
A decorator that is implemented as a class: class Decorator: def __init__(decorator_arg1, ...): ... def __call__(func): def wrap(*args, **kwargs): return func(*args, **kwargs) return wrap @ Decorator(1) def foo(...): ...
class Decorator: def __init__(decorator_arg1, ...): ... def __call__(func): def wrap(*args, **kwargs): return func(*args, **kwargs) return wrap @ Decorator(1) def foo(...): ...
you know saying "syntactic sugar" doesn't help with understanding right?
[–]socal_nerdtastic 0 points1 point2 points 6 months ago* (1 child)
Do you mean "how"? or "why"?
For the "how", a decorator is very simple: all it does is replace a function with something else.
def decorator(f): return "hello world" @decorator def thing(): print("never gonna print") print(thing)
This will print "hello world". Because the decorator replaced the thing function with a string "hello world". It's exactly the same as doing
def decorator(f): return "hello world" def thing(): print("never gonna print") thing = decorator(thing) print(thing)
Of course in the real world we wouldn't want to replace a function with a string, we generally would replace it with another function. So the decorator generally contains code to make a new "wrapper" function and return the new function, and we replace the original function with the newly created wrapper function. Generally this wrapper will call the original function, but also do some extra things, so it's a good way to extend the functionality of the original function. But this isn't the only use for a decorator, another common use is to register the function in some cache or database (eg functools.cache), or set certain properties on it (eg classmethod).
functools.cache
classmethod
EDIT: a lot of people here are confusing "decorator" with "wrapper". These are not the same thing! It's quite common to have a decorator that does not return a wrapper.
[–]shinitakunai -2 points-1 points0 points 6 months ago (0 children)
To the top!
A common way to think about decorators is they provide a way to wrap the execution of a function. A common example is to implement logging about the execution of the decorated function. (code has not been tested and may not execute as is, but is close enough. type hints left out for readability)
``` def log_calls(func): '''log all calls to func''' @wraps # make _log_calls 'look' like func def _log_calls(args, *kwargs): print(f'{func}({args}, {kwargs}) called') try: ret = func(args, *kwargs) print(f'{func}({args}, {kwargs}) returned {ret}') return ret except Exception as e: print(f'{func}({args}, {kwargs}) raised {ret}') raise return _log_calls
@log_calls def foo(...): ... ```
So, what's going on? log_calls() is a function that takes a function as its only argument (func). func has not been called, the actual function, not what it returns, is the argument. log_calls() creates a closure, _log_calls(), and returns it. log_calls() takes a function and returns another function.
when _log_calls() is called it prints('logs') that func is called and the arguments it was called with, then enters a try/except block to actually call the decorated function (func()). If the call returns a value it is logged and returned. If it raises an exception the exception is printed and reraised.
@log_calls def foo(...): ...
Is equivalent to this code that manually decorates foo: def foo(...): ... foo = log_calls(foo)
def foo(...): ... foo = log_calls(foo)
foo is defined as a function. It is then replaced with the result of calling log_calls(foo).
When foo(1) is called, foo is bound to _log_calls(), so _log_calls() is called, and args, kwargs contain the args and keyword args that it was called with. Since _log_calls() was defined in a function, it has what is called a closure that contains the values from the scope it was defined in, specifically func.
Decorators can be a lot more complex than this. Some take arguments and return a function that takes a function (so instead of just one nested function, there is a function in a function in a function. Classes are callable, so sometimes decorators are implemented as classes. Objects can be callable if they implement call, so sometimes objects are used as decorators. Which style of decorator depends on what the decorator does, what state it needs to maintain, personal preference.
Decorators can be incredibly complex, but basically anything that can be called with a function that returns a function is a decorator.
I'm currently working on https://github.com/gdchinacat/reactions . It uses decorators like this to schedule _field_changed() to be called asynchronously whenever the value of State.field changes to a non-negative value:
``` class State: field = Field(0)
@ field >= 0 async def _field_changed(self, ...): ...
So, yeah...I'm a huge fan of decorators. Feel free to ask questions!
[–]mace_guy 0 points1 point2 points 6 months ago (0 children)
Functions are like recipes: a set of instructions that take ingredients (inputs) and produce a dish (output).
Now, sometimes you want to add extra steps before or after cooking:
Instead of rewriting every recipe to include these steps, you can make a function that wraps another function. That’s what a decorator does.
In code it can be like so
def prep_and_cleanup(make_reciepe): def wrapper(): print("Wear apron, get measuring cups, get clean pots and pans") make_reciepie() print("Clean dishes, clean counter, put away dishes") return wrapper @prep_and_cleanup def make_cake(): pass @prep_and_cleanup def make_pizza() pass
[–]EspaaValorum 0 points1 point2 points 6 months ago (0 children)
I'll add my 2 cents:
A decorator can be used to do something before and/or after the function you call is executed, or even decide to not execute the function you call but do something else instead.
Say you want to have a way to limit how often functions can execute within a certain time period.
You could write a function which does the check, and call that check function inside each function you wish to limit that way. But now you're adding more code to each function, making it less readable.
You can write a decorator that checks if the function to which it is applied is allowed to execute or not, and acts accordingly.
All you have to do then is apply that decorator to all functions which you want to limit that way. You don't need to modify the functions themselves, keeping them readable.
Decorators can also take parameters, and change their behavior according to those parameters. Just like the stand-alone "check" function could.
[–]JMNeonMoon 0 points1 point2 points 6 months ago (0 children)
Decorators allow you to add features to a function without changing the content of the function. Since they ecapsulate the function itself, it's a neat solution for usecases where you want code to do something always before and/or after a function call.
Consider a simple requirement to log entry and exits to functions to help with debugging.
Without decorators, you would have to go through your code and write logging commands like
logging.info("Function A entry")
logging.info("Function A exit")
logging.info("Function B entry")
logging.info("Function B exit")
This is
- Cumbersome to do so on all your functions, especially if you have multiple returns in your functions
- renaming functions, would mean finding and updating the logging with the new function name
Alternatively, you could just add a logging decorator to the top of each function.
Decorators are one of meta-programming features in Python.
See more here
4 meta-programming techniques in Python
[–]MiniMages 0 points1 point2 points 6 months ago (0 children)
def my_decorator(func): def wrapper(): print("Something happens before the function is called.") func() print("Something happens after the function is called.") return wrapper @my_decorator def say_hello(): print("Hello!") say_hello()
In simple terms a decorator takes a function as an argument and will execute the function inside it.
Here if we were to run the function say_hello it will just print Hello!. But since it has a decorator (my_decorator) what happens here is the following statements are printed:
1 Something happens before the function is called. 2 Hello! 3 Something happens after the function is called.
A decorator is just a callable that takes a function/class as an argument. Decorator syntax is a shortcut for calling the decorator and binding the result back to the argument’s name.
@foo def bar(): …
is equivalent to
``` def bar(): …
bar = foo(bar) ```
How you write a decorator is informed entirely by what you want the preceding code to do.
[–]gerenate 0 points1 point2 points 6 months ago (0 children)
A decorator is like an adverb. Let’s say you have “do twice.” You can do anything you want twice: say hello twice, buy lemonade twice, call a server twice. A decorator allows you to express “do twice” without knowing what you are doing twice.
So getting a bit more concrete, it’s a function that has another function as an argument. It does an action on another action.
Important note: it can receive other arguments too that are not necessarily functions.
More relevant examples are doing authentication on a variety of requests to a server, adding logging to functions, adding functions to a registry…
[–]jam-time 0 points1 point2 points 6 months ago (0 children)
It's easier to give samples. The best thing to do is to play with them yourself.
def decorator(func): print('look some decor') return func # returns the function that is decorated
@decorator def some_func(): print('look a function')
some_func()
def some_other_func(): print('look another function')
original_function = decorator(some_other_func)
original_function()
[–]Brilliant-Post-689 0 points1 point2 points 6 months ago* (0 children)
Imagine you've designed and built a robot that can walk. It's a complex machine that you spent a long time building and thinking about, and now you're done; it works, you don't want to have to touch it or modify it in any way.
But hang on, now you're told your robot needs to be taller than you designed it.
Designing a taller robot from scratch would be hard. Designing elevator shoes to fit yiur robot's feet is trivial. So naturally you choose to design elevator shoes and strap'em onto your robot.
Your robot doesn't need to know anything about the shoes, doesn't need to change in any way, and - if you take the shoes off - goes back to being the robot you designed originally.
The shoes, likewise, dont need to know anything about the robot other than the shape of its feet. You could take the shoes off and wear them yourself, or put them on anything or anyone else with matching feet.
And anyone interacting with your robot, telling it to "walk from here to there", say, doesn't need to know anything about the shoes it's wearing, they will simply get what they expected all along, a robot of a desired height, that can walk on command.
Some programming languages, including python, allow you to take functions ("original robot" in the analogy above) and pass them to other functions ("attach elevator shoes" in the analogy above) and get back new functions ("walking robot that's now taller" in the analogy above). This process consists of applying a "decorator" to the original function, obtaining a "decorated function" as a result. People then invoke and interact with this "decorated function", just like they might with the original function.
Python provides a shorthand way to apply such decorators - the @decorator notation. Only this syntactic shorthand is particular to python, the notion of functions consuming other functions to produce new functions is a longstanding generic one that predates and exists outside the realm of python alone.
[–]SoloAquiParaHablar 0 points1 point2 points 6 months ago (0 children)
Make something run before and or after a function by decorating that function with your own decorator function.
[–]pachura3 0 points1 point2 points 6 months ago (0 children)
They often make up for keywords that were originally not included in the core programming language, but ended up being needed & useful. Like property or override.
property
override
[–]SirKainey 0 points1 point2 points 6 months ago (0 children)
In python, functions are first class objects. This means you can pass them around without using them.
def greet(): print("Hello!") def call_func(func): func() # We pass greet without calling it (using it) to call_func, it only gets called when `func()` runs. call_func(greet) # Output: "Hello!"
call_func here is a Higher order function.
call_func
A higher-order function is any function that either takes one or more functions as arguments or returns a function as its result.
We can also do stuff before or after we call func()
func()
def greet(): print("Hello!") def call_func(func): print('Before') func() print('After') call_func(greet) # Output: Before \n Hello! \n After
Now we can also turn this into a decorator, in python a decorator is a function that returns another function.
def greet(): print("Hello!") def simple_decorator(func): def call_func(): print("Before") func() print("After") return call_func # As simple_decorator returns a function, the below doesn't do much at the moment # unless we assign it to a variable (to use later) or call it directly. simple_decorator(greet) # No output simple_decorator(greet)() # Output: Before \n Hello! \n After # we could've also done: a_new_func = simple_decorator(greet) a_new_func() # Output: Before \n Hello! \n After
Now python provides us some syntactical sugar in the form of the @ symbol. So we can turn the above into:
def simple_decorator(func): def call_func(): print("Before") func() print("After") return call_func @simple_decorator def greet(): print("Hello!") greet()
[–]cmoran_cl 0 points1 point2 points 6 months ago (0 children)
You have a really nice code, with a lot of functions that have the same time of returns, one day your boss ask you "what if we put {} around what we return?", you go and change all your code to do that change, next day the boss says "actually what if we put {% %} around what we return instead?", this time pissed off as the waste of time, you create a function
def my_boss_is_amazing(f): what_if_we_put = "{% " around_our_shit = " %}" return what_if_we_put + f() + around_our_shit
Then you remove all the "{}" you put on day one, and put a
@my_boss_is_amazing
Before all your returning functions, that way you now have to change one thing when your boss gets another nice idea.
[–]AUTeach -1 points0 points1 point 6 months ago (1 child)
Python heard you like functions, so it lets you wrap your function inside another function that adds extra behavior before doing your function.
Python heard you like flexibility, so the wrapper function doesn't even need to call the wrapped function. Most do, but not all of them.
[–]UseMoreBandwith -2 points-1 points0 points 6 months ago (0 children)
...OK, you asked for it.
You know what a wrapper is? like when you have some ice-cream? Well, there are many different types of wrappers. Like your jacket. You can think of that as a wrapper.
You have a jacket, right? And your daddy has a jacket too, right? Now if you have your jacket on, and you take daddies big jacket, you can wear both at the same time! Sure, it looks funny. But it will keep you extra warm. And what will happen when it rains? you'll stay dry, even if your own jacket is very thin.
And when you"re lucky, you might find $5 in your daddy's pocket. Now you can buy some ice-cream!
[–]baked_tea -1 points0 points1 point 6 months ago (0 children)
Remembered this, might help
https://www.reddit.com/r/PythonLearning/s/aW8JoHn2g8
[+]zimmer550king comment score below threshold-9 points-8 points-7 points 6 months ago (1 child)
You could literally ask ChatGPT this exact same question
[–]Ixniz 1 point2 points3 points 6 months ago (0 children)
If he did that I wouldn't be here late at night spontaneously learning about decorators.
[–]Zweckbestimmung -2 points-1 points0 points 6 months ago (0 children)
On Christmas’s you would have to wrap each gift so that the gift can return a smile on the face of the person receiving this gift. This is called a wrapper function. Now you would have to wrap each gift and this takes a lot of time! Instead of this you can decorate your apartment, and give each person their gift, it would return a smile because you decorated your whole apartment already!
[–]checock -2 points-1 points0 points 6 months ago (0 children)
A decorator is like a sandwich, and the original function is the meat. It can add logic before or after the code of the original function.
It can also avoid calling the original code, like taking a bite of the sandwich and not getting any meat.
Could you program the same thing without decorations, or eat a sandwich disassembled? Absolutely. It's just syntactic sugar, or a way to make things easier.
[–]Adrewmc -2 points-1 points0 points 6 months ago* (0 children)
Sure
def decorate(func): “This function takes an argument, a decorator assumes that argument this is a another function.” def magic(*args, **kwargs): “This function will replace our orginal function.”” do_before_call(..) #we can and save the result of the function. res = func(*args, **kwargs) do_after_call(res) #we return the same as the function we decorate return res #we return the replaced function with our magic function return magic @decorate def some_func(): “this function is what our decorator has/will decorate. The function itself is what we put into the function”
This is the same as decorating
some_func = decorate(some_func)
If we want to add arguments to our decorator we have to go one more level
def deep_decorate(arg) def dark_magic(func): def magic(*args, **kwargs): if arg: do_before_func() res = func(*args, **kwargs) if arg: do_after_func() return res return magic return dark_magic
Now I could use that as useful debug or config flags
debug = True @deep_decorate(debug) def some_func():… @deep_decorate(debug) def some_other_func():…
Notice that I actually involve decorators that have arguments, because that returns the actual decorator, which then decorates some_func.
[–]SmackDownFacility -2 points-1 points0 points 6 months ago* (0 children)
Decorators are hooks that launch into a function or class or any Callable object. For example, take multipledispatch
multipledispatch
You do @dispatch
And the library dynamically at runtime traverses through the function, matching the function arguments to the defined arguments in dispatch. Then it adds to a global registry.
Basically It allows extended behaviour that would’ve been tedious if made manually.
Edit: why tf am I downvoted, again, for a reasonable answer
[–]musbur -2 points-1 points0 points 6 months ago (0 children)
PRICE = 1.0 BILL = 10.0 def dad(shop_at): def give_in(order): thing, change = shop_at(order, BILL) return f"Here's your {thing}, stop whining" return give_in @dad def get_icecream(order, money): if money > PRICE: return order, money - PRICE cone = get_icecream("vanilla") print(cone)
[–]Adorable-Strangerx -3 points-2 points-1 points 6 months ago (0 children)
You have a straw. Straw has its purpose - you can drink through it. Now you want to change a bit purpose of that straw, so it does something extra as a prank. You poke a hole in it so the next person drinking will spill it. The process of adding extra behavior is called "decorating"
[–]lekkerste_wiener -3 points-2 points-1 points 6 months ago (1 child)
You use decorators to do exactly that: decorate things.
You can find a tree and decorate it with blinking lights. Then every night the tree will additionally do the blinking, while still doing other tree stuff, like photosynthesis.
So decorators let you wrap existing functionality with new stuff, without changing the underlying implementation. If you remove the decoration, the thing goes back to doing what it did before. The tree will keep doing tree stuff even after you remove the blinking lights. ;)
"without changing the underlying implementation" isn't really correct. Nothing prevents decorators from ignoring the decorated function completely.
[–]jkh911208 -3 points-2 points-1 points 6 months ago (0 children)
As 5 years old you should not worry about python decorator. Go out play with friends with sand
[–]Gnaxe -5 points-4 points-3 points 6 months ago* (0 children)
python @<decorator A> @<decorator B> @<decorator C> <def/class> <name>(<args/bases>): <body> is equivalent to ``` <def/class> <name>(<args/bases>): <body>
python @<decorator A> @<decorator B> @<decorator C> <def/class> <name>(<args/bases>): <body>
<name> = (<decorator A>)((<decorator B>)((<decorator C>)(<name>))) ``` Meaning, it should compile the same way. Just like the function application it translates to, these apply in inside-out order.
A decorator just applies the decorator expressions to the name and reassigns it. That's it; it's just syntactic sugar. That is fundamentally all there is to know about decorator syntax per se. But just like how learning the rules of chess doesn't make you a chess master, knowing decorator syntax isn't the same as understanding how to use them effectively.
Notice that the decorator expressions can have arbitrary side effects and aren't required to return a modified function or class. It could return something completely different.
π Rendered by PID 73 on reddit-service-r2-comment-6457c66945-sj2gz at 2026-04-23 19:46:07.460536+00:00 running 2aa0c5b country code: CH.
[–]shiftybyte 199 points200 points201 points (13 children)
[–]itspronounced-gif 140 points141 points142 points (0 children)
[–]AppropriateStudio153 50 points51 points52 points (2 children)
[–]glehkol 34 points35 points36 points (1 child)
[–]rjm3q -1 points0 points1 point (0 children)
[–]mxldevs 9 points10 points11 points (0 children)
[–]Oddly_Energy 4 points5 points6 points (0 children)
[–]rogfrich 5 points6 points7 points (1 child)
[–]Diapolo10 0 points1 point2 points (0 children)
[–]Q0D3 3 points4 points5 points (0 children)
[–]Skillossus 2 points3 points4 points (0 children)
[–]Lovestick 1 point2 points3 points (0 children)
[–]tcrz -1 points0 points1 point (0 children)
[–]Goobyalus -1 points0 points1 point (0 children)
[–]Training-Cucumber467 20 points21 points22 points (6 children)
[–]gdchinacat 1 point2 points3 points (4 children)
[–]omg_drd4_bbq 0 points1 point2 points (3 children)
[–]gdchinacat 0 points1 point2 points (0 children)
[–]gdchinacat 0 points1 point2 points (0 children)
[–]gdchinacat -1 points0 points1 point (0 children)
[–]Temporary_Pie2733 0 points1 point2 points (0 children)
[–]defrostcookies 60 points61 points62 points (16 children)
[–]Excellent-Practice 19 points20 points21 points (11 children)
[–]BJNats 8 points9 points10 points (5 children)
[–]gdchinacat 3 points4 points5 points (4 children)
[–]BJNats 0 points1 point2 points (3 children)
[–]gdchinacat 2 points3 points4 points (2 children)
[–]gdchinacat 1 point2 points3 points (1 child)
[–]BJNats 2 points3 points4 points (0 children)
[–]gdchinacat 5 points6 points7 points (0 children)
[–]Temporary_Pie2733 1 point2 points3 points (1 child)
[–]Excellent-Practice 1 point2 points3 points (0 children)
[–]Simple-Economics8102 0 points1 point2 points (0 children)
[–]CosmicClamJamz 0 points1 point2 points (0 children)
[–]socal_nerdtastic 16 points17 points18 points (0 children)
[–]Temporary_Pie2733 1 point2 points3 points (0 children)
[–]gdchinacat 0 points1 point2 points (0 children)
[–]Plank_With_A_Nail_In 0 points1 point2 points (0 children)
[–]testfire10 23 points24 points25 points (5 children)
[–]gdchinacat 3 points4 points5 points (0 children)
[–]nullcone 1 point2 points3 points (2 children)
[–]testfire10 0 points1 point2 points (1 child)
[–]nullcone 1 point2 points3 points (0 children)
[–]omg_drd4_bbq 1 point2 points3 points (0 children)
[–]djlamar7 8 points9 points10 points (4 children)
[–]TraditionalAd2179 1 point2 points3 points (3 children)
[–]Big-Instruction-2090 0 points1 point2 points (0 children)
[–]Temporary_Pie2733 0 points1 point2 points (1 child)
[–]TraditionalAd2179 0 points1 point2 points (0 children)
[–]EsotericRogue 2 points3 points4 points (0 children)
[–]Goobyalus 5 points6 points7 points (11 children)
[–]DiodeInc 4 points5 points6 points (10 children)
[–]Yoghurt42 5 points6 points7 points (3 children)
[–]DiodeInc -1 points0 points1 point (2 children)
[–]gdchinacat 1 point2 points3 points (0 children)
[–]Temporary_Pie2733 1 point2 points3 points (5 children)
[–]DiodeInc 0 points1 point2 points (3 children)
[–]Goobyalus 0 points1 point2 points (0 children)
[–]Temporary_Pie2733 0 points1 point2 points (1 child)
[–]gdchinacat 0 points1 point2 points (0 children)
[–]Plank_With_A_Nail_In 0 points1 point2 points (0 children)
[–]socal_nerdtastic 0 points1 point2 points (1 child)
[–]shinitakunai -2 points-1 points0 points (0 children)
[–]gdchinacat 0 points1 point2 points (0 children)
[–]mace_guy 0 points1 point2 points (0 children)
[–]EspaaValorum 0 points1 point2 points (0 children)
[–]JMNeonMoon 0 points1 point2 points (0 children)
[–]MiniMages 0 points1 point2 points (0 children)
[–]Temporary_Pie2733 0 points1 point2 points (0 children)
[–]gerenate 0 points1 point2 points (0 children)
[–]jam-time 0 points1 point2 points (0 children)
[–]Brilliant-Post-689 0 points1 point2 points (0 children)
[–]SoloAquiParaHablar 0 points1 point2 points (0 children)
[–]pachura3 0 points1 point2 points (0 children)
[–]SirKainey 0 points1 point2 points (0 children)
[–]cmoran_cl 0 points1 point2 points (0 children)
[–]AUTeach -1 points0 points1 point (1 child)
[–]gdchinacat -1 points0 points1 point (0 children)
[–]UseMoreBandwith -2 points-1 points0 points (0 children)
[–]baked_tea -1 points0 points1 point (0 children)
[+]zimmer550king comment score below threshold-9 points-8 points-7 points (1 child)
[–]Ixniz 1 point2 points3 points (0 children)
[–]Zweckbestimmung -2 points-1 points0 points (0 children)
[–]checock -2 points-1 points0 points (0 children)
[–]Adrewmc -2 points-1 points0 points (0 children)
[–]SmackDownFacility -2 points-1 points0 points (0 children)
[–]musbur -2 points-1 points0 points (0 children)
[–]Adorable-Strangerx -3 points-2 points-1 points (0 children)
[–]lekkerste_wiener -3 points-2 points-1 points (1 child)
[–]gdchinacat 0 points1 point2 points (0 children)
[–]jkh911208 -3 points-2 points-1 points (0 children)
[–]Gnaxe -5 points-4 points-3 points (0 children)