Search code examples
python-3.xpython-decorators

How to create list of random int and set it as value using @property decorator in Python?


I am fairly new to the concept of decorators.

I am trying to implement a function which creates a list of random integers and set it to the group_list variable in the __init__ method.

And the second method takes an input from the user and set it to the value variable in the __init__ method.

class Order(object):

def __init__(self, group_list=None, value=None):
    self.group_list=groupList
    self.value=number
    print(self.groupList)


@property
def groupList(self):
    return self._groupList

@groupList.setter
def _groupList(self):
    global list_val
    for _ in range(10):
        currentelement= randint(1,15)
        list_val.append(currentelement)
    self._groupList=list(set(list_val))

@property
def number(self):
    return self._number

@number.setter
def number(self):
    val=input('Enter any number from the list: ')
    self._number=val

What am I doing wrong here ? Any help will be highly appreciated!


Solution

  • I know what it's like to try to accomplish a task on limited time, and have to wait for answers to questions, so I took really big swing at your problem while I had some down time today. I hope overall I managed to answer your question (What am I doing wrong here ?).

    Forgive me for not detailing everything out in sections bouncing in and out of code blocks, I was in a bit of a hurry. In addition, with all commenting done within the code, you can copy/paste this into a file, and run it. This assumes python3+

    from random import randint
    
    class Order(object):
    
        # Notice that group_list is used several places, so in the parameters I'm changing it to g_list just to make
        # it obvious which variable or parameter is being accessed
        def __init__(self, g_list=None, value=None):
            # Whenever calling a method inside of class, you have to reference the class
            # itself as part of the call. So in this case the call should be `self.groupList`
            #
            # Pep8 calls for methods to be lower case rather than hump case, and when using python
            # getter/setter (properties) you usually want to set a property with a different name (implied private) than
            # the defined property (getter/setter).
            #
            # Notice a few things in the new call. The property name is made private, and the methods that are decorated
            # with @property and @method.setter are NOT private or protected (see below). Also note that since
            # We're calling the group_list property below we need to set a value (notice the list_val property
            # in the setter below).
            #
            #
            # NOTE: If we were to set the __group_list directly, we would not allow any of the code in the setter
            # method to execute, possibly bypassing any data checking we wanted to do.
            #
            # self.group_list=groupList becomes:
            self.__group_list = []  # defines the property we're going to use
            self.group_list = g_list # calls the setter passing in the g_list parameter
    
            # Again since you are accessing your properties with methods, make the value private or at least protected
            # keep in mind that python's idea of private and protected methods is strictly a suggestion. There really
            # isn't a private/protected method in python.
            #
            # Since at this point we're assuming a "value" was passed into the class, we can't call the
            # setter, or it will ask for a number right away. If that's the goal, there are better ways to handle it.
            # with the information provided in the question, it's best to just set this value directly. Of course you
            # should verify the data first.
            #
            # self.__value=self.number
            self.__value = value
    
            # removed so not to cause confusion with example code outside object
            # print(self.group_list)
    
    
        @property
        # Here the method is named according to what we want to access in our code. Also properly
        # named using word_word format. The property decorator tells python to treat this as a
        # getter, and return a value. In this case we're returning the value of the property we created in init.
        #
        # def groupList(self):
        def group_list(self):
            # here we change the name of the property to be returned to match the property we defined in the init
            # return self._groupList
            return self.__group_list
    
        # The name of the property setter decorated must match the name of a method defined by the property
        # decorator prior to the setter being defined.
        #
        # @groupList.setter
        @group_list.setter
        # The method name must match the method name used where you defined the property as mentioned above. Also
        # notice that I added a parameter so that the list_val can be passed into the method.
        # def _groupList(self):
        #
        # Please note that if you're not going to need to use the parameter on a "setter" you're better off
        # not using the setter decorator and just renaming the method to something logical. In this case, something
        # like "generate_list" would be perfect.
        def group_list(self, list_val):
            # Global values in python are global only to the module. Your code as I was typing this did not include any
            # declaration of the list_val, and even if you have one defined, you should still pass in the value
            # and more than likely do away with a global defined one.
            #
            # There is a place for module level variables of course, but they should be the exception not the rule when
            # working with objects. Your code is much clearer, and is likely no less efficient by passing around references
            # of lists.
            #
            # global list_val
            if list_val is None:
                for _ in range(10):
                    # You can do all of this in a single line. Notice though that I changed
                    # the property being set.
                    # currentelement= randint(1,15)
                    # list_val.append(currentelement)
                    # self._groupList=list(set(list_val))
                    self.__group_list.append(randint(1, 15))
            elif isinstance(list_val, list):
                self.__group_list = list_val
    
        @property
        def number(self):
            # here again, we want to access the property we are setting.
            #
            # return self._number
            return self.__value
    
        # Setters require a signature of (self, value). You have a signature of (self) here. A setter is expecting
        # that you will want to pass a value in to be set.
        #
        # Without knowing all the details involved I have to say that this code produces smell. It would likely be
        # better to ask for the user input someplace else in your code and pass that value in to be stored or do something
        # with it. Having a module or class devoted to user interaction (a View) is common. But having it mixed with
        # application logic is usually bad.
        #
        # assuming you want to keep this request within the class here, this would be best renamed and not used
        # as a setter.
        #
        # Again notice the change from _number to __value.
        #
        # Also note I added a conversation to integer on the user input. This assumes the user will always chose an
        # integer. Of course checks should be put in place to validate all user input.
        #
        # @number.setter
        # def number(self):
        #     val=input('Enter any number from the list: ')
        #     self._number=val
        def ask_user_for_number(self):
            val=input('Enter any number from the list: ')
            self.__value=int(val)
    
    
    # now I can use the class as such:
    
    # If I pass no data in at instantiation a new list will be
    print("Example with random list")
    obj = Order()
    obj.ask_user_for_number()
    print("List of numbers: {}".format(obj.group_list))
    print("User selected: {}".format(obj.number))
    in_list = obj.number in obj.group_list
    print(in_list)
    
    # if I want to provide a list
    print("-" * 10)
    print("Example with fixed list")
    my_list = [1,2,3,4,5,6,7,8,9,10]
    my_obj = Order(g_list=my_list)
    my_obj.ask_user_for_number()
    print("List of numbers: {}".format(my_obj.group_list))
    print("User selected: {}".format(my_obj.number))
    in_my_list = my_obj.number in my_obj.group_list
    print(in_my_list)