1

I'm working on an exercise, I tried to solve it, but no result, I had to look at the solution, in order to have an idea and repeat it, the problem, I am stuck, a little lost.

# Create an @authenticated decorator that only allows the function to run is user1 has 'valid' set to True:

 user1 = { 'name': 'Sorna',
           'valid': True }         #changing this will either run or not run the message_friends function.

Solution :

def authenticated(fn):
  def wrapper(*args, **kwargs):
    if args[0]['valid']:
      return fn(*args, **kwargs)
  return wrapper

@authenticated
def message_friends(user):
    print('message has been sent')

message_friends(user1)

I really don't get this part :

if args[0]['valid']:

My question is if user1 = dict, why can't i just use **kwards so i can just check if the value is True by calling only [valid]: where it comes from the args[0]?

Help, i'm really stuck with this..

Phix
  • 9,364
  • 4
  • 35
  • 62
Zeentanas
  • 11
  • 4
  • 3
    Does this answer your question? [What does \*\* (double star/asterisk) and \* (star/asterisk) do for parameters?](https://stackoverflow.com/questions/36901/what-does-double-star-asterisk-and-star-asterisk-do-for-parameters) – Eric Jin May 28 '20 at 21:12
  • As it is, you're calling `message_friends(user1)`, which passes `user1` as a plain positional argument, so it will be in `args[0]`. If you want to call a function and pass the contents of a dictionary as kwargs, use the `**` operator: `message_friends(**user1)` – John Gordon May 28 '20 at 21:19

3 Answers3

0

You can use kwargs if you pass in user1 as a keyword-argument. For example, message_friends(user_argument=user1) would then have args == [] and kwargs == {'user_argument': user1}

Further illustration of this point

def my_func(*args, **kwargs):
   print(f"Args: {args}")
   print(f"Kwargs: {kwargs}")

>> my_func(1, 2, 3, 4)
Args: [1, 2, 3, 4]
Kwargs: {}

>> my_func(1, 2, three=3, four=4)
Args: [1, 2]
Kwargs: {"three": 3, "four": 4}
wakey
  • 2,283
  • 4
  • 31
  • 58
0

The decorator can be written a bit more clearly to not use args[0], does this help you understand?

def authenticated(fn):
    def wrapper(user, *args, **kwargs):
        if user['valid']:
            return fn(user, *args, **kwargs)

    return wrapper

@authenticated
def message_friends(user):
    print('message has been sent to', user['name'])

message_friends({'name': 'Sorna', 'valid': True})
message_friends({'name': 'Bob', 'valid': False})

Now *args, **kwargs is only there to pass along any other arguments the decorated function might have.

This is also more robust because it works whether user is passed positionally or as a keyword argument.

Alex Hall
  • 34,833
  • 5
  • 57
  • 89
  • So by writing this args[0] i'm saying that i have 0 args so the programm will try to look in **kwargs ? – Zeentanas Jun 01 '20 at 08:20
  • @Zeentanas no, args is a tuple of all positional arguments (except those already bound to other parameters, like `user` in this case) passed to the function, and args[0] is the first one. – Alex Hall Jun 01 '20 at 08:33
0

The @authenticated decorator does the following:

  1. Ensure that the function is called with at least one positional argument (else, an exception is raised).
  2. Ensure that this first argument is a dictionary, or at least supports item access by string subscripts (again, otherwise an exception is raised).
  3. Ensure that the key 'valid' is found in the dictionary (else, exception).
  4. If the key exists in the dictionary and has a True boolean value, then the original function is called and its return value returned by the decorated function. Else, no function is called, and no value returned.

It's not a very neat way of decorating functions, but if you can ensure that conditions 1 through 3 always hold, condition 4 can be useful in some scenarios.

Amitai Irron
  • 1,973
  • 1
  • 12
  • 14