6.5. Access Modifiers

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

6.5.2. Example

class Public:
    firstname: str
    lastname: str

class Protected:
    _firstname: str
    _lastname: str

class Private:
    __firstname: str
    __lastname: str

6.5.3. DataClasses

from dataclasses import dataclass


@dataclass
class Public:
    firstname: str
    lastname: str


@dataclass
class Protected:
    _firstname: str
    _lastname: str


@dataclass
class Private:
    __firstname: str
    __lastname: str

6.5.4. Public Attribute

  • name - public attribute

from dataclasses import dataclass


@dataclass
class Astronaut:
    firstname: str
    lastname: str


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

vars(astro)
# {'firstname': 'Mark', 'lastname': 'Watney'}

print(astro.firstname)
# Mark

print(astro.lastname)
# Watney

6.5.5. Protected Attribute

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

  • IDE should warn: "Access to a protected member _firstname of a class"

from dataclasses import dataclass


@dataclass
class Astronaut:
    _firstname: str
    _lastname: str


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

vars(astro)
# {'_firstname': 'Mark', '_lastname': 'Watney'}

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

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

6.5.6. Private Attribute

  • __name - private attribute (name mangling)

from dataclasses import dataclass


@dataclass
class Astronaut:
    __firstname: str
    __lastname: str


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

vars(astro)
# {'_Private__firstname': 'Mark', '_Private__lastname': 'Watney'}

print(astro._Private__firstname)
# Mark

print(astro._Private__lastname)
# Watney

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

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

6.5.7. Show Attributes

  • vars() display obj.__dict__

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


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

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

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

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


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

print(protected_attributes)
# {'_firstname': 'Mark',
#  '_lastname': 'Watney'}

6.5.8. System Attributes

  • __name__ - Current module

  • obj.__class__

  • obj.__dict__ - Getting dynamic fields and values

  • obj.__doc__ - Docstring

  • obj.__annotations__ - Type annotations of an object

  • obj.__module__

from dataclasses import dataclass


@dataclass
class Astronaut:
    firstname: str
    lastname: str


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

vars(astro)
# {'firstname': 'Mark',
#  'lastname': 'Watney'}

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

6.5.9. Protected Method

from dataclasses import dataclass


@dataclass
class Astronaut:
    _firstname: str
    _lastname: str

    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.10. 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]}.'


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

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

6.5.11. 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'


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

print(str(astro))
# stringification

print(repr(astro))
# representation

6.5.12. Show Methods

  • dir()

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))
# ['_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']

6.5.13. Assignments

Code 6.4. 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.5. Solution
"""
* Assignment: OOP Access 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 = []