Python - Iterating over Arguments passed to a Function - python

Suppose I have the following example:
class foo:
...
def bar(self, w, x, y, z, ...):
self.w = w
self.x = x
self.y = y
self.z = z
...
I wish to reduce the n-number of attribute assignment lines in bar() to one assignment line set using a setattr() cycle through the arguments. Is there a good way to cycle through said arguments for this purpose?
I wish to retain the defined parameter names so as to limit the number of parameters passed to the function as well as the order in which they are passed. I also understand that functions can be handled like objects; so is it possible to obtain a list of the defined parameters as an attribute of the function and iterate through that?

Use locals() and you can get all the arguments (and any other local variables):
class foo:
def bar(self, w, x, y, z):
argdict = {arg: locals()[arg] for arg in ('w', 'x', 'y', 'z')}
for key, value in argdict.iteritems():
setattr(self, key, value)
...
Might be possible to do it more efficiently, and you could inline argdict if you prefer less lines to readability or find it more readable that way.

So you don't have to actually name the arguments explicitly use:
class foo:
def __init__(self, w, x, y, z):
args = locals()# gets a dictionary of all local parameters
for argName in args:
if argName!='self':
setattr(self, argName, args[argName])

The __setattr__ attribute only assigns one attribute at a time, if you want to assign multiple attribute, you can use **kwargs in your function header and for limiting the number of arguments you can simply check the length of kwargs within your function. and call the __setattr__ for each each of the arguments one by one. One good reason for this recipe is that basically assigning attribute to an object without considering anything is not a correct and desirable job, due to a lot of reasons. Thus you have to assign each attribute one at a time by considering all the required conditions.
You can also do this manually by updating the instance dictionary but you should handle the exceptions too.
In [80]: class foo:
def bar(self, **kwargs):
if len(kwargs) != 4:
raise Exception("Please enter 4 keyword argument")
for k, v in kwargs.items():
foo.__setattr__(self, k, v)
....:
In [81]: f = foo()
In [82]: f.bar(w=1, x=2, y=3, z=4)
In [83]: f.w
Out[83]: 1
In [84]: f.bar(w=1, x=2, y=3, z=4, r=5)
---------------------------------------------------------------------------
Exception Traceback (most recent call last)
<ipython-input-84-758f669d08e0> in <module>()
----> 1 f.bar(w=1, x=2, y=3, z=4, r=5)
<ipython-input-80-9e46a6a78787> in bar(self, **kwargs)
2 def bar(self, **kwargs):
3 if len(kwargs) != 4:
----> 4 raise Exception("Please enter 4 keyword argument")
5 for k, v in kwargs.items():
6 foo.__setattr__(self, k, v)
Exception: Please enter 4 keyword argument
By using __setatter__ it will take care of the exception automatically:
In [70]: f.bar(1, 2)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-70-07d1f3c9e27f> in <module>()
----> 1 f.bar(1, 2)
<ipython-input-65-1049e26120c1> in bar(self, *args)
2 def bar(self, *args):
3 for item in args:
----> 4 foo.__setattr__(self, item, item)
5
TypeError: attribute name must be string, not 'int'

Related

Count how many arguments passed as positional

