ImLearning
ImLearning

Reputation: 75

Python - why I must use self in this method?

class ValidateStrategy(abc.ABC):
    def validate(self, input_string):
        pass


class IntValidateStrategy(ValidateStrategy):
    def validate(self, input_string):
        try:
            int(input_string)
            return True
        except ValueError:
            return False


def string_validate(input_string, validate_strategy):
    return validate_strategy.validate(input_string)

string_validate("22", IntValidateStrategy)

Why am I getting an error because the self argument is missing?

> Traceback (most recent call last):   File "F:\Pobrane\drive\test.py",
> line 11, in <module>
>     print(el + ": " + str(validator.string_validate(el, validator.IntValidateStrategy)))   File
> "F:\Pobrane\drive\validator.py", line 34, in string_validate
>     return validate_strategy.validate(input_string) TypeError: IntValidateStrategy.validate() missing 1 required positional argument:
> 'input_string'

Upvotes: 2

Views: 167

Answers (2)

Mad Physicist
Mad Physicist

Reputation: 114250

Functions are non-data descriptors in Python.

When you access a function through a class, you get the function exactly as you defined it. As a concrete example, IntValidateStrategy.validate is a function that accepts two named positional arguments, self, and string.

Descriptor magic is invoked when you access a function through an instance of a class. Descriptor protocol means calling validate.__get__(instance). For regular functions, this returns a bound method object, which has a parameter list that does not explicitly include the first positional parameter of the function you defined, self. A concrete example would be if you instantiated your strategy:

IntValidateStrategy().validate('234')

The simplest fix for your example would be to instantiate the class you are passing in:

string_validate("22", IntValidateStrategy())

A better solution might be too allow string_validate to accept any callable as a validator:

def string_validate(input_string, validate_strategy):
    return validate_strategy(input_string)

string_validate("22", IntValidateStrategy().validate)

At this point, you don't need to even have string_validate. In fact you can simplify your interface and make it more flexible by replacing the name validate with __call__. You'll be able to use functions for stateless validators, and full classes if you need state:

class ValidateStrategy(abc.ABC):
    def __call__(self, input_string):
        pass

def stateless_int_validate_strategy(input_sting):
    try:
        int(input_string)
        return True
    except ValueError:
        return False

class StatefulIntValidateStrategy(ValidateStrategy):
    def __call__(self, input_string):
        return stateless_int_validate_strategy(input_sting)

stateless_int_validate_strategy("22")
validator = StatefulIntValidateStrategy()
validator("23")

Upvotes: 2

RossM
RossM

Reputation: 438

Short answer, you don't need to.

Whenever you call a method of a class, unless it is decorated as a @classmethod or @staticmethod the first argument will be the instance of the class the method was called from.

To explain:

If I call the method x as Class.x(), then no first argument will be automatically passed.

If I call the method x as instance.x() the first argument will be the instance.

You are passing IntValidateStrategy to your function. This means that the first argument in the function will be the class IntValidateStrategy.

Since you aren't accessing any variables within the class, you can add the decorator @staticmethod above your method definition, and remove the self argument since nothing is being passed automatically.

This means when you call IntValidateStrategy.validate(...), nothing is passed automatically and you are free to use your function.

Code:

class IntValidateStrategy(ValidateStrategy):
    @staticmethod
    def validate(input_string):
        try:
            int(input_string)
            return True
        except ValueError:
            return False

Upvotes: 4

Related Questions