3

Here is a sample decorator:

def smart_divide(func):
   def inner(a,b):
      print("I am going to divide",a,"and",b)
      if b == 0:
         print("Whoops! cannot divide")
         return

      return func(a,b)
   return inner

@smart_divide
def divide(a,b):
    return a/b

If func is an object then how do the variables a and b get accessed from it?

Isn't it like trying to to do this?

def func(potato):
      print(y, x)

Is there a fundamental concept I am not getting? Is what is happening here part of some pattern in Python or is it a special case situation where a and b know were to look because it is a generator?


Update

New example from another stack exchange answer

def my_shiny_new_decorator(a_function_to_decorate):

    def the_wrapper_around_the_original_function():

        print("Before the function runs")

        a_function_to_decorate()

        print("After the function runs")

    return the_wrapper_around_the_original_function

def a_stand_alone_function():
    print("I am a stand alone function, don't you dare modify me")

Generators the manual way

a_stand_alone_function = my_shiny_new_decorator(a_stand_alone_function)
a_stand_alone_function()

Generators the proper way

@my_shiny_new_decorator
def another_stand_alone_function():
    print("Leave me alone")

According to the place where I got the new answer from the 'manual' way and the 'proper way' are the same .

I think this example may have caused me to get stuck as I was trying to extend it to when there were parameters involved.

I now realise that what I was imagining didn't make sense

I thought that the original code I posted was equivalent to this

divide = smart_divide(divide(a,b))

which if executed would look like this

def smart_divide(divide(a,b)):
   def inner(a,b):
      print("I am going to divide",a,"and",b)
      if b == 0:
         print("Whoops! cannot divide")
         return

      return func(a,b)
   return inner

But this would cause divide(a,b) to be executed right in the top line

in the new example 'a_stand_alone_function' did not have () on the end. Which means it was treated as an object.

So my idea of it looking like this def smart_divide(divide(a,b)): doesn't make sense because the function won't be treated as an object anymore

This leaves me confused as to how smart_devide get the information passed as a parameter.

Jonathan De Wet
  • 336
  • 1
  • 4
  • 12
  • I don't really understand the question. `func` is the function that you are decorating, and a and b are its parameters. – Daniel Roseman Nov 19 '17 at 10:13
  • No, your decorator returns `inner` as _implementaion_ for `divide`. Thus you call the function `inner` when you execute `divide(1, 2)` for instance, and you have to respect the signature of `inner` (and `divide` as in your code). – clemens Nov 19 '17 at 10:15
  • @ Daniel Roseman. But func is passed into `smart_devide` not a & b. If a & b are not passed in how are they assessed. – Jonathan De Wet Nov 19 '17 at 10:15
  • `a` and `b` are passed to `inner` and not to `smart_divide`. `smart_divide` is a function accepting a function as parameter and returning a function as result. – clemens Nov 19 '17 at 10:17
  • @macmoonshine I appreciate your reply, unfortunately I don't seem understand the background concepts you are using. More specifically what is 'implementaion' and 'signature'? – Jonathan De Wet Nov 19 '17 at 10:19
  • @macmoonshine But I didn't think inner could access those variables as they are outside of its scope. Isn't it like doing this [link to code example](https://www.dropbox.com/s/f1f3xclfrmguv1q/Screen%20Shot%202017-11-19%20at%2011.22.14%20PM.png?dl=0) – Jonathan De Wet Nov 19 '17 at 10:24

2 Answers2

2

No, your decorator returns inner as new implementaion for divide. Thus, you first call the function inner when your program executes divide(1, 2) for instance. Calls to divide have always to respect the signature of inner (and divide as in your code).

A function like

def divide(a, b):  # signature
    return a / b   # implementation or body

consists of two parts. The signature describes the parameters, and the implementation what the function does.

Your decorator will only modify the implementation of your function as follows:

def divide(a, b):                            # signature remains unmodified
    print("I am going to divide",a,"and",b)  # implementation of inner
    if b == 0:
        print("Whoops! cannot divide")
        return
    return a / b                             # call to the original implementation of divide   

The name and the signature of divide remains the same. Thus, the signature of inner matters, and not the signature of your decorator.

clemens
  • 16,716
  • 11
  • 50
  • 65
  • Thanks for the answer. However, I still feel like I don't understand. Your explanation that the decorator returns inner as the new implimentation for divide, doesn't seem to link into anything else I know about how python works. It also doesn't match how generators were explained to me which was that they are equivilent to `divide = smart_divide(divide(a,b))` – Jonathan De Wet Nov 19 '17 at 11:20
  • The last row of your decorator is `return inner` which returns the implementation of `inner`. The decorator replaces the implementation for `divide` with this returned value. You can prove that by letting your decorator returning something other than a function, say the value 1. When you call `devide` than, you will get the error _TypeError: 'int' object is not callable_, because the decorator has applied an integer to `devide` instead of a function. – clemens Nov 19 '17 at 11:31
  • Interesting. Thanks for taking the time to clarify. I think I mostly get it now. I still feel like the decorator function is manipulating data in a way that nothing else I know about in python does. e.i I feel like I am missing out of the general principle that this is exploiting. – Jonathan De Wet Nov 19 '17 at 11:41
  • Keep in mind that functions in Python are generally variables as well. In particular, you can assign them new values, e. g.: `divide = multiply` ... and the decoration gets "lost", if `multiply` wasn't decorated. – clemens Nov 19 '17 at 11:53
2

smart_divide doesn't get a and b passed into it. It returns a function object (the inner function), and that function gets a and b passed into it.

You can see what's actually happening if you try this:

def smart_divide(func):
   print("I am running in smart_divide; func=", func)
   def inner(a,b):
      print("I am going to divide",a,"and",b)
      if b == 0:
         print("Whoops! cannot divide")
         return

      return func(a,b)
   print("I am returning from smart_divide")
   return inner

print("I am running at top level before declaring divide")

@smart_divide
def divide(a,b):
    return a/b

print("The name 'divide' now refers to", divide)

print("I am now going to call the divide function")
divide(1, 2)

This outputs:

I am running at top level before declaring divide
I am running in smart_divide; func= <function divide at 0x108ff2bf8>
I am returning from smart_divide
the name 'divide' now refers to <function smart_divide.<locals>.inner at 0x10565db70>
I am now going to call the divide function
I am going to divide 1 and 2
Daniel Pryden
  • 59,486
  • 16
  • 97
  • 135