4

I have a series of subclasses which each have their own functions for accessing data. Sometimes, it can be more complex, but by default it calls that method (see example below). The problem arises when I try to simply call the defined function and it passes self as an argument. The data access function signatures aren't defined for that and are used other ways, so it doesn't make sense to add self as an argument. How can I accomplish this design with the right implementation?

# data.py

def get_class_a_records(connection, date):
    pass


def get_class_b_records(connection, date):
    pass


class Parent:
    def get_records(self, connection, **kwargs):
        self.data_method(connection=connection, **kwargs)


class A(Parent):
    data_method = get_class_a_records


class B(Parent):
    data_method = get_class_b_records


class C(Parent):
    def get_records(self, connection, **kwargs):
        # logic for custom code/post-processing
        pass

Now if we instantiate one of these classes, we run into an issue:

a = A()
a.get_records(connection=None, date='test')
TypeError: get_class_a_records() got multiple values for argument 'connection'

This is because the call self.data_method actually passes self as an argument and we see get_class_a_records clearly doesn't have self as an argument.

TomNash
  • 3,147
  • 2
  • 21
  • 57
  • You can annotate with @staticmethod if you don't want self as a parameter, then you just call the method straight from the class - no need to instantiate the object: IE`A.get_records(...)` instead of `a=A(); a.get_records(...)` – AlpacaJones Sep 15 '20 at 21:22

1 Answers1

5

You can call the first parameter of an instance method anything you want, but python will always insert a reference to the instance object in that position when the method is called. By assigning the get_class_a_records function to a class variable, congratulations, you've made it an instance method. More technically, python will make it an instance method when you call it.

The rules for class and static methods are different. For a class, the instance's class object is passed. And for a static method, nothing is added. That's the one you want. Just convert the function to a staticmethod and it will work.

def get_class_a_records(connection, date):
    print("class a records", connection)


def get_class_b_records(connection, date):
    pass


class Parent:
    def get_records(self, connection, **kwargs):
        self.data_method(connection=connection, **kwargs)


class A(Parent):
    data_method = staticmethod(get_class_a_records)


class B(Parent):
    data_method = staticmethod(get_class_b_records)


class C(Parent):
    def get_records(self, connection, **kwargs):
        # logic for custom code/post-processing
        pass

A().data_method("connection", "date")
tdelaney
  • 73,364
  • 6
  • 83
  • 116
  • 1
    I know about the `@staticmethod` decorator and that's effectively what I wanted to do, but didn't think to use it like that. Nice one. – TomNash Sep 15 '20 at 21:26
  • @TomNash - the decorator syntax is really just a shortcut for calling the function and assigning its result to a class variable. When I get puzzled about a decorator, I'll do it manually like this to make sure I understand what's going on. – tdelaney Sep 15 '20 at 21:31
  • I wanted to see what `staticmethod` does. Unfortunately, it's a C function, so IntelliJ doesn't give me much. What it does do is tell me it's a class, not a global function. So then `data_method` ends up being assigned an instance of the `staticmethod` class that has taken the function to be called as a constructor arg. So that means it's a Function object, right? But then it should have a `__call__` method, shouldn't it? I don't see one in the stub class in IntelliJ. So I'm confused. – CryptoFool Sep 15 '20 at 21:50
  • 1
    @Steve you might find [this answer](https://stackoverflow.com/a/59654533/8468264) helpful. – d_kennetz Sep 15 '20 at 22:04
  • 1
    @d_kennetz, that's Exactly! what I was looking for. Fabulous. Thanks! – CryptoFool Sep 15 '20 at 22:06
  • 1
    Its implemented as `PyStaticMethod_Type` in [`funcobject.c`](https://github.com/python/cpython/blob/master/Objects/funcobject.c) but I think @d_kennetz linked answer is much better than digging through the source. – tdelaney Sep 15 '20 at 22:13