-2
from random import randint
import  time
state = 0 #close

open_time = 0
failure_count = 0
count = 0
status = {0 : "closed" , 2 : " open" , 1 : "half closed"}
def circuitbreaker(func):
    global count
    global open_time , state
    print("circuit status "+ status.get(state))
    if state ==0: #close state
        try:
            func()
        except Exception as ex:
            print(ex)
            count+=1
            if count>2:
                print("opening circuit breaker")
                state = 2
                open_time = int(time.time())
    elif (state == 2): #open state
        if( time.time() - open_time > 5) :
            state = 1
        else:
            print("circuit opened")
    else:
        try:
            func()
            count = 0
            open_time = 0
            print("closing circuit breaker")
            state = 0
        except Exception as ex:
            state = 2
            open_time = int(time.time())
            print("opening circuit breaker")

@circuitbreaker
def generic_func():
    a = randint(0,9)
    print("hello")
    print("random number = "+str(a))
    if a>4:
        return a
    else:
        raise Exception('Yalla!')

if __name__=="__main__":

    # while(True):
    #     generic_func()
        time.sleep(1)

I have this code . I have a couple of question:- 1)why does generic function is being called even if I comment it in main.

2)when I uncomment the commented part in main function . I get following error .How do I properly call this generic function . My motive is to implement a circuit breaker who closed when there is some kind of error or exception in a calling function . I can directly use :- circuitbreaker(calling function) but I wanted to use decorators

Traceback (most recent call last):
circuit status closed
hello
  File "/Users/abhishekkumar/PycharmProjects/RateLimiter/circuitbreaker.py", line 53, in <module>
random number = 1
Yalla!
    generic_func()
TypeError: 'NoneType' object is not callable

Process finished with exit code 1

The issue was the decorator should be returning a function object and should have relevant logic inside a function and then return that function otherwise it returns none object

  • Possible duplicate of [Python NoneType object is not callable (beginner)](https://stackoverflow.com/questions/9768865/python-nonetype-object-is-not-callable-beginner) – Patrick Artner Dec 13 '18 at 07:07
  • Set a break point into your funciton and step through - you are somewhere passing a NoneType into something and then do `something()` – Patrick Artner Dec 13 '18 at 07:09

3 Answers3

2

the answer to question #1: it's because of circuitbreaker decorator, as its logic is executed during module import and it calls the decorated function. Check out the following lines

...
try:
    func() # <-- here
except Exception as ex:
    print(ex)
    ...

The way of implementing decorators is to return a wrapper function, which contains the business logic:

from functools import wraps

def decorator(f):
    @wraps(f)
    def wrapper(*args, **kwargs):
        ... your logic here ...
    return wrapper

The answer to question #2 derives from the previous one.

Dan D.
  • 73,243
  • 15
  • 104
  • 123
1

Answers to almost all questions you never asked about decorators

The function that decorates something is supposed to return the function of what it does itself - not do all the stuff - you are not returnning a "functionpointer" but an implicit None from your deocrator (implicitly as you return nothing). This None is then called ...

How to fix:

def circuitbreaker(func):
    def the_works():
        global count
        global open_time , state
        print("circuit status "+ status.get(state))
        # .. all your other code ...
    return the_works

for _ in range(5): 
    generic_func()

Output of fix:

circuit status closed
hello
random number = 3
Yalla!
circuit status closed
hello
random number = 3
Yalla!
circuit status closed
hello
random number = 0
Yalla!
opening circuit breaker
circuit status  open
circuit opened
circuit status  open
circuit opened
Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • 1
    thanks a lot for that stackoverflow link . It was really helpful . I though we can do without wrapping the all code inside an insider function and returning that function – abhishek_rathaur Dec 13 '18 at 07:50
1
  1. Decorators run right after the decorated function is defined, and this is usually at import time. In the decorator circuitbreaker, you call generic_func already.

Here is an example from Fluent Python:

registry = []

def register(func):
    print('running register(%s)' % func)
    registry.append(func)
    return func

@register
def f1():
    print('running f1()')

@register
def f2():
    print('running f2()')

def f3():
    print('running f3()')

def main():
    print('registry ->', registry)
    f1()
    f2()
    f3()

if __name__ == '__main__':
    main()

The output is

running register(<function f1 at 0x1055ae378>)
running register(<function f2 at 0x1055ae400>)
registry -> [<function f1 at 0x1055ae378>, <function f2 at 0x1055ae400>]
running f1()
running f2()
  1. Is this what you want: How do I catch an exception in a decorator but allow the caller to catch it as well?