Arguments in a function - python

I understand that when defining a function, you can provide a parameter, like so:
def func(lst):
# code
I also understand that when you are defining a function, you can provide multiple parameters using the *args syntax, like so:
def func(*lst):
# code
I have a problem where I have to define a function that sorts a list and removes any duplicates.
Here's what I did:
def func(lst):
return sorted(set(lst))
The website that I was doing this practice problem (edabit.com) tested my code, like so:
Test.assert_equals(func([1, 3, 3, 5, 5]), [1, 3, 5])
The test ran successfully, and my code was correct. But here's where I got confused, the test provided multiple parameters (1, 3, 3, 5, 5), I didn't use the *args syntax, yet somehow it ran successfully.
Isn't it supposed to give me an error, saying something like func() takes exactly 1 argument (5 given)?
When I provided the *args syntax, it told me TypeError: unhashable type:'list'
My guess is that this probably happened because the test didn't call the function, instead they used the assert keyword. Is my guess correct?

No, you gave a single argument of type list. If you have
a = [1,2,3,4]
You have a list
Calling
f(a)
And f([1, 2,3,4]) is the same
Notice the [ brackets.
If, however, you were too call f(1, 2,3,4), that would be a mistake.
Also: the assert keyword still calls the function. It has no way of not calling it, as it has been put in as an expression.
if you call f(g(5))
Then f is already called with the result of g, not the function call itself.

You passed in a list of numbers, which is one argument:
func([1, 3, 3, 5, 5])
vs.
func(1, 3, 3, 5, 5)

It looks like they only did pass a single parameter to your function.
Test.assert_equals(func([1, 3, 3, 5, 5]), [1, 3, 5]))
The array [1, 3, 3, 5, 5] is passed as the single argument to func(), then the array [1, 3, 5] is passed as the second argument to assert_equals().

Related

Accumulate does not work with sum, but does work with equivalent lambda function. Why?

I was playing around with the accumulate function, and I thought that accumulate(<int[]>,sum) would yield a cumulative sum. However, running the following code results in an error.
from itertools import accumulate
print([*accumulate([1,2,3],sum)])
Specifically, I get TypeError: 'int' object is not iterable. On the other hand, running the same code with a lambda function that does the exact same thing leads to the expected result.
from itertools import accumulate
print([*accumulate([1,2,3],lambda *args:sum(args))])
# [1, 3, 6]
When I run this code using a named, custom function which does the same thing, I get yet another bizarre result.
from itertools import accumulate
def my_sum(*args): return sum(args)
print([*accumulate([1,2,3]),my_sum])
#[1, 3, 6, <function my_sum at 0x7fd57139caf0>]
It's not clear what is leading to the difference in behavior. sum,my_sum, and the anonymous function are are of the type "function", so the type alone isn't determining things. I also did the following to see if I could get any other lead; the only difference I noticed is that sum is a built in function.
print(lambda *args:sum(args),my_sum,sum,sep='\n')
# <function <lambda> at 0x7fd57139cb80>
# <function my_sum at 0x7fd57139cc10>
# <built-in function sum>
So what's going on here?
From the docs: for itertools.accumulate(iterable[, func, *, initial=None])
If func is supplied, it should be a function of two arguments. Elements of the input iterable may be any type that can be accepted as arguments to func. (For example, with the default operation of addition, elements may be any addable type including Decimal or Fraction.)
sum() does accept two arguments, but the first argument must be an iterable, and the second is the start value. Docs
Let's see what accumulate() passes to its func argument by printing the args in my_sum()
def my_sum(*args):
print(args)
return sum(args)
accumulate([1, 2, 3], my_sum)
# (1, 2)
# (3, 3)
So accumulate() passes the last accumulated value and the next number to func. Since the first argument to sum() must be iterable (which an int is not), you get that error.
Your lambda is not equivalent to sum(): sum() takes one iterable and returns the sum of its elements. Your lambda takes any number of arguments and returns the sum of those arguments. To test this, see what you get when you do sum([1, 2, 3]), and my_sum([1, 2, 3]).
In your final example you have a typo. You didn't pass my_sum to accumulate(). You created a list containing the result of accumulate([1, 2, 3]), and then the function my_sum. Fix it to print([*accumulate([1,2,3], my_sum)]) and you get the same output as the lambda case.
Note that providing no func behaves as if func=operator.add and will give you a cumulative sum.
>>> accumulate([1, 2, 3])
[1, 3, 6]

