5.2. OOP Attribute Static/Dynamic¶
5.2.1. Recap¶
Type annotations are not variable definition:
>>> x: int
>>>
>>> print(x)
Traceback (most recent call last):
NameError: name 'x' is not defined
Type annotations will only tell, that if there will be a identifier with
name x
, it should be an int
:
>>> x: int
>>> x = 0
>>>
>>> print(x)
0
Typically it is written in shorter form:
>>> x: int = 0
>>>
>>> print(x)
0
5.2.2. Static Fields¶
Fields defined on a class
Must have default values
Share state
Static fields are defined on a class:
>>> class Astronaut:
... pass
>>>
>>>
>>> Astronaut.firstname = 'Mark'
>>> Astronaut.lastname = 'Watney'
Static fields are defined in a class:
>>> class Astronaut:
... firstname = 'Mark'
... lastname = 'Watney'
5.2.3. Dynamic Fields¶
Fields defined on an instance
Do not share state (unless mutable argument)
By convention initialized in
__init__()
Dynamic fields are defined on an instance:
>>> class Astronaut:
... pass
>>>
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Mark'
>>> astro.lastname = 'Watney'
Dynamic fields are defined in init:
>>> class Astronaut:
... def __init__(self):
... self.firstname = 'Mark'
... self.lastname = 'Watney'
Dynamic fields with variable values:
>>> class Astronaut:
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
5.2.4. Static and Dynamic Fields¶
Static and dynamic fields defined in code:
>>> class Astronaut:
... pass
>>>
>>>
>>> Astronaut.firstname = 'Mark'
>>> Astronaut.lastname = 'Watney'
>>>
>>> astro = Astronaut()
>>> astro.firstname = 'Melissa'
>>> astro.lastname = 'Lewis'
Static and dynamic fields defined in class:
>>> class Astronaut:
... firstname = 'Mark'
... lastname = 'Watney'
...
... def __init__(self):
... self.firstname = 'Mark'
... self.lastname = 'Watney'
Note, the last example makes not meaningful sense. Dynamic fields will shadow static fields.
5.2.5. Type Annotations¶
No fields at all (sic!), type annotations only:
>>> class Astronaut:
... firstname: str
... lastname: str
Static fields with type annotations:
>>> class Astronaut:
... firstname: str = 'Mark'
... lastname: str = 'Watney'
Dynamic fields with type annotations:
>>> class Astronaut:
... firstname: str
... lastname: str
...
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
Both static and dynamic fields with type annotations:
>>> class Astronaut:
... firstname: str = 'Mark'
... lastname: str = 'Watney'
...
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
Note, that that static field which does not change you can use Final:
Static fields with type annotations:
>>> from typing import Final
>>>
>>>
>>> class Astronaut:
... firstname: Final[str] = 'Mark'
... lastname: Final[str] = 'Watney'
5.2.6. Dataclasses¶
Dataclass uses static field notation to create dynamic fields
Dataclass do not validate type annotations, unless
ClassVar
orInitVar
>>> from dataclasses import dataclass, InitVar
>>> from typing import ClassVar
Dynamic fields:
>>> @dataclass
... class Astronaut:
... firstname: str
... lastname: str
Dynamic fields with default values
>>> @dataclass
... class Astronaut:
... firstname: str = 'Mark'
... lastname: str = 'Watney'
Static fields created by ClassVar
>>> @dataclass
... class Astronaut:
... firstname: ClassVar[str] = 'Mark'
... lastname: ClassVar[str] = 'Watney'
Using InitVar
will not produce any fields at all. InitVar
specifies parameters to __post_init__()
method. They will be
forgotten as soon after __post_init__()
returns, unless you
assign them to whatever fields.
>>> @dataclass
... class Astronaut:
... firstname: InitVar[str] = 'Mark'
... lastname: InitVar[str] = 'Watney'
5.2.7. Static vs. Dynamic Fields¶
Static vs. Dynamic fields:
Lets define a class with static field:
>>> class Astronaut:
... agency = 'NASA'
Lets create three instances of Astronaut
class:
>>> watney = Astronaut()
>>> lewis = Astronaut()
>>> martinez = Astronaut()
We will print agency
field:
>>> print(watney.agency)
NASA
>>>
>>> print(lewis.agency)
NASA
>>>
>>> print(martinez.agency)
NASA
>>>
>>> print(Astronaut.agency)
NASA
Lets change field on a class and print agency
field:
>>> Astronaut.agency = 'ESA'
>>>
>>>
>>> print(watney.agency)
ESA
>>>
>>> print(lewis.agency)
ESA
>>>
>>> print(martinez.agency)
ESA
>>>
>>> print(Astronaut.agency)
ESA
Lets change field on an instance and print agency
field:
>>> watney.agency = 'POLSA'
>>>
>>>
>>> print(watney.agency)
POLSA
>>>
>>> print(lewis.agency)
ESA
>>>
>>> print(martinez.agency)
ESA
>>>
>>> print(Astronaut.agency)
ESA
Note, that the class which defined field shadowed the static field from class.
Lets change field on a class and print agency
field:
>>> Astronaut.agency = 'NASA'
>>>
>>>
>>> print(watney.agency)
POLSA
>>>
>>> print(lewis.agency)
NASA
>>>
>>> print(martinez.agency)
NASA
>>>
>>> print(Astronaut.agency)
NASA
Lets delete field from an instance and print agency
field:
>>> del watney.agency
>>>
>>>
>>> print(watney.agency)
NASA
>>>
>>> print(lewis.agency)
NASA
>>>
>>> print(martinez.agency)
NASA
>>>
>>> print(Astronaut.agency)
NASA
5.2.8. Mechanism¶
vars(obj)
is will returnobj.__dict__
>>> class Astronaut:
... firstname = 'Mark'
... lastname = 'Watney'
...
... def __init__(self, firstname, lastname):
... self.firstname = firstname
... self.lastname = lastname
>>>
>>>
>>> astro = Astronaut('Melissa', 'Lewis')
>>>
>>> vars(astro)
{'firstname': 'Melissa', 'lastname': 'Lewis'}
>>>
>>> vars(Astronaut)
mappingproxy({'__module__': '__main__',
'firstname': 'Mark',
'lastname': 'Watney',
'__init__': <function Astronaut.__init__ at 0x...>,
'__dict__': <attribute '__dict__' of 'Astronaut' objects>,
'__weakref__': <attribute '__weakref__' of 'Astronaut' objects>,
'__doc__': None})
5.2.9. Use Case - 0x01¶
>>> class Astronaut:
... firstname: str
... lastname: str
... age: int
... AGE_MIN: int = 30
... AGE_MAX: int = 50
5.2.10. Use Case - 0x02¶
>>> class Astronaut:
... firstname: str
... lastname: str
... age: int
... AGE_MIN: Final[int] = 30
... AGE_MAX: Final[int] = 50
5.2.11. Use Case - 0x03¶
>>> class Astronaut:
... firstname: str
... lastname: str
... age: int
... AGE_MIN: int = 30
... AGE_MAX: int = 50
...
... def __init__(self, firstname, lastname, age):
... self.firstname = firstname
... self.lastname = lastname
... self.age = age
...
... if not self.AGE_MIN <= self.age < self.AGE_MAX:
... raise ValueError('age is invalid')
5.2.12. Use Case - 0x04¶
>>> from typing import Final
>>>
>>>
>>> class Astronaut:
... firstname: str
... lastname: str
... age: int
... AGE_MIN: Final[int] = 30
... AGE_MAX: Final[int] = 50
...
... def __init__(self, firstname, lastname, age):
... self.firstname = firstname
... self.lastname = lastname
... self.age = age
...
... if not self.AGE_MIN <= self.age < self.AGE_MAX:
... raise ValueError('age is invalid')
5.2.13. Use Case - 0x05¶
>>> from dataclasses import dataclass
>>>
>>>
>>> @dataclass
... class Astronaut:
... firstname: str
... lastname: str
... age: int
... AGE_MIN: ClassVar[int] = 30
... AGE_MAX: ClassVar[int] = 50
...
... def __post_init__(self):
... if not self.AGE_MIN <= self.age < self.AGE_MAX:
... raise ValueError('age is invalid')
5.2.14. Use Case - 0x06¶
>>> from dataclasses import dataclass
>>> from typing import Final
>>>
>>>
>>> @dataclass
... class Astronaut:
... firstname: str
... lastname: str
... age: int
... AGE_MIN: ClassVar[int] = 30
... AGE_MAX: ClassVar[int] = 50
...
... def __post_init__(self):
... if not self.AGE_MIN <= self.age < self.AGE_MAX:
... raise ValueError('age is invalid')