6.5. Access

6.5.1. Rationale

  • Attributes and methods are always public

  • No protected and private keywords

  • Protecting is only by convention [privatevar]

Attributes:

  • name - public attribute

  • _name - protected attribute (non-public by convention)

  • __name - private attribute (name mangling)

  • __name__ - system attribute

  • name_ - avoid name collision

Methods:

  • name(self) - public method

  • _name(self) - protected method (non-public by convention)

  • __name(self) - private method (name mangling)

  • __name__(self) - system method

  • name_(self) - avoid name collision

class Public:
    firstname: str
    lastname: str

    def __init__(self):
        self.firstname = 'Mark'
        self.lastname = 'Watney'


class Protected:
    _firstname: str
    _lastname: str

    def __init__(self):
        self._firstname = 'Mark'
        self._lastname = 'Watney'


class Private:
    __firstname: str
    __lastname: str

    def __init__(self):
        self.__firstname = 'Mark'
        self.__lastname = 'Watney'


obj = Public()
print(obj.firstname)
# Mark
print(obj.lastname)
# Watney
print(obj.__dict__)
# {'firstname': 'Mark', 'lastname': 'Watney'}

obj = Protected()
print(obj._firstname)       # IDE should warn: "Access to a protected member _firstname of a class"
# Mark
print(obj._lastname)        # IDE should warn: "Access to a protected member _lastname of a class"
# Watney
print(obj.__dict__)
# {'_firstname': 'Mark', '_lastname': 'Watney'}

obj = Private()
print(obj.__firstname)
# Traceback (most recent call last):
# AttributeError: 'Private' object has no attribute '__firstname'
print(obj.__lastname)
# Traceback (most recent call last):
# AttributeError: 'Private' object has no attribute '__lastname'
print(obj.__dict__)
# {'_Private__firstname': 'Mark', '_Private__lastname': 'Watney'}
print(obj._Private__firstname)
# Mark
print(obj._Private__lastname)
# Watney

6.5.2. Protected Attribute

  • _name - protected attribute (by convention)

Access modifiers:

class Temperature:
    pass


temp = Temperature()
temp._value = 10

print(temp._value)  # IDE should warn: "Access to a protected member _value of a class"
# 10

Access modifiers:

class Astronaut:
    def __init__(self, firstname, lastname):
        self._firstname = firstname
        self._lastname = lastname
        self.publicname = f'{firstname} {lastname[0]}.'


mark = Astronaut('Mark', 'Watney')

print(mark._firstname)  # IDE should warn: "Access to a protected member _firstname of a class"
# Mark

print(mark._lastname)  # IDE should warn: "Access to a protected member _lastname of a class"
# Watney

print(mark.publicname)
# Mark W.

print(mark.firstname)
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'firstname'

print(mark.lastname)
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute 'lastname'

6.5.3. Private Attribute

  • __name - private attribute

class Astronaut:
    def __init__(self, firstname, lastname):
        self.__firstname = firstname
        self.__lastname = lastname
        self.publicname = f'{firstname} {lastname[0]}.'


astro = Astronaut('Mark', 'Watney')

print(astro.publicname)
# Mark W.

print(astro.__firstname)
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute '__firstname'

print(astro.__lastname)
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute '__firstname'

print(astro.__dict__)
# {'_Astronaut__firstname': 'Mark',
#  '_Astronaut__lastname': 'Watney',
#  'publicname': 'Mark W.'}

6.5.4. System Attributes

  • __name__ - system attribute

obj.__dict__ - Getting dynamic fields and values:

class Astronaut:
    def __init__(self, firstname, lastname):
        self.firstname = firstname
        self.lastname = lastname


astro = Astronaut('Mark', 'Watney')

print(astro.__dict__)
# {'firstname': 'Mark',
#  'lastname': 'Watney'}

obj.__dict__ - Getting dynamic fields and values:

class Astronaut:
    def __init__(self, firstname, lastname):
        self._firstname = firstname
        self._lastname = lastname
        self.publicname = f'{firstname} {lastname[0]}.'


astro = Astronaut('Mark', 'Watney')

print(astro.__dict__)
# {'_firstname': 'Mark',
#  '_lastname': 'Watney',
#  'publicname': 'Mark W.'}

public_attributes = {attribute: value
                     for attribute, value in astro.__dict__.items()
                     if not attribute.startswith('_')}

print(public_attributes)
# {'publicname': 'Mark W.'}

6.5.5. Protected Method

class Astronaut:
    def __init__(self, firstname, lastname):
        self._firstname = firstname
        self._lastname = lastname

    def _get_fullname(self):
        return f'{self._firstname} {self._lastname}'

    def get_publicname(self):
        return f'{self._firstname} {self._lastname[0]}.'


astro = Astronaut('Mark', 'Watney')

print(dir(astro))
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
# '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__',
# '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_firstname',
# '_get_fullname', '_lastname', 'get_publicname']

public_methods = [method
                  for method in dir(astro)
                  if not method.startswith('_')]