function treats arguments as strings not integers

I'm new to python and was having some issues with a function that I had defined. I had decided to use *numbers (*args) in my function so that my function could take any amount of arguments. However, in using *numbers when defining my function, it seems that my arguments are treated as a string rather than an integer. As you can see below, I receive the output 4, 4, 4, 4, 4, 4, 4, 4 when inputting the argument "4".
The output that I was looking for was actually 32 however I can't seem to get this output as I can't get my arguments to be treated like an integer. I have tried including int(numbers) to my function but to no avail. Are there any slight changes I can make to get my desired output?
def multiply_machine(*numbers):
multiplied_number=3*numbers
m_numbers=5*numbers
return multiplied_number + m_numbers
print(multiply_machine(4))
OUT[1] : (4, 4, 4, 4, 4, 4, 4, 4)
The input is understood as a tuple.
print(multiply_machine(4))
def multiply_machine(numbers=(4,)):
multiplied_number=3*(4,)
m_numbers=5*(4,)
return (4, 4, 4) + (4, 4, 4, 4, 4)
Thus, to have expected results, you should work with it as with a tuple. What was your intention to actually achieve is not clear to me. You might want to edit your question to be more clear.
You can take advantage of 'iter' attribute provided by the '*numbers'
Option 1
def multiply_machine(*numbers): # preferable use args
print(hasattr(numbers, '__iter__'))
multiply_machine(4,3,4,4,5)
Output
True
so you can iterate through
def multiply_machine(*numbers): # preferable use args
multiplied_number = [3 * x for x in numbers]
m_numbers = [5 * x for x in numbers]
summed = [x + y for x, y in zip(multiplied_number,m_numbers)]
return summed
print(multiply_machine(4))
print(multiply_machine(4,2,1))
Output: Note it returns as
[32]
[32, 16, 8]
Option 2:
You can convert your function into a generator like so:
def multiply_machine(*numbers): # preferable use args
yield from numbers
for i in multiply_machine(4,2,3,4,5):
summed = (5*i) + (3*i)
print(summed, end=' ')
Output
32 16 24 32 40

Extract unittest.mock call arguments agnostically w.r.t whether they have been passes as positional arguments or keyword arguments

This question is similar to check unittest.mock call arguments agnostically w.r.t. whether they have been passed as positional arguments or keyword arguments, but with an extension.
Given a mocked function with a given spec, it is possible to check whether a function was called with the right arguments, regardless of whether the function was called with positional arguments or keyword arguments (or a mixture of both). So obviously, mock knows how to translate between those two.
My problem is a little more complicated, because given this signature:
def myfun(a, b, some_list, c=3, d=4)
# Do something with a, b, c, d and some_list
return None
all the following calls should be regarded as correct:
myfun(b=2, a=1, some_list = [3, 4, 5])
myfun(1, 2, [5, 4, 3])
myfun(1, 2, d=4, some_list=[4, 3, 5])
The values for a, b, c and d should be 1, 2, 3 and 4 respectively, but the value for some_list should just contain the values [3, 4, 5], without regard to order.
What I would like to do in a unittest is something like this (when complicated_fun should at one point call myfun with the given arguments):
def test_complicated_fun(self):
myobject = MyClass(...)
myobject.myfun = Mock(return_value = None, spec=myobject.myfun)
myobject.complicated_fun()
myobject.myfun.assert_called_once()
self.assertEqual(myobject.myfun.call_args['a'], 1)
self.assertEqual(myobject.myfun.call_args['b'], 2)
self.assertEqual(myobject.myfun.call_args['c'], 3)
self.assertEqual(myobject.myfun.call_args['d'], 4)
self.assertCountEqual(myobject.myfun.call_args['mylist'], [3, 4, 5])
Except this will of course fail, seeing as we can't subscript call_args.
Is there a way around this, and to get the value of mylist directly from the mock?
I could make a workaround it by using myobject.myfun.call_args[1].get('mylist', myobject.myfun.call_args[0][2]).
But that:
Depends on manually specifying again that mylist is the argument at index 2, meaning if my spec changes I should also manually edit the test.
Doesn't feel very pythonic

Use unknown number of variables to place the returns of a method

