5.3. OOP Attribute Access Modifiers

  • Attributes and methods are always public

  • No protected and private keywords

  • Protecting is only by convention 1

  • name - public attribute

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

  • __name - private attribute (name mangling)

  • __name__ - system attribute

  • name_ - avoid name collision with built-ins

>>> class Public:
...     firstname: str
...     lastname: str
>>>
>>> class Protected:
...     _firstname: str
...     _lastname: str
>>>
>>> class Private:
...     __firstname: str
...     __lastname: str

5.3.1. 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

5.3.2. 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

5.3.3. Protected Attribute

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

>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class Astronaut:
...     _firstname: str
...     _lastname: str
>>>
>>>
>>> astro = Astronaut('Mark', 'Watney')

To list all the attributes once again we can use vars():

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

Python will allow the following statement, however your IDE should warn you "Access to a protected member _firstname of a class":

>>> print(astro._firstname)
Mark
>>>
>>> print(astro._lastname)
Watney

5.3.4. Private Attribute

  • __name - private attribute (name mangling)

>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class Astronaut:
...     __firstname: str
...     __lastname: str
>>>
>>>
>>> astro = Astronaut('Mark', 'Watney')
>>>
>>> vars(astro)
{'_Astronaut__firstname': 'Mark', '_Astronaut__lastname': 'Watney'}
>>>
>>> print(astro._Astronaut__firstname)
Mark
>>>
>>> print(astro._Astronaut__lastname)
Watney
>>>
>>> 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 '__lastname'

5.3.5. Name Mangling

>>> class Person:
...     def hello(self):
...         return 'hello Person'
>>>
>>>
>>> class Astronaut(Person):
...     def hello(self):
...         return 'hello Astronaut'
>>>
>>>
>>> astro = Astronaut()
>>> astro.hello()
'hello Astronaut'
>>> class Person:
...     def __hello(self):
...         return 'hello Person'
>>>
>>>
>>> class Astronaut(Person):
...     def __hello(self):
...         return 'hello Astronaut'
>>>
>>>
>>> astro = Astronaut()
>>> astro._Astronaut__hello()
'hello Astronaut'
>>> astro._Person__hello()
'hello Person'
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class Person:
...     __firstname: str
...     __lastname: str
>>>
>>> @dataclass
... class Astronaut(Person):
...     __firstname: str
...     __lastname: str
>>>
>>> astro = Astronaut('Mark', 'Watney')
Traceback (most recent call last):
TypeError: Astronaut.__init__() missing 2 required positional arguments: '_Astronaut__firstname' and '_Astronaut__lastname'
>>>
>>> astro = Astronaut('Mark', 'Watney', 'Melissa', 'Lewis')
>>>
>>> vars(astro)  
{'_Person__firstname': 'Mark',
 '_Person__lastname': 'Watney',
 '_Astronaut__firstname': 'Melissa',
 '_Astronaut__lastname': 'Lewis'}

5.3.6. Name Collision

  • type_ = type('myobject')

  • id_ = id('myobject')

  • hash_ = hash('myobject')

  • date_ = date(1969, 7, 21)

>>> from datetime import date
>>>
>>>
>>> type_ = type('myobject')
>>> id_ = id('myobject')
>>> hash_ = hash('myobject')
>>> date_ = date(1969, 7, 21)

Example:

>>> from datetime import date
>>>
>>>
>>> class User:
...     def __init__(self, firstname, lastname):
...         self.firstname = firstname
...         self.lastname = lastname
...         self.type_ = type(self)
...         self.id_ = id(self)
...         self.hash_ = hash(self)
...         self.date_ = date(1969, 7, 21)

5.3.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 attribute.startswith('_')}
>>>
>>>
>>> print(public_attributes)
{'publicname': 'Mark W.'}
>>>
>>> print(protected_attributes)
{'_firstname': 'Mark', '_lastname': 'Watney'}

5.3.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'}

5.3.9. References

1

https://docs.python.org/3/tutorial/classes.html#private-variables

5.3.10. Assignments

