Function Overloading in python - python

So I need to create overload function sum() for adding two numbers, one should take integer values and another float. The input will be only 2 integer numbers for both sum() functions.
How can I distinguish between the first sum() function and second sum() function then? The first sum() function is supposed to be for integer parameters and the second is supposed to be for floating-point. But if the last function is always the one getting called for regardless of whether the parameter is integer or floating-point. I tired different casting but no success
I have for example these function, but can not understand how to overload them
def add(a, b):
return a + b
def add(a:float, b:float):
return a + b
I can not use dispatch() , isintance() or any other modules

Based on your comments, I'm guessing you looked at this geeksforgeeks article. If you look closely, it did not overload the function with a different number of arguments because the last defined function will always be the defined function.
At the end of the day, Python does not do function overloading like in C++ or Java. There is always only one definition for a function. You'll have to parse the inputs in order to decide the behavior within the function itself. I'm a little confused why you can't use isinstance but I suppose you can get around it with type.

If you have a statically typed language, a add function would need variants for each type of thing you want added. The underlying machine code for adding integers is different than floats, and each would need to be in its own function.
Python isn't that way. In python, + is translated to a call to the object's __add__ method and the object itself decides what it means to add. Take your single sum function and you could add integers, floats, and even concatenate strings.
>>> def sum(a, b):
... return a + b
...
>>> sum(1, 3)
4
>>> sum(1.1, 3.3)
4.4
>>> sum("foo", "bar")
'foobar'
How about adding million element arrays? It'll do that, too.
>>> import numpy as np
>>> a1 = np.ones((1000,1000), dtype=int)
>>> a2 = np.zeros((1000,1000), dtype=int)
>>> sum(a1, a2)
array([[1, 1, 1, ..., 1, 1, 1],
[1, 1, 1, ..., 1, 1, 1],
[1, 1, 1, ..., 1, 1, 1],
...,
[1, 1, 1, ..., 1, 1, 1],
[1, 1, 1, ..., 1, 1, 1],
[1, 1, 1, ..., 1, 1, 1]])

Python does not support overloading. The best that you can do is remove any type identifiers from the arguments and use if-else statements to change the function of the method depending on the input types.
For example:
def add(a,b):
if type(a) == float and type(b) == float:
pass # add floats
elif type(a) == int and type(b) == int:
pass # add ints
else:
assert False, 'input error, arguments (a,b) need to both be either a float or an int'
or
class integers():
def __init__(self) -> None:
pass
#classmethod
def add(cls,a,b):
return int(a) + int(b)
class floats():
def __init__(self) -> None:
pass
#classmethod
def add(cls,a,b):
return float(a) + float(b)
a_float_sum = floats.add(1.0, 2.0)
print(f'{a_float_sum=}')
an_int_sum = integers.add(1, 2)
print(f'{an_int_sum=}')

Related

How to input a vector of arguments in a function in python? [duplicate]

