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 Astronaut:
...     firstname: str          # public
...     lastname: str           # public
...     _salary: int            # protected
...     _address: int           # protected
...     __username: str         # private
...     __password: str         # private
...     id_: int                # avoid name collision
...     type_: str              # avoid name collision
...     __doc__: str            # system
...     __module__: str         # system

5.3.1. SetUp

>>> from dataclasses import dataclass

5.3.2. Example

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

5.3.3. Public Attribute

  • name - public attribute

>>> @dataclass
... class Astronaut:
...     firstname: str
...     lastname: str
>>>
>>>
>>> astro = Astronaut('Mark', 'Watney')

To print attributes directly:

>>> print(astro.firstname)
Mark
>>>
>>> print(astro.lastname)
Watney

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

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

5.3.4. Protected Attribute

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

>>> @dataclass
... class Astronaut:
...     _firstname: str
...     _lastname: str
>>>
>>>
>>> astro = Astronaut('Mark', '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

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

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

5.3.5. Private Attribute

  • __name - private attribute (name mangling)

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

There are no attributes with names __firstname and __lastname:

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

To print attributes directly:

>>> print(astro._Astronaut__firstname)
Mark
>>>
>>> print(astro._Astronaut__lastname)
Watney

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

>>> vars(astro)  
{'_Astronaut__firstname': 'Mark',
 '_Astronaut__lastname': 'Watney'}

5.3.6. Name Mangling

>>> @dataclass
... class English:
...     greeting: str = 'Hello'
>>>
>>>
>>> @dataclass
... class Texan(English):
...     greeting: str = 'Howdy'
>>>
>>>
>>> mark = Texan()
>>>
>>> print(mark.greeting)
Howdy
>>> @dataclass
... class English:
...     __greeting: str = 'Hello'
>>>
>>>
>>> @dataclass
... class Texan(English):
...     __greeting: str = 'Howdy'
>>>
>>>
>>> mark = Texan()
>>>
>>> print(mark._English__greeting)
Hello
>>>
>>> print(mark._Texan__greeting)
Howdy

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

>>> vars(mark)  
{'_English__greeting': 'Hello',
 '_Texan__greeting': 'Howdy'}

5.3.7. Name Collision

  • Example colliding names: type_, id_, hash_

>>> type_ = type('myobject')
>>> id_ = id('myobject')
>>> hash_ = hash('myobject')

Example:

>>> 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)

5.3.8. System Attributes

  • __name__ - Current module

  • obj.__class__ - Class from which object was instantiated

  • obj.__dict__ - Stores instance variables

  • obj.__doc__ - Object docstring

  • obj.__annotations__ - Object attributes type annotations

  • obj.__module__ - Name of a module in which object was defined

>>> @dataclass
... class Astronaut:
...     firstname: str
...     lastname: str
>>>
>>>
>>> astro = Astronaut('Mark', 'Watney')
>>> astro.__class__
<class '__main__.Astronaut'>
>>>
>>> astro.__dict__
{'firstname': 'Mark', 'lastname': 'Watney'}
>>>
>>> astro.__doc__
'Astronaut(firstname: str, lastname: str)'
>>>
>>> astro.__annotations__
{'firstname': <class 'str'>, 'lastname': <class 'str'>}
>>>
>>> astro.__module__
'__main__'

5.3.9. Show Attributes

  • vars() display obj.__dict__

>>> class Astronaut:
...     def __init__(self):
...         self.firstname = 'Mark'
...         self.lastname = 'Watney'
...         self._salary = 10_000
...         self._address = '2101 E NASA Pkwy, Houston 77058, Texas, USA'
...         self.__username = 'mwatney'
...         self.__password = 'ares3'
...         self.id_ = 1337
...         self.type_ = 'astronaut'
...         self.__doc__ = 'Astronaut Class'
...         self.__module__ = '__main__'
>>>
>>>
>>> astro = Astronaut()

All attributes:

>>> vars(astro)  
{'firstname': 'Mark',
 'lastname': 'Watney',
 '_salary': 10000,
 '_address': '2101 E NASA Pkwy, Houston 77058, Texas, USA',
 '_Astronaut__username': 'mwatney',
 '_Astronaut__password': 'ares3',
 'id_': 1337,
 'type_': 'astronaut',
 '__doc__': 'Astronaut Class',
 '__module__': '__main__'}

Public attributes:

>>> result = {attribute: value
...           for attribute, value in vars(astro).items()
...           if not attribute.startswith('_')}
>>>
>>> print(result)
{'firstname': 'Mark', 'lastname': 'Watney', 'id_': 1337, 'type_': 'astronaut'}

Protected attributes:

>>> result = {attribute: value
...           for attribute, value in vars(astro).items()
...           if attribute.startswith('_')
...           and not attribute.startswith('__')
...           and not attribute.startswith(f'_{astro.__class__.__name__}__')}
>>>
>>> print(result)
{'_salary': 10000, '_address': '2101 E NASA Pkwy, Houston 77058, Texas, USA'}

Private attributes:

>>> result = {attribute: value
...           for attribute, value in vars(astro).items()
...           if attribute.startswith(f'_{astro.__class__.__name__}__')}
>>>
>>> print(result)
{'_Astronaut__username': 'mwatney', '_Astronaut__password': 'ares3'}

System attributes:

>>> result = {attribute: value
...           for attribute, value in vars(astro).items()
...           if attribute.startswith('__')
...           and attribute.endswith('__')}
>>>
>>> print(result)
{'__doc__': 'Astronaut Class', '__module__': '__main__'}

5.3.10. References

1

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

5.3.11. Assignments

Code 5.11. Solution
"""
* Assignment: OOP AttributeAccessModifiers 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 AttributeAccessModifiers 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 AttributeAccessModifiers 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 = ...