Class Level Objects, Companion Objects & Instances

Topics for this section:

  • Class Level Objects & Companion Objects:  Key Terminology.
    • the simplest case – one object type
    • Python – an additional object type requires precise terminology
    • Kotlin: the companion object equates to the Python class object
  • Class & Companion Variables and Methods vs instance methods and variables
  •  Terminology for Clarity: Class Level Object, Variables and Methods
  • instance vs class variables in python
  • an alternative explanation of instance vs class for python

Class Level Objects & Companion Objects: Settling on Terminology

This bliki assumes a reader has knowledge of what class is, and what is an object is, but precise usage of the terminology does change from language to language, so please  tolerate a review of very basic concepts in order to be specific about exact terminology

Consider a Person class, that will have two instances, one for “Jack” and one for “Jill”.

The simplest case: the base of confusion

With a compiler language, the compiler will read the class definition and generate the code for all methods required by the class.  The compiler will also store in an internal data structure of the compiler all relevant information about Person class.  For any code relating to the class, the compiler will use the information it has stored about the class to enable generation of the appropriate code.

At run time the Jack and Jill instances Jack and Jill of the person class are created.  So there are two object, both instances of same class: the Person class.  In this environment, any reference to a Person object can only be a reference to the Jack or Jill instances.

Python: An additional object type requires precise terminology.

Now consider Python.  The python language also builds a data structure containing the relevant information about the person class, and in fact that data structure is an object of the ‘type’ class.  With python, there is not the same distinction between compile time and run time, and that instance of a ‘type’ object holding the information on the class is accessible to the application being run in python. The ‘internal data structure’, becomes no longer restricted to being internal.  At run time, there will be one instance of the ‘type’ class : Person, and once Jack and Jill are instanced, there will be two instances of the Person class: Jack and Jill.  As there is only one data structure describing the Person class, regardless of how many instances of Person,  there will always be one Person class level object, regardless of whether there are zero or one hundred instances.  So with the Jack and Jill example with python, there are now three objects available to the running program.  A type object: Person, and two Person Objects: Jack and Jill.  When we say ‘Person object’, do we mean the object describing the class, or one of the two objects which are instances of the class?

In python, the convention is that the object describing the class is ‘the class’, and a class variable is a variable within that object describing the class, as opposed to a variable within an instance of the class such as Jack and Jill.  The variables within these instances are instance variables.  This contrasts with the previous simple case, where, with only one instances type of object for any class, the term class variable can only mean an instance variable.

Kotlin: The companion Object partially equates to the Python class object

Kotlin also provides for an object holding data at the class level, the companion object. Unlike Python, not all classes have a companion object.  All more fundamental difference is that the companion object does automatically hold ‘reflection’ information related to the class.  All python class level objects share a common set of attributes (and thus are instances of the ‘type’ class), while kotlin companion objects only hold the attributes specified for that companion object, and thus are effectively singletons.

Kotlin Classes  must specifically declare a companion object to have any object at class level. Keeping the companion object as optional allows for building the metaprogramming power of Python, or alternatively, the closer to C++ internal simplicity of Java. Companion object can hold reflection data, can implement interfaces and inherit from base classes, giving them more potential power than python ‘type’ objects, but less power by default than python ‘type’ objects.  With no standard base class for companion objects, code cannot be written which will be useful across even all those classes with companion objects, while with python, code to process all class objects is well established.

Like the Python class object, there is only one instance of the companion object for a class, regardless of how many (or even when there are zero) instances of object of the class have been created.

Terminology for Clarity: Class Level Object, Variables and Methods

The python term ‘class method’ or ‘ class variable’ has a very high potentional for confusion with regular instance variables or instance methods. The current in use within this bliki is to use the term ‘class level variable‘ orclass level method‘.

Terminology: Class Level Object. The singleton object associated with the class itself, and always present, even when no instances of the class are present. In both Python and Kotlin there is the concept of a single object shared by all instances of the class, and existing even if there are no members of the class instanced. In python, every class has such and object and the python terminology is simply (and potentially ambiguously) “class”.  In Kotlin, such objects at class level only exist for a class if declared in that class as a “companion object”.  The term to describe the concept is in these pages is “class level object”