I have a method __some_method which might return one parameter, or two, or any number. I want to call this method in another one and place the returns into variables, like so
var1, var2 ... varN = self.__some_method()
Is there any way that one can do this, in a very general setting, such that it works for any number of returned parameters?
I still think a dictionary is the best way to solve what you are trying to do. However, this does not go without saying that you should probably revise why it is you have a function that could return an indeterminate amount of variables. That seems like a bad decision. At the very least that return should be collected inside a data structure, so you know that you will have a single return type where you can deal with the contents yourself.
With that being said. If you are looking for some kind of way where you can assign names to these individual variables that will come back, you can make use of a dictionary to control this. Here is an example:
def foo(*args):
return args
result = foo(1, 2, 3, 4, 5, 6, 7, 8)
var_pack = {'var_{}'.format(idx): data for idx, data in enumerate(result, 1)}
print(var_pack)
# {'var_7': 7, 'var_4': 4, 'var_8': 8, 'var_2': 2, 'var_3': 3, 'var_6': 6, 'var_1': 1, 'var_5': 5}
print(var_pack['var_8'])
# 8

Overwriting builtins python class

What I am looking for is a way to do that in python 2.7
oldlist = list
class list(oldlist):
def append(self, object):
super(list, self).append(object)
return self
def sort(self, cmp=None, key=None, reverse=False):
super(list, self).sort(cmp, key, reverse)
return self
__builtins__.list=list
print list([3, 4, 1, 2]).append(5)
print list([3, 4, 1, 2]).append(5).sort()
print list([3, 4, 1, 2]).append(5).sort(reverse=True)
print list([3, 4, 1, 2]).append(5).sort()[0]
print [3, 4, 1, 2].append(5)
print [3, 4, 1, 2].append(5).sort()
print [3, 4, 1, 2].append(5).sort(reverse=True)
print [3, 4, 1, 2].append(5).sort()[0]
Actually print :
[3, 4, 1, 2, 5]
[1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]
1
None
...
AttributeError: 'NoneType' object has no attribute 'sort'
Should print :
[3, 4, 1, 2, 5]
[1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]
1
[3, 4, 1, 2, 5]
[1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]
1
I know it can be dangerous to edit builtins class, but some methods really return nothing, do a python script actually expect them to return something, so what the problem ?
For now I think that much simple to do :
filterfiles(myfilelist.sort())
than doing :
myfilelist.sort()
filterfiles(myfilelist)
And it permit to see the results when in interactive mode (instead of nothing)
One thing I don't understand is that when we put {1:1, 2:2}, python look for making the dict literal into a dict object, and I know I can't change python to make an instance of mydict, but is there a way to change the builtins dict directly, whereas it use somes hacky way?
No, it’s simply not possible. Literals, that means any literal (strings, numbers, lists, dicts), are part of the Python syntax. The objects they represent are created from the parser at a very low level, long before you can change anything with Python code.
There is another important thing though. The built-in objects are all implemented in native code; they don’t actually exist as Python objects in the Python environment. For that purpose, things like __builtins__.dict provides a reference to the native dictionary type. When the objects are created with literals, the real native type is used though, so __builtins__.dict is never accessed. As such, changing __builtins__.dict will not affect it at all. It will only change the environment, where these references actually matter.
You can imagine this situation like this:
# native code
class _InternalSpecialType:
pass
def makeSpecialType (): # this is our “literal” evaluator
return _InternalSpecialType()
# public interface
SpecialType = _InternalSpecialType
# then in the Python code
class NewSpecialType(SpecialType):
pass
SpecialType = NewSpecialType
# using a “literal”
x = makeSpecialType()
print(type(x)) # _InternalSpecialType
So no, you can’t change what the literal uses under the hood. It’s simply impossible. If you want to have an object of a different type, you will always have to create it explicitely. And then it’s best to name the type differently than the original type to avoid confusion (and incompatibility between the changed type and literals).
And finally, about methods of built-in types not allowing chaining: Just live with it. Guido knowingly decided against it, and usually, Guido has good reasons you can trust, so it’s likely for the better (see also this answer).
I'll explain how to solve the problem you have, rather than how to implement the solution you're after:
Write filterfiles(sorted(myfilelist)).
Methods that return None do so by design: In this case, to avoid inadvertently sorting a list in-place (and losing its current ordering) when you really wanted a sorted copy. Python already provides functional alternatives for such cases, like sorted() in this case, when it makes sense to. Note that sorted() does not modify its argument.
If you do find a use case for which no functional alternative is provided, I would recommend you get around it in the same way: Write a function (not method) that returns what you want to see. (But check out python's functools module first).

Categories

Resources