Monday, 24 April 2023

Access to Non Existing Index or Key

JavaScript, Kotlin and Python have some important differences when accessing indexes or keys that do not exist in a List/Array or Dictionary/Map. Notice that in Python we use the term attribute for fields in an object, and property for the classic getter-setter thing (that is implemented via descriptors). In JavaScript we use the term property for fields, and those properties can be a getter-setter descriptor or just a data descriptor. In Kotlin we use the term property, that always involves a getter and maybe a setter (that can be just the default ones that have no extra logic but accessing the backing field).

JavaScript is very permissive (I'm not surprised...). Accessing a non existing index of an array, a non existing key of a dictionary (a Map) or a non existing property of an object will not throw an exception, but return undefined.


 
> let ar = [];
> ar[2];
undefined
> 
> let dic = new Map();
> dic.get("name");
undefined
> 
> let ob = {};
> ob.name;
undefined

Python is not that permissive and any of the 3 accesses (missing index, missing key or missing attribute) will cause a different exception


ar = []
ar[2]
IndexError: list index out of range 

d = {}
d["name"]
KeyError: 'name'

ob = object()
ob.name
AttributeError: 'object' object has no attribute 'name'


For safe access to a dictionary key we can use the .get(key, default) method, and for safe access to an object attribute we can use getattr(ob, attr_name, default). Oddly enough, lists do not have a .get(index, default) method. Checking this question the reasonings for not having such method seem totally absurd to me. It's one of those occasions where the "being pythonic" and "zen of python" crap really infurates me. Python is an amazing language, but it could be even better if those that make the excellent work of developing and maintaining it were a bit more open to change.

In Kotlin, as it's a static language, accessing a non existent property of an object makes no sense, as the compiler prevents that. As for accessing a not existing index or key, the behaviour is a bit odd. A non existing index in an Array or List will throw an exception, but a non existing key in a map will just return null.


    val ls = listOf("a", "b")
    try {
        println(ls[4])
    }
    catch (e: Throwable) {
        println("${e::class.simpleName} - ${e.message}")
        // ArrayIndexOutOfBoundsException
    }

    val dic = mapOf(
        "France" to "Paris",
        "Austria" to "Vienna",
    )
    try {
        println(dic["Italy"]) // No exception, returns Null
    }
    catch (e: Throwable) {
        println("${e::class.simpleName} - ${e.message}")
    }

We can use getOrNull with an Array or List to get the same behaviour that we have with Maps. We also have getOrElse to return a default value rather than null. With Maps we can use both getOrElse or getOrDefault. Notice that the list and map retrieval behaviours are explained here and here

No comments:

Post a Comment