0

I'm looking to strip the white-space from each of the arguments in a function that takes a bunch of required strings. And I don't want to use **kwargs which defeats the purpose of the required arguments.

def func(a, b, c):
    for argument, argument_value in sorted(list(locals().items())):
        print(argument, ':', argument_value)
        argument_value = ' '.join(argument_value.split())
        print(argument, ':', argument_value)

    print('a is now:', a)

func(a='   a test 1  ', b='   b test 2 ', c='c test    3')

Output

a :    a test 1  
a : a test 1
b :    b test 2 
b : b test 2
c : c test    3
c : c test 3
a is now:    a test 1  

Desired output for the original 'a' argument:

a is now : a test 1  

Being a newb, I cobbled this together and then read the python documentation which clearly states.

locals()
Update and return a dictionary representing the current local symbol table. Free variables are returned by locals() when it is called in function blocks, but not in class blocks.

Note
The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.

What is the right way to do what I'm attempting here?

GollyJer
  • 23,857
  • 16
  • 106
  • 174
  • I guess you don't want to use `**kwargs`? – Cilyan Mar 14 '15 at 02:41
  • Yes. That is correct. – GollyJer Mar 14 '15 at 02:44
  • Maybe here ?: https://stackoverflow.com/questions/582056/getting-list-of-parameter-names-inside-python-function – Cilyan Mar 14 '15 at 02:45
  • Nope. That just gets the argument names and values. The key here is I want to act on each value. – GollyJer Mar 14 '15 at 02:47
  • I do not get it, there is nothing more needed to act on an argument than getting its name and current value... If you mean that you want to *set* the argument from inside the function you are going in the wrong direction, that won't happen... However, if your arguments are references (i.e. objects, lists, dicts, ...) actions you take on them will affect the original object (i.e. if you get a list as an argument value and add an item to the list, it will be added to the original list, because you manipulate a reference). – Cilyan Mar 14 '15 at 02:57

2 Answers2

2

You can use a decorator to do that kind of task.

The idea is to mask the real function behind a decorator that will take generic arguments, do modifications "on them" (actually create new variables containing the modifications) and pass the modified arguments to the real function.

def strip_blanks(f):
    def decorated_func(*args, **kwargs):
        # Strip blanks from non-keyword arguments
        new_args = [ " ".join(arg.split()) for arg in args]
        # Strip blanks from keyword arguments
        new_kwargs = { key:" ".join(arg.split()) for key,arg in kwargs.items()}
        # Pass the modified arguments to the decorated function
        # And forward its result in case needed
        return f(*new_args, **new_kwargs)
    return decorated_func

@strip_blanks
def func(a, b, c):
    for i in a, b, c:
        print(i)

Then you'd get

>>> func(a = "  foo  bar", b = "baz  boz", c = "biz buz  ")
foo bar
baz boz
biz buz
>>> func("  foo  bar", "baz  boz", "biz buz  ")
foo bar
baz boz
biz buz
>>> func(a = "  foo  bar", b = "baz  boz", c = "biz buz  ", d = " ha ha")
Traceback (most recent call last):
  File "<pyshell#40>", line 1, in <module>
    func(a = "  foo  bar", b = "baz  boz", c = "biz buz  ", d = " ha ha")
  File "<pyshell#35>", line 5, in decorated_func
    f(*new_args, **new_kwargs)
TypeError: func() got an unexpected keyword argument 'd'
>>> 
Cilyan
  • 7,883
  • 1
  • 29
  • 37
  • Awesome! This is exactly the kind of answer I was looking for. I learned a lot reading through the posted tutorial as well. Thanks @Cilyan – GollyJer Mar 14 '15 at 19:03
  • @GollyJer you're very welcome. It took me some time to understand what you needed, and then... (Aziz) light! :) – Cilyan Mar 15 '15 at 00:35
0

I would start by changing your definition to def func(**kwargs). This takes whatever keyword arguments you provide and adds them to a dictionary. For example:

def func(**kwargs):
    for key in kwargs:
        print key, kwargs[key]

>>> func(a='hello', b='goodbye')
a hello
b goodbye
>>> func()
>>>

As you can see, it works with no arguments as well (nothing to print). From there, have a look at the string method strip.

EDIT:

You're giving some pretty arbitrary restrictions. So, what you want is:

  • ...a specific number of arguments with specific names.

  • ...to loop over the arguments.

  • ...perform some work on each one.

The fastest way to do what you want is with locals() I think. I'm guessing what you're balking at is the bit about the contents of the dictionary not being modified. This isn't a concern here, as you're looping over a list of tuples representing the keys and values from the locals dictionary. When you do for argument, argument_value in ____ you are unpacking the tuples and assigning one value to each of those names. When you then do argument_value = 'blahblah' you are assigning a new string to argument_value. Strings are immutable, so you can't change the value "in placed". You aren't changing the value in the dictionary, as you haven't assigned anything to the dictionary's key.

paidhima
  • 2,312
  • 16
  • 13