‘Only in Python’ Series (1): ClassMethod

‘Only in Python’ series talks about the constructs that are more specific to Python than other mainstream languages. Of course there are some overlaps with other language, so my choices would be language behavior/characteristics screams Python whenever I see it.


Python also access the class (not instance) by its own name (just like MATLAB) followed by dot operator. In C++, class-wise (not instance-wise) are always accessed through SRO, aka Scope Resolution Operator ::.

There isn’t really a concept for class method despite Python has a @classmethod decorator. The syntactic sugar merely for the use case where a static function happens to access to static members of the same class through its own class’s name.

Static methods and static data members are pretty much glorified namespaces (just like packages and modules in Python) that isn’t tied to any object instances. If a function wants to access the class-wise members (attributes/properties), simply call them by the name of the class instead of object instances of it.

Btw, C++ implicitly passes *this internally and not rely on the function prototype, aka first argument, to pass self around, so you don’t have this problem.

MATLAB does not have native support for static data member but it has static methods. (Btw, in MATLAB, attributes means the access modifiers to methods and properties block, not members of a class like in Python). It’s smart enough to figure out if a method is marked static so it’s exempt from receiving the object itself as the first argument.

Python is a little primative in this department. Calling a method from an object instances always imply passing self as the first argument in the call, whether you want it (instance methods) or not (static/class-wise methods).

If you call a method by the class name, no implict first argument was passed.


To address this mandatory self issue, Python simply use decorators (function wrappers) to wrestle the passed self to fit your purposes.

@staticmethod effectively tosses out the implied first argument (self) passed by method calls from object instances since it’s by definition not needed/wanted.

@classmethod effectively takes the implied first argument (self) and replace it with its class name, aka type(self) as cls so your function prototype expects the first argument. I said effectively because if you call the @classmethod-decorated method by class name, which self is not passed, the deocrator still produces the variable cls to match the first argument in your function prototype.

Here’s an example:

class A:
  a = 3
  def __init__(self):
    self.a = 42

  @classmethod
  def print_a_cls(cls):
    print(A.a)
    print(cls.a)
  # Both obj_a.print_a_cls() and A.print_a_cls() prints 3 twice
  # @classmethod basically swap the passed 'self' argument and replace the first arg
  # with type(self) which is the class's name A here

  @staticmethod
  def print_a_static():
    print(A.a)
  # Both obj_a.print_a_static() and A.print_a_static() prints 3 
  # @staticmethod basically absorbs and discard the first argument 'self' if passed

  def print_a_instance(self):
    print(self.a)

  def print_a_classwise():
    print(A.a)
  # obj_a.print_a_classwise()
  # "TypeError: A.print_a_classwise() takes 0 positional arguments but 1 was given"
  # A.print_a_classwise() prints 3

If you don’t use these declarators, methods without self as the first argument will work only if you call them by the class’s name. If you try to call it through an object instance, it’ll rightfully refuse to run because the call supplies self as an argument which your function isn’t ready to accept.

Loading

Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments