These pages are targeted at people who can already program in Python, so the most basic aspects of functions and methods is assumed knowledge, and this page focuses on what is of interest to those who aleady have that assumed knowledge.
- Default Value Parameters.
- The default value trap
- ‘Pure’ functions / methods
- Function overloading
Default Value Parameters
Both Python and Kotlin provide for default values for parameters. This radically reduces the need for overloading of functions and is a great feature.
The default value trap (Python).
Python.
…. but there is a trap with default values in Python! How default values work is very different in Python than with Kotlin. In fact the biggest trap is to mistake Python as working the way Kotlin does. Here is two examples of the behaviour that can be a trap with Python.
a=1
def fred(i=a):
print(i)
>>> print( fred() )
1
>>> a = 5
>>> print( fred() )
1 # were you expecting 5?
# second example
def add_to_list(value, the_list = []):
the_list.append(value)
return the_list
>>> add_to_list(1)
[1]
>>> add to list(4, [1,2,3])
[1, 2, 3, 4]
>>> hold = add_to_list(5)
>>> hold
[1, 5] # may not be expected result!!
>>> hold.append(11) # hold references the list used as the default!
>>> add_to_list(20)
[1, 5, 11, 20]
Where a default value is specified by an expression in Python, the default value is calculated at the time the function is defined, not at the time the function is called or invoked
. This means the first part of the example will always default to the value 1
because a
had the value 1
when the function was defined. Changing a
prior to calling fred
makes no difference.
But it is the second case above that more often results in confusion. Using a mutable value such as an empty list or empty dictionary as a default value is common requirement, but cannot be coded in what seems the obvious way, as above, as this results in all calls to the function using the same mutable list or dictionary, The solution is to use a special default value that is immutable, such as None, as an indicator that the default is required, and instance a new empty list in the code.
# second example - fixed
def add_to_list(value, the_list = None):
if the_list == None:
the_list = []
the_list.append(value)
return the_list
testing the example:
>>> add_to_list(1)
[1]
>>> add to list(4, [1,2,3])
[1, 2, 3, 4]
>>> hold = add_to_list(5)
>>> hold
[5] # now the expected result!!
>>> hold.append(11) # hold now references a unique list
>>> add_to_list(20)
[20]
By moving the assignment to a new list from the parameter default to within the function body, the problem is eliminated.
However, dataclasses provide a better solution, enabling a factory function (which usually can be a lambda) to be used to provide default values. The following example is a class with a dict (“a_dict
“) with a default value generated by a simple lamba that returns a new dictionary.
@dataclass
Class Sample:
a_dict:dict = field(default_factory= lambda: {})
Kotlin.
Python defines functions at program executing the ‘def’ statement, while Kotlin, as a compiled language, has functions defined by the compiler prior to any program code being run. This means that python cannot run code as the ‘fun’ statement is executed, as it simply is not executed. Default values are calculated when the function is called.
So the Kotlin equivalent code to working the Python example is:
fun addToList(value: Int, theList:MutableList<Int> = mutableListOf())
= theList.apply { add(value)}
‘Pure’ functions / methods
Pure functions are simply functions with no ‘side effects’. This is already well explained online: eg here
I will look to update this section with more links.
Function/Method overloading
As type annotations are not processed by the Python language, any Python function can accept parameters of any type. With Kotlin, parameters must fit the declared parameter type. This provides each language with different solutions to the same problem. Consider this (rather lame) function for building up a formatted string in python:
def add_formatted(sofar: str, toadd):
if isinstance(toadd, str):
return sofar + f" str: {toadd}"
if isinstance(toadd, int):
return sofar + f" int: {toadd}"
return sofar + f" ???: {toadd}" #only providing for str and int
Yes, the code is very similar for all cases in this contrived example. More useful examples of overloading will be provided in later sections, when used together with objects, however this still provides some insight. Any python function will accept any parameter, it is up to the run time code to raise and error or otherwise handle any unexpected types. A Kotlin function could be written in the same way, accepting toad as an Any type, and switching on the actual type. Howwever consider this alternative:
fun addFormatted(sofar:String, toadd:String) = sofar + " str: $toadd"
fun addFormatted(sofar:String, toadd:Int) = sofar + " int: $toadd"
By defining functions for the types that are expected, the IDE will immediately highlight any attempt to call addFormatted
with an inappropriate type. No need for error checking in the code, as the code can never be called with the wrong type.
More…
Much of the more advanced features of functions with both Kotlin and Python relate to member functions (also known as methods) and will be covered in the section ” Classes Objects, Class Object & Instances ” see the contents page.
[…] Default Value Trap: The risk with mutable default value parameters in Python […]
LikeLike
A very well explained lesson. The only thing that is missing is code highlighting. I am following another website (Kotlin-Android)and I like their code highlighting feature. Easy to read.
LikeLike
[…] trap is that mutable default values become shared between all instances of the class as explained here. However, as in the example below, default values for mutable types using factories are […]
LikeLike