1. Object initialization

1.1. __call__()

  • __call__() method invokes the following:

    • __new__()

    • __init__()

Listing 415. Intuition definition of __new__() and __init__()
class Iris:
    def __call__(cls):
        iris = Iris.__new__(cls)
        iris.__init__()
class Astronaut:
    pass


twardowski = Astronaut      # Creates alias to class (not an instance)
twardowski()                # Creates instance by calling ``.__call__()``

twardowski = Astronaut()    # Creates instance by calling ``.__call__()``

1.2. __new__()

  • the constructor

  • solely for creating the object

  • cls as it's first parameter

  • when calling __new__() you actually don't have an instance yet, therefore no self exists at that moment

class Iris:
    def __new__(cls):
        print("Iris.__new__() called")
        return super().__new__(cls)

Iris()
# Iris.__new__() called

1.3. __init__()

  • the initializer

  • for initializing object with data

  • self as it's first parameter

  • __init__() is called after __new__() and the instance is in place, so you can use self with it

  • it's purpose is just to alter the fresh state of the newly created instance

class Iris:
    def __init__(self):
        print("Iris.__init__() called")

Iris()
# Iris.__init__() called

1.4. Example usage

class Iris:
    def __call__(cls):
        iris = Iris.__new__(cls)
        iris.__init__()

    def __new__(cls):
        print("Iris.__new__() called")
        return super().__new__(cls)

    def __init__(self):
        print("Iris.__init__() called")

Iris()
# Iris.__new__() called
# Iris.__init__() called

1.5. Returning values

1.5.1. Missing return from constructor

class Iris:
    def __new__(cls):
        print("Iris.__new__() called")

    def __init__(self):
        print("Iris.__init__() called")  # -> is actually never called

Iris()
# Iris.__new__() called
# None

The instantiation is evaluated to None since we don't return anything from the constructor.

1.5.2. Return invalid from constructor

class Iris:
    def __new__(cls):
        print("Iris.__new__() called")
        return 29

Iris()
# Iris.__new__() called
# 29

1.5.3. Return invalid from initializer

class Iris:
    def __init__(self):
        print("Iris.__new__() called")
        return 33

Iris()
# TypeError: __init__ should return None

1.6. Why?

  • Factory method

  • Could be used to implement Singleton

class PDF:
    pass

class Docx:
    pass

class Document:
    def __call__(self, *args, **kwargs):
        Document.__new__(*args, **kwargs)

    def __new__(cls, *args, **kwargs):
        filename, extension = args[0].split('.')

        if extension == 'pdf':
            return PDF()
        elif extension == 'docx':
            return Docx()


file1 = Document('myfile.pdf')
# <__main__.PDF object at 0x1092460f0>

file2 = Document('myfile.docx')
# <__main__.DOCX object at 0x107a6c160>

1.7. Initial arguments mutability and shared state

1.7.1. Bad

Listing 416. Initial arguments mutability and shared state
class Astronaut:
    def __init__(self, name, locations=[]):
        self.name = name
        self.locations = locations


watney = Astronaut('Mark Watney')
watney.locations.append('Johnson Space Center')
print(watney.addresses)
# ['Johnson Space Center']

twardowski = Astronaut('Jan Twardowski')
print(twardowski.locations)
# ['Johnson Space Center']

1.7.2. Good

Listing 417. Initial arguments mutability and shared state
class Contact:
    def __init__(self, name, locations=()):
        self.name = name
        self.locations = list(locations)


watney = Astronaut('Mark Watney')
watney.locations.append('Johnson Space Center')
print(watney.locations)
# ['Johnson Space Center']

twardowski = Astronaut('Jan Twardowski')
print(twardowski.locations)
# []

1.8. Do not run methods in __init__()

  • It is better when user can choose a moment when call .connect() method

Listing 418. Let user to call method
class Server:

    def __init__(self, host, username, password=None):
        self.host = host
        self.username = username
        self.password = password
        self.connect()    # Better ask user to ``connect()`` explicitly

    def connect(self):
        print(f'Logging to {self.host} using: {self.username}:{self.password}')


localhost = Server(
    host='localhost',
    username='admin',
    password='admin'
)

# This is better
localhost.connect()