Last active
November 22, 2018 10:14
-
-
Save predakanga/bb6d910ad9ba7ac7ef39d09d22ae3949 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Because functions are variables, we can copy and delete the following function | |
def foo(): | |
return True | |
bar = foo | |
del foo | |
# foo() would now cause an error | |
# bar() would return True |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This function is a decorator - when you use it, "f" will be the original function | |
def pointless(f): | |
print("I'm a pointless decorator! I don't change the function at all") | |
# Whatever you return will replace the original function | |
return f | |
# This... | |
@pointless | |
def foo(): | |
return True | |
# Is equivalent to | |
foo = pointless(foo) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Because decorators can replace the function, we can wrap or even replace the original function with our own logic | |
def negate(f): | |
def wrapper(): | |
original_result = f() | |
return not original_result | |
return wrapper | |
@negate | |
def trueish(): | |
return True | |
print(trueish()) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# At this point, it's important to ask why we needed that weird nested function | |
# Why couldn't we just write... | |
def negate(f): | |
return not f() | |
# The reason is that the decorator has to return a *function*, which other code will eventually use | |
# It may help to imagine the whole .py file as one long script that gets run line-by-line when you call "import your_module" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from collections import defaultdict | |
# In this example, we'll assume a static rate limit of one call per 10s | |
# Adding arguments to decorators is a bit more complicated | |
# This means that you can make your own generic wrapper functions like the following: | |
def rate_limit(f): | |
# The wrapper function should have the same parameters as the original function | |
def wrapper(bot, trigger): | |
# We'll store our rate limit info in bot.memory | |
if not 'rate_limit_times' in bot.memory: | |
bot.memory['rate_limit_times'] = defaultdict(lambda: defaultdict(lambda: 0)) | |
if not 'rate_limit_warned' in bot.memory: | |
bot.memory['rate_limit_warned'] = defaultdict(lambda: defaultdict(lambda: False)) | |
# Again, cool Python things - the function can actually be a key for our dict | |
last_trigger = bot.memory['rate_limit_times'][f][trigger.nick] | |
if trigger.time < (last_trigger + 10): | |
# Rate limited - check if we should warn the user | |
if !bot.memory['rate_limit_warned'][f][trigger.nick]: | |
bot.reply("You can only use that command once every 10 seconds") | |
bot.memory['rate_limit_warned'][f][trigger.nick] = True | |
# Return, because we were rate limited | |
return | |
# Otherwise, call the original function | |
retval = f(bot, trigger) | |
# Account for NOLIMIT | |
if retval != NOLIMIT: | |
bot.memory['rate_limit_times'][f][trigger.nick] = trigger.time | |
bot.memory['rate_limit_warned'][f][trigger.nick] = False | |
# Finally, pass on the original function's return value | |
return retval | |
return wrapper | |
# Then you can use it like so | |
import random | |
@commands("test") | |
@rate_limit | |
def test_func(bot, trigger): | |
# Random reply to avoid the "..." issue | |
bot.reply(random.randint(1, 10)) | |
# Finally, one important caveat | |
# With this simple example, @rate_limit has to be placed after any Sopel decorators, because of the way they work |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment