About the passing the function arguments in Keyword form.

I am having an idea on loosing the argument validity check when passing the function arguments in keyword way. For example: ------------------------------- deff(x, y): print(x, y) defcall_f(): f(x=7, y=9, z=9) call_f() ------------------------------ In the current of python, the extra pass of 'z' would let the interpreter raise an exception and stop work. My idea is that the interpreter need not stop because all the needed args are completely provided. Of course for this toy example, 'f' can be define as f(x, y, **kwargs) to achieve the same goal. However, essentially it is reasonably to keep interpreter going as long as enough args are passed. And this modification can bring more freedom of programming. Think about the following situations: situation 1) there are many 'f's written by other people, and their args are very similar and your job is to run each of them to get some results. --------------------- ##########code by others: def f0(): ... def f1(x): ... def f2(x, y): ... def f3(x, y, z): ... #if passing extra args are valid, you can run all the functions in the following way, which is very compact and easy to read. def test_universal_call(): funcs = [f0, f1, f2, f3] args = {'x':1, 'y':5, 'z':8} for f in funcs: f(**args) ------------------ situation 2) there are several steps for make one product, each step is in an individual function and needs different args. ------------------ def make_oil(oil): ... def make_water( water): ... def make_powder(powder): ... ## if passing extra args are valid, you can run all the functions in the following way, which is very compact and easy to read. def dish(): procedures = [make_oil, make_water, make_powder] args = {'oil' : 1, 'water': 10, 'powder': 4} for f in procedures: f(**args) --------------- This idea is different from **kwargs. **kwargs are used when user wants to record all the keywords passed. This idea is that even if the user doesn’t want to record the arguments, that extra pass of keyword arguments wont’t cause an exception. Sorry for bothering you guys if this is a stupid idea. Happy to hear your suggestions. Li Mo

On Mon, Dec 24, 2018 at 06:21:31PM +0800, 李默 wrote:
Correct. As the Zen of Python says: Errors should never pass silently. Passing an unexpected argument "z" is an error, regardless of whether you pass it by keyword or as a positional argument. It should raise an exception. Don't think about toy examples like your f above with single character names. Think about code with proper names: def download(url, output_file=None, overwrite=True): if output_file is None: output_file = generate_filename(url) ... # Oops, a typo, which silently deletes data. download(url, override=False)
I don't agree that it is reasonable. To quote Chris Smith: "I find it amusing when novice programmers believe their main job is preventing programs from crashing. ... More experienced programmers realize that correct code is great, code that crashes could use improvement, but incorrect code that doesn’t crash is a horrible nightmare." Functions which silently ignore unexpected arguments instead of telling us that we have made a mistake ("got an unexpected keyword argument z") just *hides* the error, instead of reporting it so we can fix it.
And this modification can bring more freedom of programming.
Freedom to have more hard to diagnose bugs in our code. -- Steve

Similar features exists in JavaScript (where you can also do the same thing with positional arguments), and Clojure to make two. I personally think this is extremely bad. This type of behavior can make error in your code slip by undetected for a very long time. Let's take a concrete example! We have a function: def foo(*, a, b=3, c): .... People call it like so: foo(a=7, b=1, c=11) Now what happens if we rename argument b to q? The above code still runs! It just now passes 3 (the default value) to foo instead of the intended 1. I hope this example is enough to convince you of the danger of such a feature. It's certainly the reason why I think JavaScript and Clojure are terrible when it comes to passing arguments :) Best regards Anders

