Classes
Documentation: Documentation » The Python Tutorial » 9. Classes
Creating a new class creates a new type of object, allowing new instances of that type to be made. Each class instance can have attributes attached to it for maintaining its state. Class instances can also have methods (defined by its class) for modifying its state.
Python Scopes and Namespaces
Namespaces
Some definitions:
- A namespace is a mapping from names to objects.
- There is absolutely no relation between names in different namespaces.
- In the expression
modname.funcname
,modname
is a module object andfuncname
is an attribute of it.
Namespaces are created at different moments and have different lifetimes:
- The namespace containing the
built-in
names is created when the Python interpreter starts up, and is never deleted. - The
global
namespace for a module is created when the module definition is read in; normally, module namespaces also last until the interpreter quits. - The statements executed by the top-level invocation of the interpreter, either read from a script file or interactively, are considered part of a module called main, so they have their own global namespace. (The built-in names actually also live in a module; this is called builtins.)
- The
local
namespace for a function is created when the function is called, and deleted when the function returns or raises an exception that is not handled within the function. Recursive invocations each have their own local namespace.
Summary
Namespaces: built-in
→ global
for a module → top-level invocation of the interpreter → local
for a function.
Scopes
A scope is a textual region of a Python program where a namespace is directly accessible.
Although scopes are determined statically, they are used dynamically. At any time during execution, there are 3 or 4 nested scopes whose namespaces are directly accessible:
- the innermost scope, which is searched first, contains the
local
names of the (textually) current function; - the scopes of any enclosing functions, which are searched starting with the nearest enclosing scope, contains
non-local
, but alsonon-global
names; - the next-to-last scope contains the current module’s
global
names; - the outermost scope (searched last) is the namespace containing
built-in
names.
Summary
Scopes: local
→ enclosing functions non-local
& non-global
→ current module’s global
→ built-in
.
The global statement can be used to indicate that particular variables live in the global scope and should be rebound there; the nonlocal statement indicates that particular variables live in an enclosing scope and should be rebound there.
Note
If no global or nonlocal statement is in effect – assignments to names always go into the innermost scope.
Important
Assignments do not copy data — they just bind names to objects.
The same is true for deletions: the statement del x
removes the binding of x
from the namespace referenced by
the local scope.
Example
def scope_test():
def do_local():
spam = 'local spam' # local assignment (which is default)
# didn’t change `scope_test`'s binding of `spam`
def do_nonlocal():
nonlocal spam # changed `scope_test`'s binding of spam
spam = 'nonlocal spam'
def do_global():
global spam # changed the module-level binding
spam = 'global spam'
spam = 'test spam' # scope_test's binding of spam
do_local()
print('After local assignment:', spam)
do_nonlocal()
print('After nonlocal assignment:', spam)
do_global()
print('After global assignment:', spam)
scope_test()
print("In global scope:", spam)
After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
A First Look at Classes
Class Definition Syntax
When a class definition is entered, a new namespace is created, and used as the local scope — thus, all assignments to local variables go into this new namespace.When a class definition is left normally (via the end), a class object is created. This is basically a wrapper
around the contents of the namespace created by the class definition. The original local scope (the one in effect
just before the class definition was entered) is reinstated, and the class object is bound here to the class name
given in the class definition header (ClassName
in the example).
Class Objects
Class objects support two kinds of operations: attribute references and instantiation.
Attribute references use the standard syntax used for all attribute references in Python: obj.name
.
class MyClass(object):
"""A simple example class."""
i: int = 12345 # Class variable shared by all instances.
data: list[int] = [] # Mistaken use of a class variable; don't use mutable objects because
# just a single list would be shared by all instances; use an instance variable instead.
def __init__(self, data: list[int]):
"""Customize to a specific initial state."""
self.data: list[int] = data # Data attribute or instance variable unique to each instance.
# If the same attribute name occurs in both an instance and in a class,
# then attribute lookup prioritizes the instance.
print(f'type(self.data): {type(self.data)}')
print('data: ' + f'{self.data=}'.split('=')[1].split('.')[-1])
def f(self): # Method.
return 'hello world'
# MyClass.i (int) and MyClass.f (function object) are valid attribute references.
print(f'MyClass.i: {MyClass.i}')
print(f'MyClass.f: {MyClass.f}')
# Class attributes can also be assigned to.
MyClass.i = 123
print(f'MyClass.i: {MyClass.i}')
# __doc__ is also a valid attribute, returning the docstring belonging to the class.
print(f'MyClass.__doc__: {MyClass.__doc__}')
MyClass.i: 12345
MyClass.f: <function MyClass.f at 0x7f7c4c97a320>
MyClass.i: 123
MyClass.__doc__: A simple example class.
Class instantiation uses function notation. Just pretend that the class object is a parameterless function that returns a new instance of the class.
Instance Objects
Note
The only operations understood by instance objects are attribute references.
There are two kinds of valid instance attribute names: data attributes and methods.
Data attributes need not be declared; like local variables, they spring into existence when they are first assigned to.
Method. A method is a function that «belongs to» an object.Note
x.f
—method object; MyClass.f
— function object.
Method Objects
# Calling a class method:
print(f'x.f(): {x.f()}')
# Is exactly equivalent to:
print(f'MyClass.f(x): {MyClass.f(x)}')
# Method object can be stored away and called at a later time.
xf = x.f
print(f'xf(): {xf()}')
Class and Instance Variables
Generally speaking:
- Instance variables are for data unique to each instance.
- Class variables are for attributes and methods shared by all instances of the class.
class Dog:
kind = 'canine' # class variable shared by all instances
def __init__(self, name):
self.name = name # instance variable unique to each instance
Important
Don't use mutable objects as class variables. Use an instance variable instead.
Random Remarks
This is valid, but don't do it in such a way as not to confuse the reader of the code:
Each value is an object, and therefore has a class (also called its type).
Inheritance
Info
Inheritance is a way of creating a new class for using details of an existing class without modifying it. The newly formed class is a derived class (or child class). Similarly, the existing class is a base class (or parent class).
class DerivedClassName(BaseClassName):
pass
class DerivedClassName(modname.BaseClassName):
'''When the base class is defined in another module'''
pass
Resolving attribute references. If a requested attribute is not found in the class, the search proceeds to look in the base class. This rule is applied recursively if the base class itself is derived from some other class.
Resolving method references. The corresponding class attribute is searched, descending down the chain of base classes if necessary, and the method reference is valid if this yields a function object.
Derived classes may override methods of their base classes. An overriding method in a derived class may in fact want
to extend rather than simply replace the base class method of the same name. There is a simple way to call the base
class method directly: just call BaseClassName.methodname(self, arguments)
.
Python has two built-in functions that work with inheritance:
- Use
isinstance()
to check an instance’s type:isinstance(obj, int)
will beTrue
only ifobj.__class__
isint
or some class derived fromint
. - Use
issubclass()
to check class inheritance:issubclass(bool, int)
isTrue
sincebool
is a subclass ofint
. However,issubclass(float, int)
isFalse
sincefloat
is not a subclass ofint
.
Multiple Inheritance
# Python supports a form of multiple inheritance as well.
class DerivedClassName(Base1, Base2, Base3):
pass
Thus, if an attribute is not found in DerivedClassName
, it is searched for in Base1
, then (recursively) in the base
classes of Base1
, and if it was not found there, it was searched for in Base2
, and so on.
Private Variables
Note
«Private» instance variables that cannot be accessed except from inside an object don’t exist in Python.
Non-public. There is a convention that is followed by most Python code: a name prefixed with an underscore
(e.g. _spam
) should be treated as a non-public part of the API (whether it is a function, a method or a data
member). It should be considered an implementation detail and subject to change without notice.
Name mangling: any identifier of the form __spam
(at least two leading underscores, at most one trailing
underscore) is textually replaced with _classname__spam
, where classname
is the current class name with leading
underscore(s) stripped.
Name mangling is helpful for letting subclasses override methods without breaking intraclass method calls. For example:
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
def update(self, iterable):
for item in iterable:
self.items_list.append(item)
__update = update # private copy of original update() method
class MappingSubclass(Mapping):
def update(self, keys, values):
# provides new signature for update()
# but does not break __init__()
for item in zip(keys, values):
self.items_list.append(item)
Note
The mangling rules are designed mostly to avoid accidents; it still is possible to access or modify a variable that is considered private.
Iterators
It is easy to add iterator behavior to your classes. Define an __iter__()
method which returns an object
with a __next__()
method.
Note
If the class defines __next__()
, then __iter__()
can just return self
.
class Reverse:
"""Iterator for looping over a sequence backwards."""
def __init__(self, data):
self.data = data
self.index = len(data)
def __iter__(self):
return self
def __next__(self):
if self.index == 0:
raise StopIteration # To signal that there are no further items produced by the iterator.
self.index = self.index - 1
return self.data[self.index]
>>> rev = Reverse('spam')
>>> iter(rev)
<__main__.Reverse object at 0x00A1DB50>
>>> for char in rev:
... print(char)
...
m
a
p
s
Generators
Generators are a simple and powerful tool for creating iterators. They are written like regular functions but use the yield statement whenever they want to return data. Each time next() is called on it, the generator resumes where it left off (it remembers all the data values and which statement was last executed).
Anything that can be done with generators can also be done with class-based iterators. What makes generators so compact is that the__iter__()
and __next__()
methods are created automatically.
Note
Another key feature is that the local variables and execution state are automatically saved between calls.
This made the function easier to write and much more clear than an approach using instance variables like
self.index
and self.data
.
In addition to automatic method creation and saving program state, when generators terminate, they automatically raise StopIteration.
Methods, classmethods and staticmethods
Documentation:
Let's look at the difference with an example:
# below x will be used by static method
# if we do not define it, the staticmethod will generate error.
x: int = 10
class NumberAdder(object):
x: int = 5 # class variable
def __init__(self, x: int):
self.x: int = x # instance variable
def AddMethod(self, y: int):
print("method:", self.x + y)
@classmethod
# as convention, `cls` must be used for classmethod, instead of `self`
def AddClassMethod(cls, y: int):
print("classmethod:", cls.x + y)
@staticmethod
def AddStaticMethod(y: int):
print("staticmethod:", x + y)
def main():
# 1. method
m = NumberAdder(x=1)
m.AddMethod(y=2) # method: 3
# 2. classmethod
cm = NumberAdder(x=2)
# for class method, class variable x = 5, will be used for addition
# instead of an instance variable
cm.AddClassMethod(y=10) # classmethod: 15
# 3. for static method, x=10 (at the top of file), will be used for addition
sm = NumberAdder(x=1)
sm.AddStaticMethod(y=2) # staticmethod: 12
if __name__ == '__main__':
main()