Is there any syntax for aliasing function args? If not, are there any PEP proposals? I'm not a programming languages theorist, so my opinion is probably uninformed, but I think implementing some sort of function arg aliasing could be useful.
I'm doing some changes to libcloud and my idea would help me avoid breaking others while making changes to the APIs.
For example, say I'm refactoring and would like to rename a function arg 'foo' to 'bar':
Original:
def fn(foo):
<code (using 'foo')>
I could:
def fn(foo, bar=None):
if foo and bar:
raise Exception('Please use foo and bar mutually exclusively.')
bar = foo or bar
<code (using 'bar')>
# But this is undesirable because it changes the method signature to allow
# a new parameter slot.
fn('hello world', 'goodbye world')
My unrefined syntactic sugar idea:
def fn(bar|foo|baz):
# Callers can use foo, bar, or baz, but only the leftmost arg name
# is used in the method code block. In this case, it would be bar.
# The python runtime would enforce mutual exclusion between foo,
# bar, and baz.
<code (using 'bar')>
# Valid uses:
fn(foo='hello world')
fn(bar='hello world')
fn(baz='hello world')
fn('hello world')
# Invalid uses (would raise some exception):
fn(foo='hello world', bar='goodbye world')
fn('hello world', baz='goodbye world')
No, there is no such syntactic sugar.
You could use **kwargs to capture extra keyword arguments and look for deprecated names in that (and raise an exception if there are not there). You could even automate that with a decorator perhaps.
from functools import wraps
def renamed_argument(old_name, new_name):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
if old_name in kwargs:
if new_name in kwargs:
raise ValueError(
"Can't use both the old name {} and new name {}. "
"The new name is preferred.".format(old_name, new_name))
kwargs[new_name] = kwargs.pop(old_name)
return func(*args, **kwargs)
return wrapper
return decorator
@renamed_argument('bar', 'foo')
def fn(foo=None):
<method code>
Demo:
>>> @renamed_argument('bar', 'foo')
... def fn(foo=None):
... return foo
...
>>> fn() # default None returned
>>> fn(foo='spam')
'spam'
>>> fn(bar='spam')
'spam'
>>> fn(foo='eggs', bar='spam')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 9, in wrapper
ValueError: Can't use both the old name bar and new name foo. The new name is preferred.
No perhapses, you can do that with decorators (as shown above), or for first hand with kwargs:
def fn (**kw):
arg = kw.get("foo") or kw.get("bar") or kw.get("baz")
if arg==None: raise TypeError, "foo nor bar nor baz given az argument"
print arg
Here the order of precedence is: "if foo exists, arg is foo. if it doesn't but bar exists, arg is bar, if neither foo nor bar exists, the arg is baz. If baz doesn't, i.e. all 3 are missing, arg is None.
Of course, you may check whether either one exists and force the mutual exclusion, but I don't see why would you need such a thing.
You are clever and you will never pass them in together. Even if you do, some will be ignored.
You can also do it with callable objects, even introduce your syntax and behavior into the interpreter at runtime if you want.
But in my opinion, introducing this as a valid Python syntax is not Pythonic and would never happen, even if there is a PEP.
As you can see, you can do what you want without dirtying Python syntax.
I don't mean that your example is not syntactically clear, just unnecessary.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With