If I have a function
def foo(x, y):
pass
how can I tell, from inside the function, whether y was passed positionally or with its keyword?
I'd like to have something like
def foo(x, y):
if passed_positionally(y):
print('y was passed positionally!')
else:
print('y was passed with its keyword')
so that I get
>>> foo(3, 4)
y was passed positionally
>>> foo(3, y=4)
y was passed with its keyword
I realise I didn't originally specify this, but is it possible to do this whilst preserving type annotations? The top answer so far suggests using a decorator - however, that does not preserve the return type
You can create a decorator, like this:
def checkargs(func):
def inner(*args, **kwargs):
if 'y' in kwargs:
print('y passed with its keyword!')
else:
print('y passed positionally.')
result = func(*args, **kwargs)
return result
return inner
>>> #checkargs
...: def foo(x, y):
...: return x + y
>>> foo(2, 3)
y passed positionally.
5
>>> foo(2, y=3)
y passed with its keyword!
5
Of course you can improve this by allowing the decorator to accept arguments. Thus you can pass the parameter you want to check for. Which would be something like this:
def checkargs(param_to_check):
def inner(func):
def wrapper(*args, **kwargs):
if param_to_check in kwargs:
print('y passed with its keyword!')
else:
print('y passed positionally.')
result = func(*args, **kwargs)
return result
return wrapper
return inner
>>> #checkargs(param_to_check='y')
...: def foo(x, y):
...: return x + y
>>> foo(2, y=3)
y passed with its keyword!
5
I think adding functools.wraps would preserve the annotations, following version also allows to perform the check over all arguments (using inspect):
from functools import wraps
from inspect import signature
def checkargs(func):
#wraps(func)
def inner(*args, **kwargs):
for param in signature(func).parameters:
if param in kwargs:
print(param, 'passed with its keyword!')
else:
print(param, 'passed positionally.')
result = func(*args, **kwargs)
return result
return inner
>>> #checkargs
...: def foo(x, y, z) -> int:
...: return x + y
>>> foo(2, 3, z=4)
x passed positionally.
y passed positionally.
z passed with its keyword!
9
>>> inspect.getfullargspec(foo)
FullArgSpec(args=[], varargs='args', varkw='kwargs', defaults=None,
kwonlyargs=[], kwonlydefaults=None, annotations={'return': <class 'int'>})
_____________HERE____________
Update Python 3.10
In Python 3.10+ new ParamSpec type annotation was introduced (PEP 612), for better specifying parameter types in higher-order functions. As of now, the correct way to annotate this decorator would be like this:
from functools import wraps
from inspect import signature
from typing import Callable, ParamSpec, TypeVar, TYPE_CHECKING
T = TypeVar("T")
P = ParamSpec("P")
def check_args(func: Callable[P, T]) -> Callable[P, T]:
"""
Decorator to monitor whether an argument is passed
positionally or with its keyword, during function call.
"""
#wraps(func)
def inner(*args: P.args, **kwargs: P.kwargs) -> T:
for param in signature(func).parameters:
if param in kwargs:
print(param, 'passed with its keyword!')
else:
print(param, 'passed positionally.')
return func(*args, **kwargs)
return inner
Which correctly preserves type annotation:
if TYPE_CHECKING:
reveal_type(foo(2, 3))
# ─❯ mypy check_kwd.py
# check_kwd.py:34: note: Revealed type is "builtins.int"
# Success: no issues found in 1 source file
At the end, if you are going to do something like this:
def foo(x, y):
if passed_positionally(y):
raise Exception("You need to pass 'y' as a keyword argument")
else:
process(x, y)
You can do this:
def foo(x, *, y):
pass
>>> foo(1, 2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() takes 1 positional argument but 2 were given
>>> foo(1, y=2) # works
Or only allow them to be passed positionally:
def foo(x, y, /):
pass
>>> foo(x=1, y=2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got some positional-only arguments passed as keyword arguments: 'x, y'
>>> foo(1, 2) # works
See PEP 570 and PEP 3102 for more.
Adapted from #Cyttorak 's answer, here's a way to do it which maintains the types:
from typing import TypeVar, Callable, Any, TYPE_CHECKING
T = TypeVar("T", bound=Callable[..., Any])
from functools import wraps
import inspect
def checkargs() -> Callable[[T], T]:
def decorate(func):
#wraps(func)
def inner(*args, **kwargs):
for param in inspect.signature(func).parameters:
if param in kwargs:
print(param, 'passed with its keyword!')
else:
print(param, 'passed positionally.')
result = func(*args, **kwargs)
return result
return inner
return decorate
#checkargs()
def foo(x, y) -> int:
return x+y
if TYPE_CHECKING:
reveal_type(foo(2, 3))
foo(2, 3)
foo(2, y=3)
Output is:
$ mypy t.py
t.py:27: note: Revealed type is 'builtins.int'
$ python t.py
x passed positionally.
y passed positionally.
x passed positionally.
y passed with its keyword!
It is not ordinarily possible. In a sense: the language is not designed to allow you to distinguish both ways.
You can design your function to take different parameters - positional, and named, and check which one was passed, in a thing like:
def foo(x, y=None, /, **kwargs):
if y is None:
y = kwargs.pop(y)
received_as_positional = False
else:
received_as_positional = True
The problem is that, although by using positional only parameters as abov, you could get y both ways,
it would be shown not once for a user (or IDE) inspecting the
function signature.
I hav a feeling you just want to know this for the sake of knowing - if
you really intend this for design of an API, I'd suggest you'd rethink
your API - there should be no difference in the behavior, unless both
are un-ambiguously different parameters from the user point of view.
That said, the way to go would be to inspect the caller frame, and check
the bytecode around the place the function is called:
In [24]: import sys, dis
In [25]: def foo(x, y=None):
...: f = sys._getframe().f_back
...: print(dis.dis(f.f_code))
...:
In [26]: foo(1, 2)
1 0 LOAD_NAME 0 (foo)
2 LOAD_CONST 0 (1)
4 LOAD_CONST 1 (2)
6 CALL_FUNCTION 2
8 PRINT_EXPR
10 LOAD_CONST 2 (None)
12 RETURN_VALUE
None
In [27]: foo(1, y=2)
1 0 LOAD_NAME 0 (foo)
2 LOAD_CONST 0 (1)
4 LOAD_CONST 1 (2)
6 LOAD_CONST 2 (('y',))
8 CALL_FUNCTION_KW 2
10 PRINT_EXPR
12 LOAD_CONST 3 (None)
14 RETURN_VALUE
So, as you can see, when y is called as named parameter, the opcode for the call is CALL_FUNCTION_KW , and the name of the parameter is loaded into the stack imediately before it.
You can trick the user and add another argument to the function like this:
def foo(x,y1=None,y=None):
if y1 is not None:
print('y was passed positionally!')
else:
print('y was passed with its keyword')
I don't recommend doing it but it does work
In foo, you can pass the call stack from traceback to positionally, which will then parse the lines, find the line where foo itself is called, and then parse the line with ast to locate positional parameter specifications (if any):
import traceback, ast, re
def get_fun(name, ast_obj):
if isinstance(ast_obj, ast.Call) and ast_obj.func.id == name:
yield from [i.arg for i in getattr(ast_obj, 'keywords', [])]
for a, b in getattr(ast_obj, '__dict__', {}).items():
yield from (get_fun(name, b) if not isinstance(b, list) else \
[i for k in b for i in get_fun(name, k)])
def passed_positionally(stack):
*_, [_, co], [trace, _] = [re.split('\n\s+', i.strip()) for i in stack]
f_name = re.findall('(?:line \d+, in )(\w+)', trace)[0]
return list(get_fun(f_name, ast.parse(co)))
def foo(x, y):
if 'y' in passed_positionally(traceback.format_stack()):
print('y was passed with its keyword')
else:
print('y was passed positionally')
foo(1, y=2)
Output:
y was passed with its keyword
Notes:
This solution does not require any wrapping of foo. Only the traceback needs to be captured.
To get the full foo call as a string in the traceback, this solution must be run in a file, not the shell.

How to know how many arguments to pass to referenced function

lets say a I have two functions:
def foo1(bar, a, b, c):
result = bar(a, b)
return result
def foo2(bar, a, b, c):
result = bar(a, b, c)
return result
the arguments are the same in both situation, but it depends on the function "bar" that may need only 2, and another one may need all 3 (in the same order)
is it possible to make this into a single function without knowing how many arguments the referenced function needs?
You can use the function object's __code__.co_argcount attribute to obtain the number of arguments it expects, with which you can slice the argument list:
def bar1(a, b):
return b, a
def bar2(a, b, c):
return c, b, a
def foo(bar, *args):
return bar(*args[:bar.__code__.co_argcount])
print(foo(bar1, 1, 2, 3))
print(foo(bar2, 1, 2, 3))
This outputs:
(2, 1)
(3, 2, 1)
Try this.
from inspect import signature
def foo(bar, *args):
arg_count = len(signature(bar).parameters)
return bar(*args[:arg_count])
This passes however many arguments the function expects, and ignores the rest. If you want to use all the arguments later, they're in the args list.
Yes. If I understand you correctly, you want to pass unspecified amount of arguments to a function?
If so, you can accept a tuple; such that the function can accept (a, b, c) or several more arguments. Tuples are similar to lists, but are not mutable. Of course, it's up to you to make sure the right amount of arguments are inputted.
Below, arguments is a tuple.
You can do len(arguments) to find out how many arguments were inputted.
arguments=(bar, a, b)
foo(arguments)
def foo(my_tuple):
result = arguments[0](arguments[1], arguments[2])
return result
I think you may be looking for the * expansion operator
def foo(bar, *args):
result = bar(*args)
return result
For example:
In [1]: def foo(bar,*args):
...: return bar(*args)
...:
In [2]: def f1(a):
...: return a
...:
In [3]: def f2(a,b):
...: return a+b
...:
In [4]: foo(f1,5)
Out[4]: 5
In [5]: foo(f2,5,6)
Out[5]: 11
In [6]: foo(f1,5,6)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-6-5ca26cac7f4d> in <module>
----> 1 foo(f1,5,6)
<ipython-input-1-fe8d4699f744> in foo(bar, *args)
1 def foo(bar,*args):
----> 2 return bar(*args)
3
TypeError: f1() takes 1 positional argument but 2 were given

How do I dynamically check and set an instances variable values?

I want to be able to dynamically check and set an instances attribute values. The use-case is for a point-like class, where I want to have a custom 'set_value' method that checks for which value the client is trying to set, and then determines what to do from there.
My goal is to have a method which allows the values (x, y) of the instance to be set once, and then disallows future assignments to those variables... effectively making them 'immutable'.
I know that this does not technically make them immutable, I'm relying on the gentleman's agreement to not do funny stuff and mess around with it.
I've trying messing around with the setattr method, to no avail, as it then won't allow the init to run in the first place.
My class is shown below:
class Node:
"""Represents the nodes as points with a position x, y."""
def __init__(self, x=0, y=0, x_state=0, y_state=0):
"""Create a new node at x, y"""
self.x = x
self.y = y
if self.x != 0:
self.x_state = 1
else:
self.x_state = x_state
if self.y != 0:
self.y_state = 1
else:
self.y_state = y_state
# some other dunder methods here
def set_value(self, attr, value):
if self.[attr]_state == 0:
self.[attr] = value
self.[attr]_state = 1
else:
raise NotImplementedError
The method checks the appropriate 'state' variable and then assigns the x or y variable to the value given by the user if the 'state' is 0, and then gives the 'state' a value of 1, thereby rendering future value assignments useless.
The confusion I have is with dynamically referencing the attributes.
Is this possible?
Thanks in advance for any assistance!
Option 1 (old, backwards compatible style)
Sounds like you would want a namedtuple (or to subclass it). Without anything more specific, it's unclear what would be reasons not to use it.
In [7]: from collections import namedtuple
point = namedtuple("Point", ["x", "y"])
In [8]: point
Out[8]: __main__.Point
In [9]: point(1, 2)
Out[9]: Point(x=1, y=2)
In [10]: p = point(1, 2)
In [11]: p.x = 1
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-11-4e1ffb2a7978> in <module>()
----> 1 p.x = 1
AttributeError: can't set attribute
Tuples are awesome for this as they are efficient and the "pythonic" way to get immutability.
Option 2 (if you care about Python 3.7+)
If you're on the latest version of Python (3.7+), you could do this:
In [1]: from dataclasses import dataclass
...:
In [2]: #dataclass(frozen=True)
...: class Point():
...: x: int
...: y: int
...:
...:
In [3]: point = Point(x=1, y=2)
In [4]: point
Out[4]: Point(x=1, y=2)
In [5]: point.x = 1
---------------------------------------------------------------------------
FrozenInstanceError Traceback (most recent call last)
<ipython-input-5-7f9ef2714879> in <module>()
----> 1 point.x = 1
<string> in __setattr__(self, name, value)
FrozenInstanceError: cannot assign to field 'x'
The cool part here is that it easy to add some functions to extend your Point.
Option 3 (for shits and giggles)
setattr is indeed quite a difficult way to use for your case. Here's an implementation for fun, but this is not recommended code (hacky, and probably there are some edge cases):
class Node():
def __init__(self, x, y):
self.x = x
self.y = y
self.frozen = True
def __setattr__(self, key, value):
if getattr(self, "frozen", False):
raise ValueError("Cannot change value")
self.__dict__[key] = value

Function in Python gives error message

I am learning Python and I am reading the "Think Python" and doing some simple exercises included in the book.
I am asked "Define a new function called do_four that takes a function object and a value and calls the function four times, passing the value as a parameter."
I am trying to compose this function with one statement by calling a function already defined called do_twice() and test it with a function called print_double(). Here is the code:
def do_twice(f, x):
f(x)
f(x)
def do_four(f, v):
do_twice(do_twice(f, v), v)
def print_twice(s):
print s
print s
s = 'abc'
do_four(print_twice, s)
This code produces an error:
abc
abc
abc
abc
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-41-95b513e5e0ee> in <module>()
----> 1 do_four(print_twice, s)
<ipython-input-40-100f8587f50a> in do_four(f, v)
1 def do_four(f, v):
----> 2 do_twice(do_twice(f, v), v)
<ipython-input-38-7143620502ce> in do_twice(f, x)
1 def do_twice(f, x):
----> 2 f(x)
3 f(x)
TypeError: 'NoneType' object is not callable
In trying to understand what is happening I tried to construct a Stack Diagram as described in the book. Here it is:
Could you explain the error message and comment on the Stack Diagram?
Your advice will be appreciated.
do_twice gets a function on the first argument, and doesn't return anything. So there is no reason to pass do_twice the result of do_twice. You need to pass it a function.
This would do what you meant:
def do_four(f, v):
do_twice(f, v)
do_twice(f, v)
Very similar to how you defined do_twice by f
do_twice(do_twice(f, v), v)
^^^^^^^^^^^^^^
Slightly rewritten:
result = do_twice(f, v)
do_twice(result, v)
You're passing the return value of do_twice(...) as the first parameter to do_twice(...). That parameter is supposed to be a function object. do_twice does not return anything, so result is None, which you're passing instead of the expected function object.
There's no point in nesting the two do_twice in any way here.

Possible to use more than one argument on __getitem__?

I am trying to use
__getitem__(self, x, y):
on my Matrix class, but it seems to me it doesn't work (I still don't know very well to use python).
I'm calling it like this:
print matrix[0,0]
Is it possible at all to use more than one argument? Thanks. Maybe I can use only one argument but pass it as a tuple?
__getitem__ only accepts one argument (other than self), so you get passed a tuple.
You can do this:
class matrix:
def __getitem__(self, pos):
x,y = pos
return "fetching %s, %s" % (x, y)
m = matrix()
print m[1,2]
outputs
fetching 1, 2
See the documentation for object.__getitem__ for more information.
Indeed, when you execute bla[x,y], you're calling type(bla).__getitem__(bla, (x, y)) -- Python automatically forms the tuple for you and passes it on to __getitem__ as the second argument (the first one being its self). There's no good way[1] to express that __getitem__ wants more arguments, but also no need to.
[1] In Python 2.* you can actually give __getitem__ an auto-unpacking signature which will raise ValueError or TypeError when you're indexing with too many or too few indices...:
>>> class X(object):
... def __getitem__(self, (x, y)): return x, y
...
>>> x = X()
>>> x[23, 45]
(23, 45)
Whether that's "a good way" is moot... it's been deprecated in Python 3 so you can infer that Guido didn't consider it good upon long reflection;-). Doing your own unpacking (of a single argument in the signature) is no big deal and lets you provide clearer errors (and uniform ones, rather than ones of different types for the very similar error of indexing such an instance with 1 vs, say, 3 indices;-).
No, __getitem__ just takes one argument (in addition to self). In the case of matrix[0, 0], the argument is the tuple (0, 0).
You can directly call __getitem__ instead of using brackets.
Example:
class Foo():
def __init__(self):
self.a = [5, 7, 9]
def __getitem__(self, i, plus_one=False):
if plus_one:
i += 1
return self.a[I]
foo = Foo()
foo[0] # 5
foo.__getitem__(0) # 5
foo.__getitem__(0, True) # 7
I learned today that you can pass double index to your object that implements getitem, as the following snippet illustrates:
class MyClass:
def __init__(self):
self.data = [[1]]
def __getitem__(self, index):
return self.data[index]
c = MyClass()
print(c[0][0])

Categories