6.12. Constructor¶
6.12.2. Example¶
class Astronaut:
def __new__(cls):
return super().__new__(cls)
def __init__(self):
pass
Astronaut()
# Astronaut.__new__() called
# Astronaut.__init__() called
6.12.3. New Method¶
the constructor
solely for creating the object
cls
as it's first parameterwhen calling
__new__()
you actually don't have an instance yet, therefore noself
exists at that moment
class Astronaut:
def __new__(cls):
print(f'Astronaut.__new__() called')
return super().__new__(cls)
Astronaut()
# Astronaut.__new__() called
6.12.4. Init Method¶
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 useself
with itit's purpose is just to alter the fresh state of the newly created instance
class Astronaut:
def __init__(self):
print('Astronaut.__init__() called')
Astronaut()
# Astronaut.__init__() called
6.12.5. Return¶
class Astronaut:
def __new__(cls):
print('Astronaut.__new__() called')
return super().__new__(cls)
def __init__(self):
print('Astronaut.__init__() called')
Astronaut()
# Astronaut.__new__() called
# Astronaut.__init__() called
Missing return
from constructor. The instantiation is evaluated to None
since we don't return anything from the constructor:
class Astronaut:
def __new__(cls):
print('Astronaut.__new__() called')
def __init__(self):
print('Astronaut.__init__() called') # -> is actually never called
Astronaut()
# Astronaut.__new__() called
Return invalid from constructor:
class Astronaut:
def __new__(cls):
print('Astronaut.__new__() called')
return 1337
Astronaut()
# Astronaut.__new__() called
# 1337
Return invalid from initializer:
class Astronaut:
def __init__(self):
print('Astronaut.__new__() called')
return 1337
Astronaut()
# Traceback (most recent call last):
# TypeError: __init__() should return None, not 'int'
6.12.6. Use Cases¶
Factory method
Could be used to implement Singleton
class PDF:
pass
class Docx:
pass
class Document:
def __new__(cls, *args, **kwargs):
filename, extension = args[0].split('.')
if extension == 'pdf':
return PDF()
elif extension == 'docx':
return Docx()
file1 = Document('myfile.pdf')
file2 = Document('myfile.docx')
print(file1)
# <__main__.PDF object at 0x10f89afa0>
print(file2)
# <__main__.Docx object at 0x10f6fe9a0>
DATA = [(5.8, 2.7, 5.1, 1.9, 'virginica'),
(5.1, 3.5, 1.4, 0.2, 'setosa'),
(5.7, 2.8, 4.1, 1.3, 'versicolor'),
(6.3, 2.9, 5.6, 1.8, 'virginica'),
(6.4, 3.2, 4.5, 1.5, 'versicolor'),
(4.7, 3.2, 1.3, 0.2, 'setosa')]
class Iris:
def __new__(cls, *args, **kwargs):
*measurements, species = args
if species == 'setosa':
cls = Setosa
elif species == 'versicolor':
cls = Versicolor
elif species == 'virginica':
cls = Virginica
else:
raise TypeError
return super().__new__(cls)
def __init__(self, sepal_length, sepal_width,
petal_length, petal_width, species):
self.sepal_length = sepal_length
self.sepal_width = sepal_width
self.petal_length = petal_length
self.petal_width = petal_width
def __repr__(self):
cls = self.__class__.__name__
args = tuple(self.__dict__.values())
return f'\n{cls}{args}'
class Setosa(Iris):
pass
class Virginica(Iris):
pass
class Versicolor(Iris):
pass
result = [Iris(*row) for row in DATA]
result
# [Virginica(5.8, 2.7, 5.1, 1.9),
# Setosa(5.1, 3.5, 1.4, 0.2),
# Versicolor(5.7, 2.8, 4.1, 1.3),
# Virginica(6.3, 2.9, 5.6, 1.8),
# Versicolor(6.4, 3.2, 4.5, 1.5),
# Setosa(4.7, 3.2, 1.3, 0.2)]
6.12.7. Do not trigger methods for user¶
It is better when user can choose a moment when call
.connect()
method
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}')
connection = Server(
host='example.com',
username='myusername',
password='mypassword')
Let user to call method:
class Server:
def __init__(self, host, username, password=None):
self.host = host
self.username = username
self.password = password
def connect(self):
print(f'Logging to {self.host} using: {self.username}:{self.password}')
connection = Server(
host='example.com',
username='myusername',
password='mypassword')
connection.connect()
However... it is better to use self.set_position(position_x, position_y)
than to set those values one by one and duplicate code. Imagine if there will be a condition boundary checking (for example for negative values):
class Bad:
def __init__(self, position_x=0, position_y=0):
self.position_x = position_x
self.position_y = position_y
def set_position(self, x, y):
self.position_x = x
self.position_y = y
class Good:
def __init__(self, position_x=0, position_y=0):
self.set_position(position_x, position_y)
def set_position(self, x, y):
self.position_x = x
self.position_y = y
class Bad:
def __init__(self, position_x=0, position_y=0):
self.position_x = min(1024, max(0, position_x))
self.position_y = min(1024, max(0, position_y))
def set_position(self, x, y):
self.position_x = min(1024, max(0, x))
self.position_y = min(1024, max(0, y))
class Good:
def __init__(self, position_x=0, position_y=0):
self.set_position(position_x, position_y)
def set_position(self, x, y):
self.position_x = min(1024, max(0, x))
self.position_y = min(1024, max(0, y))
6.12.8. Use Cases¶
Note, that this unfortunately does not work this way. Path()
always returns PosixPath
:
from pathlib import Path
Path('/etc/passwd')
# PosixPath('/etc/passwd')
Path('c:\\Users\\Admin\\myfile.txt')
# WindowsPath('c:\\Users\\Admin\\myfile.txt')
Path(r'C:\Users\Admin\myfile.txt')
# WindowsPath('C:\\Users\\Admin\\myfile.txt')
Path(r'C:/Users/Admin/myfile.txt')
# WindowsPath('C:/Users/Admin/myfile.txt')
6.12.9. Assignments¶
"""
* Assignment: OOP Constructor Syntax
* Complexity: easy
* Lines of code: 6 lines
* Time: 5 min
English:
1. Use data from "Given" section (see below)
2. Define class `Point` with methods:
a. `__new__()` returning new `Point` class instances
b. `__init__()` taking `x` and `y` and stores them as attributes
4. Compare result with "Tests" section (see below)
Polish:
1. Użyj kodu z sekcji "Given" (patrz poniżej)
2. Zdefiniuj klasę `Point` z metodami:
a. `__new__()` zwraca nową instancję klasy `Point`
b. `__init__()` przyjmuje `x` i `y` i zapisuje je jako atrybuty
3. Porównaj wyniki z sekcją "Tests" (patrz poniżej)
Tests:
>>> from inspect import isclass
>>> assert isclass(Point)
>>> assert hasattr(Point, '__new__')
>>> assert hasattr(Point, '__init__')
>>> pt = Point.__new__(Point)
>>> assert type(pt) is Point
>>> pt.__init__(1, 2)
>>> assert pt.x == 1
>>> assert pt.y == 2
"""
"""
* Assignment: OOP Constructor Passwd
* Complexity: easy
* Lines of code: 21 lines
* Time: 13 min
English:
TODO: English translation
Polish:
1. Użyj kodu z sekcji "Given" (patrz poniżej)
2. Iteruj po liniach w `DATA`
3. Odrzuć puste linie i komentarze
4. Podziel linię po dwukropku
5. Stwórz klasę `Account`, która zwraca instancje klas `UserAccount` lub `SystemAccount` w zależności od wartości pola UID
6. User ID (UID) to trzecie pole, np. `root:x:0:0:root:/root:/bin/bash` to UID jest równy `0`
7. Konta systemowe (`SystemAccount`) to takie, które w polu UID mają wartość poniżej `1000`
8. Konta użytkowników (`UserAccount`) to takie, które w polu UID mają wartość `1000` lub więcej
9. Porównaj wyniki z sekcją "Tests" (patrz poniżej)
Tests:
>>> result # doctest: +NORMALIZE_WHITESPACE
[SystemAccount(username='root'),
SystemAccount(username='bin'),
SystemAccount(username='daemon'),
SystemAccount(username='adm'),
SystemAccount(username='shutdown'),
SystemAccount(username='halt'),
SystemAccount(username='nobody'),
SystemAccount(username='sshd'),
UserAccount(username='twardowski'),
UserAccount(username='jimenez'),
UserAccount(username='ivanovic'),
UserAccount(username='lewis')]
"""
# Given
DATA = """root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
nobody:x:99:99:Nobody:/:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
twardowski:x:1000:1000:Jan Twardowski:/home/twardowski:/bin/bash
jimenez:x:1001:1001:José Jiménez:/home/jimenez:/bin/bash
ivanovic:x:1002:1002:Иван Иванович:/home/ivanovic:/bin/bash
lewis:x:1002:1002:Melissa Lewis:/home/lewis:/bin/bash"""