1

First of all to find "lcm" of two numbers I made a function lcm(a, b). Then I thought of finding "hcf" too so I made a decorator decor and defined a function hcf(a, b) in it. And then I returned this function by just typing the name of the function and I didn't put brackets with it but it is still working. I cant understand why this function is working even though I didn't used brackets.

def decor(lcm_arg):        # just to practice decorators
    def hcf(a, b):
        if a > b:
            a, b = b, a
        while True:
            if b % a == 0:
                print("hcf is", a)
                break
            else:
                a, b = b % a, a
        return lcm_arg(a, b)
    return hcf              # how hcf function is working without using brackets

@decor
def lcm(a, b):
    if a > b:
        a, b = b, a
    for x in range(b, a*b+1, b):
        if x % a == 0:
            print("lcm is", x)
            break


lcm(2, 4)

Output:

hcf is 2
lcm is 4
Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • Has your question been answered, or do you need further explanation? – timgeb Oct 07 '18 at 12:20
  • @timgeb - Well, I am a beginner and i dont know much about python yet. I am still trying to help me out with my doubt....but thanks a lot man for your support.....but i still cant understand that why the inner function inside the decorator runs on returning by just typing its name....and why we are not using parrntheses with it – Pardeep Singh Brar Oct 07 '18 at 15:13
  • But the inner function **does not run on returning it**! My answer lists all the function calls in order and all of them happen with parentheses. `return hcf` does **not** call `hcf`. – timgeb Oct 07 '18 at 15:18
  • if `return hcf` does not call the function then how its functionality is working – Pardeep Singh Brar Oct 07 '18 at 15:34
  • and this is also not clear to me that how the inner function takes the arguments...like they seem to me the formal arguments but they work as the actual arguments taken from the function passed as an argument in the decorator – Pardeep Singh Brar Oct 07 '18 at 15:39
  • In your code, `hcf` is called when you call `lcm`, because the decorating does `lcm = decor(lcm)`. – timgeb Oct 07 '18 at 15:47
  • One last question....what actually `return hcf` will do ? – Pardeep Singh Brar Oct 07 '18 at 17:30
  • Do you know what `return 1` would do? It's exactly the same, just that the function `hcf` is returned instead of the value `1`. – timgeb Oct 07 '18 at 19:01
  • Okk..thanxx man...i appreciate ur help – Pardeep Singh Brar Oct 08 '18 at 04:33

2 Answers2

6

I don't think you understand decorators. Let's make a minimal example.

def my_decorator(some_function):        
    def new_function(*args, **kwargs):
        'announces the result of some_function, returns None'
        result = some_function(*args, **kwargs)
        print('{} produced {}'.format(some_function.__name__, result))
    return new_function # NO FUNCTION CALL HERE!

@my_decorator
def my_function(a, b):
    return a + b

my_function(1, 2) # will print "my_function produced 3"

We have a simple function my_function which returns the sum of its two arguments and a decorator which will just print out the result of whatever function it decorates.

Note that

@my_decorator
def my_function(a, b):
    return a + b

is equivalent to

def my_function(a, b):
    return a + b

my_function = my_decorator(my_function)

Since my_decorator accepts a function as an argument (here we are giving it my_function) and returns a new function new_function (without calling it!), we effectively override my_function because we reassign the name to whatever my_decorator returns.

In action:

>>> my_function(1, 2)
my_function produced 3

Note that at every point in the example when a function is called, it happens with the parentheses-syntax. Here are all the function calls that happen in the first block of code I posted, in order:

  1. my_decorator(my_function) is called and the return value is reassigned to the name my_function. This either happens through the @ syntax or more explicitly in the equivalent code snippet.
  2. my_function(1, 2) is called. At this point, my_function is the new_function that got returned by the decorator. Brain-parse it as new_function(1, 2).
  3. Inside the body of new_function, the argument we gave to my_decorator is called (result = some_function(*args, **kwargs)) which happens to be the value of my_function before the reassignment that happened in step 1.
  4. print is called.

If you want to understand how new_function is holding on to some_function despite my_decorator already having returned from its call, I suggest looking into the topics free variables and closures.

timgeb
  • 76,762
  • 20
  • 123
  • 145
1

return hcf does not call the function because there are no parentheses, as you noticed. The decor function is used as a decorator which reassigns the name lcm to refer to the returned function. What I mean by this is that

@decor
def lcm(a, b):
    // ...

is equivalent to

def lcm(a, b):
    // ...

lcm = decor(lcm)

After this executes, lcm refers to the function hcf. So calling lcm(2, 4) now executes the code in hcf. I think the key here is to understand that at the line lcm(2, 4), lcm and hcf are two names which refer to the same function.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268