Method receivers and class methods: who gets bound?

Read what a call hands a method automatically, an object, the class the call went through, or nothing, in Java and Python. Methods and type contracts · Object-oriented programming.

A dashboard keeps a Meter class. One line calls meter.reading() on a particular meter, another calls Meter.unit() on the class itself, and a third calls FastMeter.from_label("fast") on a subclass. All three look like an ordinary method call, yet the language quietly hands each method a different first thing: an object, a class, or nothing. The method's kind decides which, and that decides what its body can reach.

Method dispatch showed which body runs. This lesson asks only what is bound before the first argument you write.

An object call and a class call

An instance method works on one object's state, so the call hands it that object, the one the call is made on, called the receiver. Java supplies it as the implicit this. Python shows it as the first parameter, conventionally self, filled in when you reach the method through the object. Nothing is copied: self is the very object meter refers to.

A method that needs no object's state can be declared static in Java or @staticmethod in Python. The call then hands over nothing: no this, no self, no cls.

final class Meter {
    private final String label;
    Meter(String label) { this.label = label; }

    String reading() { return label; }     // this is the receiver
    static String unit() { return "ms"; }  // no this exists here
}

Meter meter = new Meter("queue");
meter.reading();   // "queue"
Meter.unit();      // "ms"
class Meter:
    def __init__(self, label):
        self.label = label

    def reading(self):              # self is the receiver
        return self.label

    @staticmethod
    def unit():                     # nothing is bound
        return "ms"

meter = Meter("queue")
print(meter.reading())   # queue
print(Meter.unit())      # ms
print(meter.unit())      # ms: reaching it through meter still binds nothing

Reading label inside unit is a compile error in Java, because there is no this. In Python self is an undefined name there, and the call raises NameError. The static body can use only the arguments you pass it.

Python: what an object call and a class call hand over

Read the steps as text

  1. Meter inherits from object. Meter declares reading and unit (a staticmethod). object defines none of these names of its own.
  2. The call print(meter.reading()) starts. The variable meter holds object M1, an instance of Meter. Python looks the name reading up on that object.
  3. Python starts at type(meter), the class Meter, and reads the order from its __mro__: Meter and object. Meter implements reading, so the search stops there.
  4. Reading meter.reading binds the function found in Meter to object M1, so self will be M1, not Meter.
  5. The body of Meter.reading runs with self set to M1, the same object the call started on.
  6. The body of Meter returns queue, and print shows it.
  7. The call print(Meter.unit()) starts. Meter is a class, not an object, so no object takes part in unit. Python looks the name unit up on that class.
  8. To find unit, Python starts at the class the call went through, Meter, and reads its __mro__: Meter and object. Meter implements unit, so the search stops there.
  9. Meter declares unit as a staticmethod, so reading Meter.unit binds nothing: the function gets no self and no cls.
  10. The body of Meter.unit runs with no self and no cls, so it can reach only its own arguments.
  11. The body of Meter returns ms, and print shows it.

A classmethod gets the class the call went through

Python adds a third kind of method. A method marked @classmethod is handed a class, conventionally cls, and it is the class named in the call, not the class where the code was written. That makes it the natural home for an alternate constructor, because cls(...) builds whatever class the call went through. The example below only reports cls.__name__, so the one thing on show is which class arrived.

class Meter:
    @classmethod
    def from_label(cls, label):
        return "made " + cls.__name__

class FastMeter(Meter):
    pass

print(Meter.from_label("queue"))      # made Meter
print(FastMeter.from_label("fast"))   # made FastMeter

The search for from_label climbs from FastMeter to Meter along the class's __mro__, as dispatch taught. Binding is a separate step: whatever class holds the code, cls is the class the call went through. So FastMeter does not need its own from_label to report FastMeter, and a call through a class involves no object at all.

Python: a classmethod gets the class the call went through