On 12/24/2018 5:21 AM, 李默 wrote:
I agree with other posters that we definitely do not want this as the default behavior in Python. However, it's also sometimes a useful pattern. I use it when I have a large plugin architecture that can take dozens or hundreds of possible parameters, but any given plugin is likely to only use a few parameters. I've written calllib (https://pypi.org/project/calllib/) to support this. It might achieve your goals. This code: ------------------- from calllib import apply def f0(): print('f0') def f1(x): print(f'f1 {x!r}') def f2(x, y): print(f'f2 {x!r} {y!r}') def f3(x, y, z): print(f'f3 {x!r} {y!r} {z!r}') def test_universal_call(): funcs = [f0, f1, f2, f3] args = {'x':1, 'y':5, 'z':8} for f in funcs: apply(f, args) test_universal_call() ------------------- produces: f0 f1 1 f2 1 5 f3 1 5 8 Eric

I agree with other posters that we definitely do not want this as the default behavior in Python. However, it's also sometimes a useful pattern. I use it when I have a large plugin architecture that can take dozens or hundreds of possible parameters, but any given plugin is likely to only use a few parameters. I've written calllib (https://pypi.org/project/calllib/) to support this. It might achieve your goals.
We do the same for various libs (tri.table for example) and our solution is just to say that you need to include **_ in your arguments for such functions. Simpler and more obvious than a simple DI system imo. / Anders

It's very important that f(z=5) Raises an exception if z is not an argument. For your case, I'd do a wrapper, instead lf calling f(z=5) you can call UniversalCall(f, x=1, y=2, z=5) if you want to specify it on the caller side. Or else, you can create a decorator : @universal_callable def f(x, y): ... f(x=1, y=2, z=5) # works ! On Mon, 24 Dec 2018, 11:21 李默 <phylimo@163.com wrote:

On Mon, Dec 24, 2018 at 06:21:31PM +0800, 李默 wrote:
Correct. As the Zen of Python says: Errors should never pass silently. Passing an unexpected argument "z" is an error, regardless of whether you pass it by keyword or as a positional argument. It should raise an exception. Don't think about toy examples like your f above with single character names. Think about code with proper names: def download(url, output_file=None, overwrite=True): if output_file is None: output_file = generate_filename(url) ... # Oops, a typo, which silently deletes data. download(url, override=False)
I don't agree that it is reasonable. To quote Chris Smith: "I find it amusing when novice programmers believe their main job is preventing programs from crashing. ... More experienced programmers realize that correct code is great, code that crashes could use improvement, but incorrect code that doesn’t crash is a horrible nightmare." Functions which silently ignore unexpected arguments instead of telling us that we have made a mistake ("got an unexpected keyword argument z") just *hides* the error, instead of reporting it so we can fix it.
And this modification can bring more freedom of programming.
Freedom to have more hard to diagnose bugs in our code. -- Steve

Similar features exists in JavaScript (where you can also do the same thing with positional arguments), and Clojure to make two. I personally think this is extremely bad. This type of behavior can make error in your code slip by undetected for a very long time. Let's take a concrete example! We have a function: def foo(*, a, b=3, c): .... People call it like so: foo(a=7, b=1, c=11) Now what happens if we rename argument b to q? The above code still runs! It just now passes 3 (the default value) to foo instead of the intended 1. I hope this example is enough to convince you of the danger of such a feature. It's certainly the reason why I think JavaScript and Clojure are terrible when it comes to passing arguments :) Best regards Anders

On 12/24/2018 5:21 AM, 李默 wrote:
I agree with other posters that we definitely do not want this as the default behavior in Python. However, it's also sometimes a useful pattern. I use it when I have a large plugin architecture that can take dozens or hundreds of possible parameters, but any given plugin is likely to only use a few parameters. I've written calllib (https://pypi.org/project/calllib/) to support this. It might achieve your goals. This code: ------------------- from calllib import apply def f0(): print('f0') def f1(x): print(f'f1 {x!r}') def f2(x, y): print(f'f2 {x!r} {y!r}') def f3(x, y, z): print(f'f3 {x!r} {y!r} {z!r}') def test_universal_call(): funcs = [f0, f1, f2, f3] args = {'x':1, 'y':5, 'z':8} for f in funcs: apply(f, args) test_universal_call() ------------------- produces: f0 f1 1 f2 1 5 f3 1 5 8 Eric

I agree with other posters that we definitely do not want this as the default behavior in Python. However, it's also sometimes a useful pattern. I use it when I have a large plugin architecture that can take dozens or hundreds of possible parameters, but any given plugin is likely to only use a few parameters. I've written calllib (https://pypi.org/project/calllib/) to support this. It might achieve your goals.
We do the same for various libs (tri.table for example) and our solution is just to say that you need to include **_ in your arguments for such functions. Simpler and more obvious than a simple DI system imo. / Anders

It's very important that f(z=5) Raises an exception if z is not an argument. For your case, I'd do a wrapper, instead lf calling f(z=5) you can call UniversalCall(f, x=1, y=2, z=5) if you want to specify it on the caller side. Or else, you can create a decorator : @universal_callable def f(x, y): ... f(x=1, y=2, z=5) # works ! On Mon, 24 Dec 2018, 11:21 李默 <phylimo@163.com wrote:
participants (5)
-
Anders Hovmöller
-
Eric V. Smith
-
Robert Vanden Eynde
-
Steven D'Aprano
-
李默