Reputation: 75
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
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
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