1

I have a Question class with a setter method as follows.

@tokens.setter
def tokens(self, param: list, use_type: bool = False) -> None:
    if not isinstance(param, list):
        raise TypeError('Question.tokens must be a list')
    if use_type:
        self._tokens = None # assign something
    else:
        self._tokens = None # assign something else

After instantiating an object of class Question, I am trying to set its' tokens as follows.

question = Question(query['id'])
question.tokens = tokens, True # tokens is a list of words

I want to pass tokens as param and True as use_type parameter for the tokens setter method. But I am getting a TypeError here as the first parameter param is getting a tuple as parameter.

How can I pass a value for the second parameter, use_type here? What is the correct syntax?

Wasi Ahmad
  • 35,739
  • 32
  • 114
  • 161
  • I do not understand your question. `param` is a tuple, so the `isinstance()` check fails. – Wasi Ahmad Aug 16 '19 at 01:30
  • my bad im reading the question too quickly. [this](https://stackoverflow.com/questions/18714262/property-setter-with-multiple-values) might help. Iirc the setter property only takes in 1 argument, so one way around it is to probably zip it together with your tokens – Axois Aug 16 '19 at 01:35

1 Answers1

2

Separating values by comma without enclosing then in square brackets - [] - in Python, actually creates a tuple, not a list.

In fact, due to dynamic typing in Python, it is not considered a good practice to check exactly for a "list", when any sequence, or even iterable, will work equally well.

The second thing is that properties are meant to enable a single argument passing. Only the param parameter in your function will be used - the second parameter, use_type will not be passed by the property mechanism.

That means you have to read your arguments explicitly inside your method code, and a little extra logic is needed so that use_type is actually optional.

Actually, if you want to parametrize the settings of tokens with more than an argument, my advice would be to, in this case, just use a plain setter method, instead of resorting to properties or other descriptors. This will cause less surprise for people viewing (or having to write) code that use this class and set .tokens:

class Question:
   ...
   def __init__(self, ...):

       self.token = ...   # Plain, unguarded attribute
       ..
   def set_token(self, value, use_type = None):
       ...


But if you really want a property that will accept a sequence, with the second value being optional. However, if you are really using typing annotations and enforcing then, you have to restrict your params to tuples (which fortunately is the data type created by Python when you just separate objects by , as is in your code)

class Question:
    ...
    @token.setter
    def token(self, params: typing.Tuple[typing.Any, typing.Optional[bool]):
         """params should be a 1-or-2 tuple, with the second item, if present
            a bool indicating "use_type" 
         """
         # The operation bellow will raise TypeError 
         # if params is not a tuple, so no need for the explicit check -
         # just let the error propagate
         value, use_type, *_ = params + (False,)  
         # if only one item is passed, "False" ends up in "use_type".
         # With 2 itens, the extra "False" is assigned to "_" indicating
         # it should be disregarded (it is a Python convention for '_' as a variable name)
         if use_type:
            ...

And with this your question.tokens = tokens, True expression will work, as will question.tokens = tokens,, but not question.tokens = tokens - as in this case, the value is not a tuple; (if it is a tuple it will be incorrectly used inside the method body as is) - I will say again that this is a strange pattern, and an explicit setter method should be preferred.

jsbueno
  • 99,910
  • 10
  • 151
  • 209