
This page will serve as repository of limitations found when attempting to move kotlin code to python code. Some limitations are a natural consequence of moving to static types, others are effectively inherited from Java.
The list here is based on placing those I find most significant at the top, and the further down the list the less significant.
- reflection
- base class parameter pass through
- constructor calls
- Intricate, preset order, class data initialisation
- *args, **kwargs
reflection
While reflection is a key part of Kotlin reflection originates from Java reflection, which itself was very much an afterthought bolted onto to Java.
base class parameter pass through
background
Kotlin, like python, allows for optional parameters, a feature not found in Java. A consequence of this feature is that libraries with heavily used classes can over time evolve to have a large number of optional parameters in the most heavily used methods. A larger number of parameters should only occur in code in a language that has optional parameters, as without the parameters being optional, every extra parameter would introduce overhead on every call. These methods with a large number of parameters are often frequently used methods, and the goal is that well chosen defaults will mean that most of these optional parameters are only provided in exceptional cases. While a typical instance of the class may require only 2 or 3 parameters, while there may be as many as 20 to chose from.
To find examples, I simply thought: “what are the most widely used python packages I can think of?”. The first three that occurred to me to check were sqlalchemy, attr, and Django, and for each of these I looked for the most basic usage cases I could find. The first examples I found are:
- sqlalchemy: Table Class 20 optional parameters (see ‘parameters’)
- attr: atrr.s() function – 9 optional parameters (see attr.s function)
- Django: Field class – 22 optional parameters (see __init__)
While such a large number of parameters should not normally occur in Java due to lack of optional parameters, I think these example establish that the pattern is common in well regarded python packages.
The Problem.
Consider the Django Field
class . This class serves as a base class for several other classes, such as TextField
, TimeField
, BinaryField
etc. To code examples like these cleanly, some type of language mechanism is needed which effectively achieves: “include all fields from the base class constructor (or other method from the base class) as also parameters to this class unless specifically declared in this class“.
Python uses the *args,**kwargs system, which for several reasons is not an appropriate solution for kotlin, but is at least a solution. There are some more elegant solutions possible for kotlin, and perhaps one will be added at a later time.
In Python the code for defining the BinaryField Class is as follows (not exact code for simplicity):
class BinaryField(Field):
def __init__(self, *args, **kwargs):
kwargs['editable'] = False
super().__init__(*args, **kwargs)
// other init code goes here
while in kotlin (slightly changed for name conventions and simplicity) the code becomes:
class BinaryField(
verboseName:String?=null,
name:String?=null,
primaryKey:Boolean=false,
maxLength:Int?=null,
unique:Boolean=false,
blank:Boolean=false,
nulled:Boolean=false,
dbIndex:Boolean=false,
rel:String?=null,
default:Any?=null,
//editable:Boolean=true, - force 'false' for editable
serialize:Boolean=true,
uniqueForYear:Int?=null,
choices:String?=null,
helpText:String="",
dbColumn:Int?=null,
dbTablespace:Int?=null,
autoCreated:Boolean=false,
validators:List<Any>?=null,
errorMessages:String?=null
):Field(verboseName=verboseName, name=name, primaryKey=primaryKey,
maxLength=maxLength, unique=unique, blank=blank, nulled=nulled,
dbIndex=dbIndex, rel=rel, default=default, editable=false,
serialize=serialize, uniqueForYear=uniqueForYear, choices=choices,
helpText=helpText, dbColumn=dbColumn,
dbTablespace=dbTablespace, autoCreated=autoCreated,
validators=validators, errorMessages=errorMessages) {
// class code here
}
Clearly, the call to the base constructor will be much shorter if not using named parameters, which is a choice, but in a list this long I would use named parameters.
The code (or almost identical code) will be repeated TextField, TimeField and the over 12 other fields that inherit from Field. Any update to the parameter list for the base Field class is tedious to say the least.
This is a case where kotlin requires boilerplate that is not required in python. What is needed is some way to say “accept all parameters to the base default constructor not specifically named in the base call, and pass these parameters through“. Given this problem will not occur in Java libraries which have no default parameters, it may be some time before kotlin designers consider this, if ever. In the mean time, messy.
Constructor Calls
(to be added: by Nov 13)
Intricate, preset order, class data initialisation
Kotlin has what can appear a rather strange way of implementing the overriding of properties when extending classes. The result is that when extending classes, the behaviour of init
code can be unexpected. The way to avoid this is to use lazy properties in place of initialising during init.
open class Base{
val simple = 3
open val open = 3
open val openGet = 3
open val getter get()= 3
open val getOpen get()= 3
open val getDelg by GetDelg(3)
init {
println("base simple $simple open $open openG $openGet "+
"getOpen $getOpen getter $getter "+
" getDelg $getDelg")
}
//open fun add(a:Int,b:Int) = a + b
}
class SubClass:Base(){
override val open = 4
override val openGet get()= 4
override val getter get() = 4
override val getOpen = 4
//override val getDelg by GetDelg(4) //uncomment for null pointer
init {
println("sub simple $simple open $open openG $openGet "+
"getOpen $getOpen getter $getter "+
" getDelg $getDelg")
}
}
class GetDelg(val value:Int){
operator fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return value
}
}
open class Base{ val simple = 3 open val open = 3 open val openGet = 3 open val getter get()= 3 open val getOpen get()= 3 open val getDelg by GetDelg(3) init { println("base simple $simple open $open openG $openGet "+ "getOpen $getOpen getter $getter "+ " getDelg $getDelg") } //open fun add(a:Int,b:Int) = a + b } class SubClass:Base(){ override val open = 4 override val openGet get()= 4 override val getter get() = 4 override val getOpen = 4 //override val getDelg by GetDelg(4) //uncomment for null pointer init { println("sub simple $simple open $open openG $openGet "+ "getOpen $getOpen getter $getter "+ " getDelg $getDelg") } } class GetDelg(val value:Int){ operator fun getValue(thisRef: Any?, property: KProperty<*>): Int { return value } }
The print from instancing a SubClass object is:
base simple 3 open 0 openG 4 getOpen 0 getter 4 getDelg 3
sub simple 3 open 4 openG 4 getOpen 4 getter 4 getDelg 3
Open is 0 in the base class init because the code to set the value has not yet been run, but not it is not 3 as you would expect.
openG, a value overridden by a get() method, perhaps unexpected returns the override value in both init() methods
getOpen, a get() method in the base overridden by a simple initialisation, behaves as a simple initialised value overridden by a new initialise, which is to be unitialised in the base init() method
getter() , a get() method overidden by another get() method returns the override value as does openG
getDelg() actually generates a null pointer exception if called during the base init() method, as the overridden value has not been calculated
Note: part of this behaviour is base on the fact that overridden properties are actually new distinct properties, so the do not inherit values from base class property which is still accessible via super.<name>
. This means, counterintuitively, that open
in the base init()
method, returns 0
while super.open
in the subclass init()
will return 3
I will update with more on the use of lazy to avoid this issue, but the main point is to think carefully before initialising values in a base class that are open.
*args, **kwargs (to be added)
A specific use of *args and **kwargs has already been covered in base class parameter pass through.
Outside of that specific use, kotlin does have effective equivalents to most use cases, but may depend on the reflections library, which is not available on all platforms at this time.
vararg parameters capture the equivalent to a *args list, and allows for using the list for calling in a manner very similar to python.
callBy provide most of the functionality of **kwargs when used for calling functions, but some code is needed to map parameter names to KParameters. A link to such code may be added to this page is someone asks 🙂
For cases where it is desired to capture parameters in map for, using a map actually makes better sense in every case I have found, but I will update this further if I find a case where this is not true.
🙂 A link to such code may be added to this page is someone asks – May I ask?
LikeLike
Sorry about the delay…. I will add this
LikeLike