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 (dunder)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 # public, avoid name collision
... type_: str # public, avoid name collision
... __doc__: str # (dunder) special system
... __module__: str # (dunder) special system
... __version__ = '1.0.0' # (dunder) special convention
... __author__ = 'mwatney' # (dunder) special convention
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 moduleobj.__class__
- Class from which object was instantiatedobj.__dict__
- Stores instance variablesobj.__doc__
- Object docstringobj.__annotations__
- Object attributes type annotationsobj.__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()
displayobj.__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__'
... self.__version__ = '1.0.0'
... self.__author__ = 'Mark Watney <mwatney@nasa.gov>'
>>>
>>>
>>> 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__',
'__version__': '1.0.0',
'__author__': 'Mark Watney <mwatney@nasa.gov>'}
Public attributes:
>>> def get_public_attributes(obj):
... return {attrname: attrvalue
... for attrname in dir(obj)
... if (attrvalue := getattr(astro, attrname))
... and not callable(attrvalue)
... and not attrname.startswith('_')}
>>>
>>>
>>> get_public_attributes(astro)
{'firstname': 'Mark', 'id_': 1337, 'lastname': 'Watney', 'type_': 'astronaut'}
Protected attributes:
>>> def get_protected_attributes(obj):
... return {attrname: attrvalue
... for attrname in dir(obj)
... if (attrvalue := getattr(obj, attrname))
... and not callable(attrvalue)
... and attrname.startswith('_')
... and not attrname.startswith(f'_{obj.__class__.__name__}_')
... and not attrname.endswith('_')}
>>>
>>>
>>> get_protected_attributes(astro)
{'_address': '2101 E NASA Pkwy, Houston 77058, Texas, USA', '_salary': 10000}
Private attributes:
>>> def get_private_attributes(obj):
... return {attrname: attrvalue
... for attrname in dir(obj)
... if (attrvalue := getattr(obj, attrname))
... and not callable(attrvalue)
... and attrname.startswith(f'_{obj.__class__.__name__}_')}
>>>
>>>
>>> get_private_attributes(astro)
{'_Astronaut__password': 'ares3', '_Astronaut__username': 'mwatney'}
System attributes:
>>> def get_system_attributes(obj):
... return {attrname: attrvalue
... for attrname in dir(obj)
... if (attrvalue := getattr(obj, attrname))
... and not callable(attrvalue)
... and attrname.startswith('__')
... and attrname.endswith('__')}
>>>
>>>
>>> get_system_attributes(astro)
{'__author__': 'Mark Watney <mwatney@nasa.gov>',
'__dict__': {...},
'__doc__': 'Astronaut Class',
'__module__': '__main__',
'__version__': '1.0.0'}
5.3.10. References¶
5.3.11. Assignments¶
"""
* 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
"""
* 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
"""
* 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 = ...