Read the steps as text

  1. FastMeter inherits from Meter, which inherits from object. Meter declares from_label (a classmethod). FastMeter and object define none of these names of their own.
  2. The call print(Meter.from_label("queue")) starts. Meter is a class, not an object, so no object takes part in from_label. Python looks the name from_label up on that class.
  3. To find from_label, Python starts at the class the call went through, Meter, and reads its __mro__: Meter and object. Meter implements from_label, so the search stops there.
  4. Meter declares from_label as a classmethod, so reading Meter.from_label binds the class Meter as cls.
  5. The body of Meter.from_label runs with cls set to Meter, so it can reach that class but no object.
  6. The body of Meter returns made Meter, and print shows it.
  7. The call print(FastMeter.from_label("fast")) starts. FastMeter is a class too, so again no object takes part in from_label. Python reads the name off that class.
  8. This time the search for from_label starts at FastMeter, the class in front of the dot, and its __mro__ is FastMeter, Meter and object. FastMeter has no from_label, so the search moves on to the next class in the MRO.
  9. Meter again implements from_label, so the search stops there.
  10. Meter declares from_label as a classmethod, but reading FastMeter.from_label binds FastMeter, the class the call went through, as cls, not Meter.
  11. The body of Meter.from_label runs with cls set to FastMeter, so it can reach that class but no object.
  12. The body of Meter returns made FastMeter, and print shows it.

Java has no invoking class to hand over

Java has no counterpart to @classmethod. A static factory is declared once and inherited, and a call through a subclass name finds it, but the body is not told which name was used.

class Meter {
    static Meter make() { return new Meter("made"); }
    // constructor and fields omitted
}
class FastMeter extends Meter { /* ... */ }

Meter made = FastMeter.make();
System.out.println(made.getClass().getSimpleName());   // Meter
System.out.println(made instanceof FastMeter);         // false

The call compiled, ran Meter's code, and returned a Meter. If the factory must make a FastMeter, FastMeter needs its own make, or the factory must be told which class to build.

Java: a static method is told nothing about the name used

Read the steps as text

  1. FastMeter extends Meter. Meter declares reading(), unit() (a static method) and make() (a static method). FastMeter defines none of these names of its own.
  2. The call m.reading() starts. The variable m is typed Meter and holds object M1, a Meter. The signature reading() is already selected.
  3. The search for reading() starts at the receiver's own class, Meter, which is also the declared type this time. Meter implements reading(), so the search stops there.
  4. The body of Meter.reading() runs with this set to M1, the same object the call started on. The body of Meter prints queue, and the call is finished.
  5. The call Meter.unit() starts. Meter is a class name, not an object, so there is no receiver object. The signature to find is unit().
  6. The call names Meter in front of the method, so the search for unit() starts at Meter. Meter implements unit(), so the search stops there.
  7. The body of Meter.unit() runs with no this, because a static method has no receiver object. The body of Meter prints ms, and the call is finished.
  8. The call FastMeter.make() starts. FastMeter is a class name, not an object, so there is no receiver object. The signature to find is make().
  9. The call names FastMeter in front of the method, so the search for make() starts at FastMeter. FastMeter has no make(), so the search goes up to its parent. Meter implements make(), so the search stops there.
  10. The body of Meter.make() runs with no this, because a static method has no receiver object.
  11. The body of Meter prints made Meter, and the call is finished.

Choosing the kind

Ask what the body must touch. If it reads or changes one object's state, write an instance method. If it is an alternate constructor that should respect subclasses, such as building from a label or a default, write a classmethod in Python and give each Java subclass its own static factory or constructor. If it needs neither an object nor a class, such as converting a unit or checking a format, make it static. A static helper is also the easiest to test, because nothing has to be built first. A static method that starts needing self is the wrong kind.

Recap

  • An instance call binds the object as this in Java and self in Python, with no copy.
  • A Java static method and a Python staticmethod bind nothing, even when an object exists.
  • A Python classmethod binds the class the call went through as cls; a Java static method is told nothing about the name used.
Go deeper

A Python function stored on a class becomes a bound method when it is retrieved through an instance, because functions are descriptors. staticmethod returns the plain function, so nothing is bound, and classmethod binds the class. A Java subclass that declares a static method with the same signature hides the inherited one for calls through its own name; that is not an override, because no object receiver chooses between them.

Continue to generic type contracts when one class should be reusable across a family of element types. Revisit method dispatch when the next question is which implementation a runtime object supplies. Practise receiver state in Linked List Fundamentals, where each node operation still acts through a particular object.