Terminology: Class Level Variable.  A variable within the class level object. So all instances of the class will be sharing the same copy class level variable. A simple example of a class level variable would be a counter of how many instances of the class have been created.  Each instance of the class could increment this counter once in the constructor/init method.  Such a variable can be accessed without an instance of the class and will still exist when no objects of the class can be instanced.

Terminology: Class Level Method.  As with static methods, this is a method that does not access the context of an instance of the class, so can be called even when no instanced objects have been created.  An example of a class level method would be a factory method.  Unlike static methods, class level methods are in the context of an object instance, however that object is the class level object, not an instance of a member of the class.

which is a method with the namespace of a class but with no access to a class level object, class level methods do have persistent data in the form of class level variables within the class level bA var

Class & Companion Variables and Methods vs instance methods and variables

Variables.

With Kotlin, the developer can declare variables within the companion object, and with Python, class variables can be declared within the class level object.  In both cases, such variables provides a single variable, shared by every instance of the class, and accessible even without the need for an instance.

Java and several other languages have static variables, which have the same property of  a single shared variable regardless of instances of the class and the possibility of access independent an any instance.   However,  with Python and Kotlin, this shared variable is also contained in an object, while a static variable has name scope within a class, but beyond that name, there is no relationship between a static variable and any object.  An example of this difference is that a companion or class object can be part of a family of related classes and implement an interface, or have inheritance or duck typing, allowing passing the object as parameter, thus allowing code to operate on the class/companion object parameter.

Simply put, the companion/ class level object is a more object oriented approach that supports more metaprogramming.

Methods

Python supports both class methods and static methods.  Static methods being an equivalent to other static methods in that they have name scope, but have no  relationship to a specific instance of the class.  So for the person class with Jack() and Jill() instanced, a class method has no access to fields such as ‘name’ within either Jack or Jill instances.

Kotlin has companion object methods with are similar to python class methods, in that they have access to the companion object containing all settings, class variables and methods.  This makes them like instance methods of their own special class,  companion type, or type object, in place of operation on an instance of the class itself.

Class level method can be called without needing an instance of the class. So no need to have an object reference such as  Jack() or Jill().


class Person:
counter  = 0 # class level variable

    def __init__(self, name):
         self.name = name

    def print_name(self):
         print("person: ", self.name)
@classmethod
    def count(cls):
        print("number of person objects: ", cls.counter)

and now kotlin:


class Person(val name: String){
init { counter++}
   fun printName() = println("Person: $name")

    companion object{
         var counter  = 0
         fun count() = println("There are $counter person objects")

    }

}

The python Person.count() method call be called on the class name, but the print_name method requires instancing a Person object.

}}} Person.count()
0
}}} jack = Person("jack")
}}} jack.print_name()
"jack"

Kotlin Companion Object Access

A kotlin companion object allows access in the basically the same way as Python Class object, but with one twist.

Person.counter‘ will provide the value of counter just as with Python.  An inside the class can be accessed simple as ‘counter‘, but code given a reference to an object, has no access to the companion object by default.  This means

fred = Person("fred")
Person.count()
There are 1 person objects
fred.counter
error: unresolved reference: counter

To allow access to the companion object from an instanced object with code external to the class, a reference to the object is required.  Two alternative solutions are provided below


class Person(val name: String){
    init { _counter++}
    fun printName() = println("Person: $name")
    companion object CompanionName{
       var _counter = 0
       fun count() = println("There are $counter person objects")
    }
val myCompanion = CompanionName  // solution 1
val counter get() = counter  // solution 2
}

This code allows access from an instance of Person through myCompanion. Note: the companion can be named  ‘Companion‘ (and if given no name it will be called ‘Companion’) and the name ‘companion‘ is valid in place of ‘myCompaionaon‘ also, the example uses other names since these can have the appearance of being reserved words when in fact they would be just valid names.

The second solution allows access through ‘counter‘ . Note in this case, only get access is provided, but if desired a var with a setter is also valid.  Also note, although the companion name was changed to ‘_counter‘, the same name can be used in both scopes, it just means ‘CompanionName.counter‘ or ‘Person.counter‘ would the have to be used inside the class. Keeping the differentiated names would be preferable.

2 thoughts on “Class Level Objects, Companion Objects & Instances”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s