Code 5.11. Solution
"""
* Assignment: OOP Access Dataclass
* Complexity: easy
* Lines of code: 5 lines
* Time: 3 min

English:
    1. Modify dataclass `Iris` to add attributes:
        a. Protected attributes: `sepal_length, sepal_width`
        b. Private attributes: `petal_length, petal_width`
        c. Public attribute: `species`
    2. Run doctests - all must succeed

Polish:
    1. Zmodyfikuj dataclass `Iris` aby dodać atrybuty:
        a. Chronione atrybuty: `sepal_length, sepal_width`
        b. Private attributes: `petal_length, petal_width`
        c. Publiczne atrybuty: `species`
    2. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass

    >>> assert isclass(Iris)
    >>> assert hasattr(Iris, '__annotations__')

    >>> assert '_sepal_width' in Iris.__dataclass_fields__
    >>> assert '_sepal_length' in Iris.__dataclass_fields__
    >>> assert '_Iris__petal_width' in Iris.__dataclass_fields__
    >>> assert '_Iris__petal_length' in Iris.__dataclass_fields__
    >>> assert 'species' in Iris.__dataclass_fields__
"""
from dataclasses import dataclass


@dataclass
class Iris:
    pass


Code 5.12. Solution
"""
* Assignment: OOP Access Init
* Complexity: easy
* Lines of code: 6 lines
* Time: 5 min

English:
    1. Modify class `Iris` to add attributes:
        a. Protected attributes: `sepal_length, sepal_width`
        b. Private attributes: `petal_length, petal_width`
        c. Public attribute: `species`
    2. Do not use `dataclass`
    3. Run doctests - all must succeed

Polish:
    1. Zmodyfikuj klasę `Iris` aby dodać atrybuty:
        a. Chronione atrybuty: `sepal_length, sepal_width`
        b. Private attributes: `petal_length, petal_width`
        c. Publiczne atrybuty: `species`
    2. Nie używaj `dataclass`
    3. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0
    >>> from inspect import isclass

    >>> assert isclass(Iris)

    >>> result = Iris(5.1, 3.5, 1.4, 0.2, 'setosa')
    >>> assert hasattr(result, '_sepal_width')
    >>> assert hasattr(result, '_sepal_length')
    >>> assert hasattr(result, '_Iris__petal_width')
    >>> assert hasattr(result, '_Iris__petal_length')
    >>> assert hasattr(result, 'species')
"""


class Iris:
    pass


Code 5.13. Solution
"""
* Assignment: OOP Access Members
* Complexity: easy
* Lines of code: 3 lines
* Time: 8 min

English:
    1. Extract from class `Iris` attribute names and their values:
        a. Define `protected: dict` with protected attributes
        b. Define `private: dict` with private attributes
        c. Define `public: dict` with public attributes
    2. Run doctests - all must succeed

Polish:
    1. Wydobądź z klasy `Iris` nazwy atrybutów i ich wartości:
        a. Zdefiniuj `protected: dict` z atrybutami chronionymi (protected)
        b. Zdefiniuj `private: dict` z atrybutami prywatnymi (private)
        c. Zdefiniuj `public: dict` z atrybutami publicznymi (public)
    2. Uruchom doctesty - wszystkie muszą się powieść

Tests:
    >>> import sys; sys.tracebacklimit = 0

    >>> assert type(public) is dict
    >>> assert all(type(k) is str for k,v in public.items())
    >>> assert all(type(v) is str for k,v in public.items())

    >>> assert type(protected) is dict
    >>> assert all(type(k) is str for k,v in protected.items())
    >>> assert all(type(v) is float for k,v in protected.items())

    >>> assert type(private) is dict
    >>> assert all(type(k) is str for k,v in private.items())
    >>> assert all(type(v) is float for k,v in private.items())

    >>> assert len(public) > 0, \
    'public: list[dict] must not be empty'

    >>> assert len(protected) > 0, \
    'protected: list[dict] must not be empty'

    >>> assert len(private) > 0, \
    'private: list[dict] must not be empty'

    >>> public
    {'species': 'virginica'}

    >>> protected
    {'_sepal_width': 5.8, '_sepal_length': 2.7}

    >>> private
    {'_Iris__petal_width': 5.1, '_Iris__petal_length': 1.9}

"""
from dataclasses import dataclass


@dataclass
class Iris:
    _sepal_width: float
    _sepal_length: float
    __petal_width: float
    __petal_length: float
    species: str


DATA = Iris(5.8, 2.7, 5.1, 1.9, 'virginica')


# All public attributes and their values
# type: dict[str,float|str]
public = ...

# All protected attributes and their values
# type: dict[str,float|str]
protected = ...

# All private attributes and their values
# type: dict[str,float|str]
private = ...