print(public_methods)
# ['get_publicname']

6.5.6. Private Method

class Astronaut:
    def __init__(self, firstname, lastname):
        self._firstname = firstname
        self._lastname = lastname

    def __get_fullname(self):
        return f'{self._firstname} {self._lastname}'

    def get_publicname(self):
        return f'{self._firstname} {self._lastname[0]}.'


mark = Astronaut('Mark', 'Watney')

print(dir(mark))
# ['_Astronaut__get_fullname', '__class__', '__delattr__', '__dict__',
#  '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
#  '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',
#  '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__',
#  '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__',
#  '__weakref__', '_firstname', '_lastname', 'get_publicname']

public_methods = [method
                  for method in dir(astro)
                  if not method.startswith('_')]

print(public_methods)
# ['get_publicname']

mark.__get_fullname()
# Traceback (most recent call last):
# AttributeError: 'Astronaut' object has no attribute '__get_fullname'

6.5.7. System Method

class Astronaut:
    def __init__(self, firstname, lastname):
        self._firstname = firstname
        self._lastname = lastname

    def __str__(self):
        return 'stringification'

    def __repr__(self):
        return 'representation'


mark = Astronaut('Mark', 'Watney')

print(str(mark))
# stringification

print(repr(mark))
# representation

6.5.8. Assignments

Code 6.26. Solution
"""
* Assignment: OOP Access Protected
* Complexity: easy
* Lines of code: 7 lines
* Time: 8 min

English:
    1. Use data from "Given" section (see below)
    2. Define `result: list[dict]`
    3. Define class `Iris` with attributes
    4. Protected attributes: `sepal_length`, `sepal_width`, `petal_length`, `petal_width`
    5. Public attribute: `species`
    6. Iterate over `DATA` and add all public attributes to `result`
    7. Compare result with "Tests" section (see below)

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Zdefiniuj `result: list[dict]`
    3. Zdefiniuj klasę `Iris`
    4. Chronione atrybuty: `sepal_length`, `sepal_width`, `petal_length`, `petal_width`
    5. Publiczne atrybuty: `species`
    6. Iteruj po `DATA` i dodaj wszystkie publiczne atrybuty do `result`
    7. Porównaj wyniki z sekcją "Tests" (patrz poniżej)

Tests:
    >>> DATA = [Iris(5.8, 2.7, 5.1, 1.9, 'virginica'),
    ...         Iris(5.1, 3.5, 1.4, 0.2, 'setosa'),
    ...         Iris(5.7, 2.8, 4.1, 1.3, 'versicolor')]

    >>> result = [{attribute: value}
    ...           for row in DATA
    ...           for attribute, value in row.__dict__.items()
    ...           if not attribute.startswith('_')]

    >>> result  # doctest: +NORMALIZE_WHITESPACE
    [{'species': 'virginica'},
     {'species': 'setosa'},
     {'species': 'versicolor'}]
"""


# Given
class Iris:
    pass


Code 6.27. Solution
"""
* Assignment: OOP Protected Dict
* Complexity: medium
* Lines of code: 8 lines
* Time: 8 min

English:
    1. Use data from "Given" section (see below)
    2. Create `result: list[Iris]`
    3. Iterate over `DATA` skipping header
    4. Separate `features` from `species` in each row
    5. Append to `result`:
        a. if `species` is "setosa" append instance of a class `Setosa`
        b. if `species` is "versicolor" append instance of a class `Versicolor`
        c. if `species` is "virginica" append instance of a class `Virginica`
    6. Initialize instances with `features` using `*args` notation
    7. Compare result with "Tests" section (see below)

Polish:
    1. Użyj danych z sekcji "Given" (patrz poniżej)
    2. Stwórz `result: list[Iris]`
    3. Iterując po `DATA` pomijając header
    4. Odseparuj `features` od `species` w każdym wierszu
    5. Dodaj do `result`:
        a. jeżeli `species` jest "setosa" to dodaj instancję klasy `Setosa`
        b. jeżeli `species` jest "versicolor" to dodaj instancję klasy `Versicolor`
        c. jeżeli `species` jest "virginica" to dodaj instancję klasy `Virginica`
    6. Instancje inicjalizuj danymi z `features` używając notacji `*args`
    7. Porównaj wyniki z sekcją "Tests" (patrz poniżej)

Hints:
    * `globals()[classname]`

Tests:
    >>> result  # doctest: +NORMALIZE_WHITESPACE
    [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)]
"""


# Given
from dataclasses import dataclass


DATA = [('Sepal length', 'Sepal width', 'Petal length', 'Petal width', 'Species'),
        (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')]


@dataclass(repr=False)
class Iris:
    _sepal_length: float
    _sepal_width: float
    _petal_length: float
    _petal_width: float

    def __repr__(self):
        name = self.__class__.__name__
        args = tuple(self.__dict__.values())
        return f'{name}{args}'


class Setosa(Iris):
    pass


class Versicolor(Iris):
    pass


class Virginica(Iris):
    pass


result: list = []