Python Language OOP
External
Internal
TODO
- How to call a method from inside the constructor. If I try, the compiler says "Unresolved reference"
Overview
Everything in Python is an object, from numbers to modules. The language offers possibility of declaring custom types, and creating object instances for those types. Unlike modules, there can be multiple instances of the same type. Creation of new types is achieved by using the class
syntax. The custom types such declared are no different, intrinsically, from the types already existing in the runtime. The existing types can be extended.
An object contains data, in form of variables called attributes and behavior, in form of functions called methods.
Class
A new type (class) is defined with the class
keyword.
class MyClass:
def __init__(self):
pass
The class may be declared with parentheses, but the IDE static checks find those as "redundant":
class MyClass():
...
Attributes
An object contains data in form of variables called attributes.
The attributes can be accessed inside the class definition using self.<attribute-name>
. Outside the class definition, they can be accessed via the variable holding the reference to the class instance: my_instance.<attribute-name>
. Unlike in other languages, all attributes are public. If an attribute was not explicitly declared inside the instance with self.some_attribute
, even just to be assigned to None
, an attempt to access the attribute will end up in:
AttributeError: 'MyClass' object has no attribute 'some_attribute'
Python offers two syntactical patterns to expose internal class instance state, either represented by an actual attributes, or computed values: properties and @property/@<attribute-name>.setter
decorators.
The state associated with a computed value can be prevented from being written by omitting the corresponding setter.
Another advantage of using properties over direct attribute access is that if the definition of the attribute changes, only the code within the class definition needs to be changed, not in all the callers,.
Keeping attributes private?
Properties
Properties are class construct that associate getter and setter methods to an attribute. The attribute name can the be used to access and write state inside the class by invoking the associated "property" methods. In other words, the getter and setter methods are "properties" of the attribute with the given name.
class A:
def __init__(self, c):
self.internal_color = c
def get_color(self):
return self.internal_color
def set_color(self, c):
self.internal_color = c
# 'color' is an attribute, though not explicitly declared on self, and get_color() and set_color() are properties of the attribute
color = property(get_color, set_color)
The first argument to property()
is the getter method, and the second argument is the setter method.
Internal class instance state can be accesses and written through the attribute, though there is no actual attribute with that name initialized on self
:
a = A('red')
assert 'red' == a.color
a.color = 'blue'
assert 'blue' == a.color
Decorators
Another way to define properties is with decorators. The same attribute color
, which is not declared on self
, can defined by two different property methods, one getter and one setter, preceded by corresponding decorators (annotations):
@property
: it annotates the getter method. ⚠️ The name of the method must match the name of the attribute.@<attribute-name>.setter
: it annotates the setter method. ⚠️ The<attribute-name>
that is part of the annotation must match the name of the attribute and the name of the setter method.
class A:
def __init__(self, c):
print('initializing A')
self.internal_color = c
@property
def color(self):
print('inside the getter')
return self.internal_color
@color.setter
def color(self, c):
print('inside the setter')
self.internal_color = c
The interaction with the internal state is done identically as in the case of the property()
declaration:
a = A('red')
assert 'red' == a.color
a.color = 'blue'
assert 'blue' == a.color
Methods
An object contains behavior, in form of functions called methods. Unlike in other languages, all methods are public.
Calling class methods from inside class methods:
class MyClass:
def __init__(self):
self.internal()
def internal(self):
pass
Special Methods
__init__()
The object instance initialization method, which is used to do anything that's needed to distinguish an object instance from other object instances of the same class. The self
argument specifies that it refers to the individual object itself. self
is not a reserved word in Python, but it is used conventionally, and it's a good idea to stick with the convention.
class MyClass:
def __init__(self, name, color):
self.name = name
self.color = color
An __init__()
method is not required.
Can there be more than one __init__() methods, each with a different set of arguments, like overloaded constructors?
__str__()
__str__()
is used by print()
, str()
and the string formatters to produce a string representation of an instance.
class MyClass:
...
def __str__(self):
return self.name + ', ' + self.color
Properties, Getters and Setters
In Python, all attributes are public, they can be accessed from outside the class using the variable name that holds a reference to a class' instance.
However, Python has syntax that allows declaring getter and setter methods to access instance state that is not available in form of an actual instance attribute. The instance state in question is known as a property, which is exposed via getter and setter methods.
Static Methods
Static Initialization at Class Level
To perform an initialization once, upon creation of the type in the class' namespace, place the code directly under the code definition: All methods placed into the class are also "executed" - like when defining a function in the global namespace - but they are not calling.
class MyClass:
# executed only once upon creation of the type
pattern = re.compile(r'^(\w+):(\w+)-(\w+)$')
def __init__(self):
...
def my_method(self):
...
The static variable can be accessed inside the class (and outside) by prefixing it with the class name, or, within an instance method, by prefixing it with self
:
class MyClass:
some_var = 'something'
def some_method(self):
print('some_var value', MyClass.some_var)
print('some_var value', self.some_var)
Objects
To create an instance of a class, use the class name followed by left parenthesis, followed by constructor arguments, followed by the right parenthesis.
o2 = SomeOtherClass('something')
If only the class name without parentheses is used, the code will work, but the variable will contain a reference to the class, not the instance of the class.
Initialization
Inheritance
Overriding
Polymorphism
.