1

Let's say we have a simple python model class.

 class Library:

     def __init__():
         self.library_name = None
         self.books = []

We would use that by:

new_library = Library()
new_library.library_name = "Delhi International Library"

This would be an empty library with no books. Is this safe? I know you shouldn't use mutable types in the method signature due to when it is evaluated.

user7692855
  • 1,582
  • 5
  • 19
  • 39
  • 1
    `new_library.library_name = "Delhi International Library"` would be fine, I'm guessing `.name` is a typo? – Peter Mar 25 '20 at 17:17
  • thanks, I updated the typo. – user7692855 Mar 25 '20 at 17:23
  • 1
    The problem is when you use a mutable value as the *default* value for a parameter, not when you pass a mutable value as an argument. – chepner Mar 25 '20 at 17:29
  • Perfect, I thought it was OK. So setting ```self.books = []``` as part of the __init__ but not as an argument is OK. – user7692855 Mar 25 '20 at 17:32
  • 1
    Correct. Every instance of `Library` will then have its *own* (initially empty) list of books, rather than sharing a reference to a single central book list. – chepner Mar 25 '20 at 17:34

2 Answers2

4

The idiomatic way to use a mutable type for a default value is to use a None default value:

class Library:
     def __init__(self, name, books=None):
         self.library_name = name
         self.books = [] if books is None else books


new_library = Library("Delhi International Library")

Here name is a required param because IMHO a library should have a name, but you could also use a default if you later will have to create a library with no parameters.

And books is optional. If it is not given, you will get en empty library, and if you pass it on first time, it will not become the default because None is not mutable...

chepner
  • 497,756
  • 71
  • 530
  • 681
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • How about init(self, name, books=[ ]) and self.books=books ? – Mercury Mar 25 '20 at 17:32
  • 1
    @Mercury No. See https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument. – chepner Mar 25 '20 at 17:33
  • are there any performance benefits to doing it that way then not having it in the initilizer and instead doing it the way I did in. In reality there are 20 or so variables and I find it get's messy? – user7692855 Mar 25 '20 at 17:35
  • There are neither benefit nor drawback on a performance point of view. What matters is what is more readable or easy to use, and it really depends on the use case. Sometimes you will declare default values, sometimes you will declare no parameters... – Serge Ballesta Mar 25 '20 at 17:40
1

you can use:

class Library:
    def __init__(self, library_name, books=None):
        self.library_name = library_name
        self.books = books or []

and then instantiate like:

new_library = Library("Delhi International Library")
kederrac
  • 16,819
  • 6
  • 32
  • 55
  • 4
    Using a list as a default value for an argument isn't a good idea, see https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument – yatu Mar 25 '20 at 17:22
  • Thanks - are there any performance benefits to doing it that way then not having it in the initilizer and instead doing it the way I did in. In reality there are 20 or so variables and I find it get's messy. – user7692855 Mar 25 '20 at 17:22
  • 1
    It's rarely necessary to make a copy of whatever list you do pass; it's often a temporary object that would be garbage-collected if `self.book` didn't save a referent to it. Use `books=None`, then set `self.books = [] if books is None else book` (or an equivalent) – chepner Mar 25 '20 at 17:27
  • 1
    That is, if someone creates a `Library` and doesn't want that object to make changes to its existing list, the *caller* passes a copy as an argument, rather than assuming `Library` will make its own copy. – chepner Mar 25 '20 at 17:32