0

I confused myself typing this title, so please let me know if it requires some clarification. I'm working my way through Python Crash Course, by Eric Matthes and one of the exercises brought up a question that I haven't been able to find an answer for. Also, apparently, I can't figure out how to properly format the following code, sorry. The code for the exercise in question follows:

class User():
    """Store info about a user profile"""

    def __init__(self, first_name, last_name, email, username):
        """Init common profile info"""
        self.first_name = first_name
        self.last_name = last_name
        self.email = email
        self.username = username
        self.login_attempts = 0

    def describe_user(self):
        """Print users info"""
        print("User info summary: \n\t" + 
            self.first_name.title() + " " +
            self.last_name.title() + "\n\t" +
            self.username + "\n\t" +
            self.email
            )

    def greet_user(self):
        """Print a short greeting"""
        print("Welcome, " + self.first_name.title() + "!")

    def increment_login_attempts(self):
        """Increments the number of login attempts by 1"""
        self.login_attempts += 1

    def reset_login_attempts(self):
        """Rest value of login_attempts to 0"""
        self.login_attempts = 0

class Privileges():
    """Represents a list of privileges"""

    def __init__(self, privileges=[]):
        """Initialize attributes for Privileges"""
        self.privileges = privileges

    def show_privileges(self):
        """Print the list of privileges for this admin"""
        for item in self.privileges:
            print(item.title())

class Admin(User):
    """Model an administrative user"""

    def __init__(self, first_name, last_name, username, email):
        """Initialize attributes of Admin"""
        super().__init__(first_name, last_name, username, email)
        self.privileges = Privileges(['can add user', 'can block user'])

bbob = Admin('billy', 'bob', 'bbob', 'blah')
bbob.privileges.show_privileges()

OK so, I'd like to be able to provide the list of privileges to the instance of class Privileges that's being created inside of Admin, at the time I'm creating the instance of Admin. Does this all make sense? Every time I type it out I get confused. It seems to me, that in a potential real-world application, it would be unreasonable to provide that list in the class definition, as it is in Admin's __init__ method. It should be able to be passed to the class as an argument or something, right?

Thanks, all, for your time reading this. Please let me know if this didn't make any sense at all.

PM 2Ring
  • 54,345
  • 6
  • 82
  • 182
CCramer
  • 28
  • 4
  • I've fixed the code formatting, please let me know if it's not right. – PM 2Ring Jul 21 '17 at 02:56
  • 2
    Doing `def __init__(self, privileges=[]):` is _not_ a good idea. Default args are evaluated when the function or method is defined, not when it's called, so every `Privileges` instance that's created without an explicit `privileges` list will share that default list. See https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument The usual way to deal with this is to give the arg a default value of `None`, and in the body of the function do something like `if privileges is None: privileges = []` – PM 2Ring Jul 21 '17 at 03:03
  • That looks much nicer, thanks! – CCramer Jul 21 '17 at 03:31
  • Also, thanks for your tip about the list as a parameter. – CCramer Jul 21 '17 at 03:46

1 Answers1

2

It is convenient and makes sense to be able to configure contained objects. It might not win you purity points, and it entangles the classes, but it's convenient, and at least some entanglement is natural and not necessarily to be avoided. Just add another parameter:

class Admin(User):

     def __init__(self, first_name, last_name, username, email,
                  privileges):
         """Initialize attributes of Admin"""
         super().__init__(first_name, last_name, username, email)
         self.privileges = Privileges(privileges)

 bbob = Admin('billy', 'bob', 'bbob', 'blah',
              ['can add user', 'can block user'])

Or, perhaps better, if Admins have some default privileges, is just to let the developer optionally add them:

class Admin(User):

     def __init__(self, first_name, last_name, username, email,
                  extra_privileges=None):
         """Initialize attributes of Admin"""
         super().__init__(first_name, last_name, username, email)
         privileges = ['can add user', 'can block user']
         privileges += extra_privileges or []
         self.privileges = Privileges(privileges)

 bbob = Admin('billy', 'bob', 'bbob', 'blah')
 ssara = Admin('sara', 'smith', 'ssara', 'blatz', ['can read emails'])

One final note: Parameter lists should not contain mutable structures like lists if you can at all avoid that. Subtle but important side-effects can make them do things you really don't expect. This is basically Python gotcha #1. Instead, if you have a null default, use None:

 def __init__(self, privileges=None):
     """Initialize attributes for Privileges"""
     self.privileges = privileges or []

This way, if __init__ is called with no privileges, self.privileges gets the empty list as before. Or if they provide a list, that is taken. But without the nasty side effects.

Jonathan Eunice
  • 21,653
  • 6
  • 75
  • 77
  • I'm not familiar with `None` yet, is that basically `Null`? I've seen `Null` used in other languages that I've played with. So this line `self.privileges = privileges or []` basically says that if `privileges` is empty(false) then `self.privileges` gets an empty list, is that right? And using `None` as the default parameter prevents the side-effects inherent in giving it an empty list? Also, adding a new parameter like in your first example seems so obvious, in hindsight. Thanks – CCramer Jul 21 '17 at 03:35
  • @CCramer Yes, that's all correct. `None` is Python's Null. It's a singleton, meaning that there's only one `None` object in a Python program, so you can safely & efficiently do stuff like `if x is None`. – PM 2Ring Jul 21 '17 at 03:52
  • Wonderful! Thanks so much to both of you for all your help! – CCramer Jul 21 '17 at 04:01