In code like zip(*x) or f(**k), what do the * and ** respectively mean? How does Python implement that behaviour, and what are the performance implications?
See also: Expanding tuples into arguments. Please use that one to close questions where OP needs to use * on an argument and doesn't know it exists.
A single star * unpacks a sequence or collection into positional arguments. Suppose we have
def add(a, b):
return a + b
values = (1, 2)
Using the * unpacking operator, we can write s = add(*values), which will be equivalent to writing s = add(1, 2).
The double star ** does the same thing for a dictionary, providing values for named arguments:
values = { 'a': 1, 'b': 2 }
s = add(**values) # equivalent to add(a=1, b=2)
Both operators can be used for the same function call. For example, given:
def sum(a, b, c, d):
return a + b + c + d
values1 = (1, 2)
values2 = { 'c': 10, 'd': 15 }
then s = add(*values1, **values2) is equivalent to s = sum(1, 2, c=10, d=15).
See also the relevant section of the tutorial in the Python documentation.
Similarly, * and ** can be used for parameters. Using * allows a function to accept any number of positional arguments, which will be collected into a single parameter:
def add(*values):
s = 0
for v in values:
s = s + v
return s
Now when the function is called like s = add(1, 2, 3, 4, 5), values will be the tuple (1, 2, 3, 4, 5) (which, of course, produces the result 15).
Similarly, a parameter marked with ** will receive a dict:
def get_a(**values):
return values['a']
s = get_a(a=1, b=2) # returns 1
this allows for specifying a large number of optional parameters without having to declare them.
Again, both can be combined:
def add(*values, **options):
s = 0
for i in values:
s = s + i
if "neg" in options:
if options["neg"]:
s = -s
return s
s = add(1, 2, 3, 4, 5) # returns 15
s = add(1, 2, 3, 4, 5, neg=True) # returns -15
s = add(1, 2, 3, 4, 5, neg=False) # returns 15
In a function call, the single star turns a list into separate arguments (e.g. zip(*x) is the same as zip(x1, x2, x3) given x=[x1,x2,x3]) and the double star turns a dictionary into separate keyword arguments (e.g. f(**k) is the same as f(x=my_x, y=my_y) given k = {'x':my_x, 'y':my_y}.
In a function definition, it's the other way around: the single star turns an arbitrary number of arguments into a list, and the double start turns an arbitrary number of keyword arguments into a dictionary. E.g. def foo(*x) means "foo takes an arbitrary number of arguments and they will be accessible through x (i.e. if the user calls foo(1,2,3), x will be (1, 2, 3))" and def bar(**k) means "bar takes an arbitrary number of keyword arguments and they will be accessible through k (i.e. if the user calls bar(x=42, y=23), k will be {'x': 42, 'y': 23})".
I find this particularly useful for storing arguments for a function call.
For example, suppose I have some unit tests for a function 'add':
def add(a, b):
return a + b
tests = { (1,4):5, (0, 0):0, (-1, 3):3 }
for test, result in tests.items():
print('test: adding', test, '==', result, '---', add(*test) == result)
There is no other way to call add, other than manually doing something like add(test[0], test[1]), which is ugly. Also, if there are a variable number of variables, the code could get pretty ugly with all the if-statements you would need.
Another place this is useful is for defining Factory objects (objects that create objects for you).
Suppose you have some class Factory, that makes Car objects and returns them.
You could make it so that myFactory.make_car('red', 'bmw', '335ix') creates Car('red', 'bmw', '335ix'), then returns it.
def make_car(*args):
return Car(*args)
This is also useful when you want to call the constructor of a superclass.
It is called the extended call syntax. From the documentation:
If the syntax *expression appears in the function call, expression must evaluate to a sequence. Elements from this sequence are treated as if they were additional positional arguments; if there are positional arguments x1,..., xN, and expression evaluates to a sequence y1, ..., yM, this is equivalent to a call with M+N positional arguments x1, ..., xN, y1, ..., yM.
and:
If the syntax **expression appears in the function call, expression must evaluate to a mapping, the contents of which are treated as additional keyword arguments. In the case of a keyword appearing in both expression and as an explicit keyword argument, a TypeError exception is raised.

what is **eval() mean in python [duplicate]

In code like zip(*x) or f(**k), what do the * and ** respectively mean? How does Python implement that behaviour, and what are the performance implications?
See also: Expanding tuples into arguments. Please use that one to close questions where OP needs to use * on an argument and doesn't know it exists.
A single star * unpacks a sequence or collection into positional arguments. Suppose we have
def add(a, b):
return a + b
values = (1, 2)
Using the * unpacking operator, we can write s = add(*values), which will be equivalent to writing s = add(1, 2).
The double star ** does the same thing for a dictionary, providing values for named arguments:
values = { 'a': 1, 'b': 2 }
s = add(**values) # equivalent to add(a=1, b=2)
Both operators can be used for the same function call. For example, given:
def sum(a, b, c, d):
return a + b + c + d
values1 = (1, 2)
values2 = { 'c': 10, 'd': 15 }
then s = add(*values1, **values2) is equivalent to s = sum(1, 2, c=10, d=15).
See also the relevant section of the tutorial in the Python documentation.
Similarly, * and ** can be used for parameters. Using * allows a function to accept any number of positional arguments, which will be collected into a single parameter:
def add(*values):
s = 0
for v in values:
s = s + v
return s
Now when the function is called like s = add(1, 2, 3, 4, 5), values will be the tuple (1, 2, 3, 4, 5) (which, of course, produces the result 15).
Similarly, a parameter marked with ** will receive a dict:
def get_a(**values):
return values['a']
s = get_a(a=1, b=2) # returns 1
this allows for specifying a large number of optional parameters without having to declare them.
Again, both can be combined:
def add(*values, **options):
s = 0
for i in values:
s = s + i
if "neg" in options:
if options["neg"]:
s = -s
return s
s = add(1, 2, 3, 4, 5) # returns 15
s = add(1, 2, 3, 4, 5, neg=True) # returns -15
s = add(1, 2, 3, 4, 5, neg=False) # returns 15
In a function call, the single star turns a list into separate arguments (e.g. zip(*x) is the same as zip(x1, x2, x3) given x=[x1,x2,x3]) and the double star turns a dictionary into separate keyword arguments (e.g. f(**k) is the same as f(x=my_x, y=my_y) given k = {'x':my_x, 'y':my_y}.
In a function definition, it's the other way around: the single star turns an arbitrary number of arguments into a list, and the double start turns an arbitrary number of keyword arguments into a dictionary. E.g. def foo(*x) means "foo takes an arbitrary number of arguments and they will be accessible through x (i.e. if the user calls foo(1,2,3), x will be (1, 2, 3))" and def bar(**k) means "bar takes an arbitrary number of keyword arguments and they will be accessible through k (i.e. if the user calls bar(x=42, y=23), k will be {'x': 42, 'y': 23})".
I find this particularly useful for storing arguments for a function call.
For example, suppose I have some unit tests for a function 'add':
def add(a, b):
return a + b
tests = { (1,4):5, (0, 0):0, (-1, 3):3 }
for test, result in tests.items():
print('test: adding', test, '==', result, '---', add(*test) == result)
There is no other way to call add, other than manually doing something like add(test[0], test[1]), which is ugly. Also, if there are a variable number of variables, the code could get pretty ugly with all the if-statements you would need.
Another place this is useful is for defining Factory objects (objects that create objects for you).
Suppose you have some class Factory, that makes Car objects and returns them.
You could make it so that myFactory.make_car('red', 'bmw', '335ix') creates Car('red', 'bmw', '335ix'), then returns it.
def make_car(*args):
return Car(*args)
This is also useful when you want to call the constructor of a superclass.
It is called the extended call syntax. From the documentation:
If the syntax *expression appears in the function call, expression must evaluate to a sequence. Elements from this sequence are treated as if they were additional positional arguments; if there are positional arguments x1,..., xN, and expression evaluates to a sequence y1, ..., yM, this is equivalent to a call with M+N positional arguments x1, ..., xN, y1, ..., yM.
and:
If the syntax **expression appears in the function call, expression must evaluate to a mapping, the contents of which are treated as additional keyword arguments. In the case of a keyword appearing in both expression and as an explicit keyword argument, a TypeError exception is raised.

How to use optional argument of random.shuffle in Python

From the doc of random.shuffle(x[, random]), it says:
The optional argument random is a 0-argument function returning a random float in [0.0, 1.0); by default, this is the function random()
Could someone please explain what 0-argument function means and give an example of random.shuffle() with the optional argument random? I searched but couldn't find any example for that case. Also, what did it mean by "this is the function random()"? Does this refer to the optional argument?
It means you can pass in the name of a function which does not require an argument.
def foo():
return 0.5
is such a function.
def bar(limit):
return limit
is not, because it requires an argument limit.
Usage example:
random.shuffle(itemlist, random=foo)
If the input itemlist was [1, 2, 3] it will now be [1, 3, 2]. I have established this experimentally, and I suppose how exactly the shuffle operation uses the output from the random function could change between Python versions.
The default if you don't specify anything is the function random().
One possible use case for this is if you want predictable output e.g. for a test case. Another is if you want nonuniform distribution - for example, a random function which prefers small values over large ones, or implements e.g. Poisson or normal distribution.
A 0-argument function has an empty argument list.
def random_seed(): # Zero arguments here.
return MY_CONFIG.predictble_random_seed # Some imaginary config.
random.shuffle(some_list, random_seed) # Always the same shuffling.
The point of this arrangement is to allow to control the predictability of the shuffling. You can return a really random number (from timer, /dev/urandom, etc, as random.random() does) in production, and a controlled number in a test environment:
def get_random_generator(environment):
if environment == 'test':
return lambda: 0.5 # A 0-argument callable returning a constant.
else:
return random.random # A function returning a random number.
# ...
# The below is predictable when testing in isolation,
# unpredictable when running in production.
# We suppose that `environment` has values like 'test' and 'prod'.
random.shuffle(entries, get_random_generator(environment))
The random parameter is the seed. Then if you always use the same seed, it always reorder your array with the same logic. See the exemple. 5 is at index 4 and go to 0. 6 go to 4 (Old index of 5) then if we reuse the same seed, 6 go the index 0 because 6 is at index 4 like 5 at the first shuffle
Exemple:
>>> import random
>>> r = random.random()
>>> r
0.4309619702601998
>>> x = [1, 2, 3, 4, 5, 6]
>>> random.shuffle(x, lambda: r)
>>> x
[5, 1, 4, 2, 6, 3]
>>> random.shuffle(x, lambda: r)
>>> x
[6, 5, 2, 1, 3, 4]
>>> x = [1, 2, 3, 4, 5, 6]
>>> random.shuffle(x, lambda: r)
>>> x
[5, 1, 4, 2, 6, 3]
Source

Concatenation of the result of a function with a mutable default argument

Suppose a function with a mutable default argument:
def f(l=[]):
l.append(len(l))
return l
If I run this:
def f(l=[]):
l.append(len(l))
return l
print(f()+["-"]+f()+["-"]+f()) # -> [0, '-', 0, 1, '-', 0, 1, 2]
Or this:
def f(l=[]):
l.append(len(l))
return l
print(f()+f()+f()) # -> [0, 1, 0, 1, 0, 1, 2]
Instead of the following one, which would be more logical:
print(f()+f()+f()) # -> [0, 0, 1, 0, 1, 2]
Why?
That's actually pretty interesting!
As we know, the list l in the function definition is initialized only once at the definition of this function, and for all invocations of this function, there will be exactly one copy of this list. Now, the function modifies this list, which means that multiple calls to this function will modify the exact same object multiple times. This is the first important part.
Now, consider the expression that adds these lists:
f()+f()+f()
According to the laws of operator precedence, this is equivalent to the following:
(f() + f()) + f()
...which is exactly the same as this:
temp1 = f() + f() # (1)
temp2 = temp1 + f() # (2)
This is the second important part.
Addition of lists produces a new object, without modifying any of its arguments. This is the third important part.
Now let's combine what we know together.
In line 1 above, the first call returns [0], as you'd expect. The second call returns [0, 1], as you'd expect. Oh, wait! The function will return the exact same object (not its copy!) over and over again, after modifying it! This means that the object that the first call returned has now changed to become [0, 1] as well! And that's why temp1 == [0, 1] + [0, 1].
The result of addition, however, is a completely new object, so [0, 1, 0, 1] + f() is the same as [0, 1, 0, 1] + [0, 1, 2]. Note that the second list is, again, exactly what you'd expect your function to return. The same thing happens when you add f() + ["-"]: this creates a new list object, so that any other calls to f won't interfere with it.
You can reproduce this by concatenating the results of two function calls:
>>> f() + f()
[0, 1, 0, 1]
>>> f() + f()
[0, 1, 2, 3, 0, 1, 2, 3]
>>> f() + f()
[0, 1, 2, 3, 4, 5, 0, 1, 2, 3, 4, 5]
Again, you can do all that because you're concatenating references to the same object.
Here's a way to think about it that might help it make sense:
A function is a data structure. You create one with a def block, much the same way as you create a type with a class block or you create a list with square brackets.
The most interesting part of that data structure is the code that gets run when the function is called, but the default arguments are also part of it! In fact, you can inspect both the code and the default arguments from Python, via attributes on the function:
>>> def foo(a=1): pass
...
>>> dir(foo)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', ...]
>>> foo.__code__
<code object foo at 0x7f114752a660, file "<stdin>", line 1>
>>> foo.__defaults__
(1,)
(A much nicer interface for this is inspect.signature, but all it does is examine those attributes.)
So the reason that this modifies the list:
def f(l=[]):
l.append(len(l))
return l
is exactly the same reason that this also modifies the list:
f = dict(l=[])
f['l'].append(len(f['l']))
In both cases, you're mutating a list that belongs to some parent structure, so the change will naturally be visible in the parent as well.
Note that this is a design decision that Python specifically made, and it's not inherently necessary in a language. JavaScript recently learned about default arguments, but it treats them as expressions to be re-evaluated anew on each call — essentially, each default argument is its own tiny function. The advantage is that JS doesn't have this gotcha, but the drawback is that you can't meaningfully inspect the defaults the way you can in Python.

what does *line mean in this code? [duplicate]

In code like zip(*x) or f(**k), what do the * and ** respectively mean? How does Python implement that behaviour, and what are the performance implications?
See also: Expanding tuples into arguments. Please use that one to close questions where OP needs to use * on an argument and doesn't know it exists.
A single star * unpacks a sequence or collection into positional arguments. Suppose we have
def add(a, b):
return a + b
values = (1, 2)
Using the * unpacking operator, we can write s = add(*values), which will be equivalent to writing s = add(1, 2).
The double star ** does the same thing for a dictionary, providing values for named arguments:
values = { 'a': 1, 'b': 2 }
s = add(**values) # equivalent to add(a=1, b=2)
Both operators can be used for the same function call. For example, given:
def sum(a, b, c, d):
return a + b + c + d
values1 = (1, 2)
values2 = { 'c': 10, 'd': 15 }
then s = add(*values1, **values2) is equivalent to s = sum(1, 2, c=10, d=15).
See also the relevant section of the tutorial in the Python documentation.
Similarly, * and ** can be used for parameters. Using * allows a function to accept any number of positional arguments, which will be collected into a single parameter:
def add(*values):
s = 0
for v in values:
s = s + v
return s
Now when the function is called like s = add(1, 2, 3, 4, 5), values will be the tuple (1, 2, 3, 4, 5) (which, of course, produces the result 15).
Similarly, a parameter marked with ** will receive a dict:
def get_a(**values):
return values['a']
s = get_a(a=1, b=2) # returns 1
this allows for specifying a large number of optional parameters without having to declare them.
Again, both can be combined:
def add(*values, **options):
s = 0
for i in values:
s = s + i
if "neg" in options:
if options["neg"]:
s = -s
return s
s = add(1, 2, 3, 4, 5) # returns 15
s = add(1, 2, 3, 4, 5, neg=True) # returns -15
s = add(1, 2, 3, 4, 5, neg=False) # returns 15
In a function call, the single star turns a list into separate arguments (e.g. zip(*x) is the same as zip(x1, x2, x3) given x=[x1,x2,x3]) and the double star turns a dictionary into separate keyword arguments (e.g. f(**k) is the same as f(x=my_x, y=my_y) given k = {'x':my_x, 'y':my_y}.
In a function definition, it's the other way around: the single star turns an arbitrary number of arguments into a list, and the double start turns an arbitrary number of keyword arguments into a dictionary. E.g. def foo(*x) means "foo takes an arbitrary number of arguments and they will be accessible through x (i.e. if the user calls foo(1,2,3), x will be (1, 2, 3))" and def bar(**k) means "bar takes an arbitrary number of keyword arguments and they will be accessible through k (i.e. if the user calls bar(x=42, y=23), k will be {'x': 42, 'y': 23})".
I find this particularly useful for storing arguments for a function call.
For example, suppose I have some unit tests for a function 'add':
def add(a, b):
return a + b
tests = { (1,4):5, (0, 0):0, (-1, 3):3 }
for test, result in tests.items():
print('test: adding', test, '==', result, '---', add(*test) == result)
There is no other way to call add, other than manually doing something like add(test[0], test[1]), which is ugly. Also, if there are a variable number of variables, the code could get pretty ugly with all the if-statements you would need.
Another place this is useful is for defining Factory objects (objects that create objects for you).
Suppose you have some class Factory, that makes Car objects and returns them.
You could make it so that myFactory.make_car('red', 'bmw', '335ix') creates Car('red', 'bmw', '335ix'), then returns it.
def make_car(*args):
return Car(*args)
This is also useful when you want to call the constructor of a superclass.
It is called the extended call syntax. From the documentation:
If the syntax *expression appears in the function call, expression must evaluate to a sequence. Elements from this sequence are treated as if they were additional positional arguments; if there are positional arguments x1,..., xN, and expression evaluates to a sequence y1, ..., yM, this is equivalent to a call with M+N positional arguments x1, ..., xN, y1, ..., yM.
and:
If the syntax **expression appears in the function call, expression must evaluate to a mapping, the contents of which are treated as additional keyword arguments. In the case of a keyword appearing in both expression and as an explicit keyword argument, a TypeError exception is raised.

Categories