Classes
Classes
Classes
**********
By the way, I use the word *attribute* for any name following a dot
--- for example, in the expression "z.real", "real" is an attribute of
the object "z". Strictly speaking, references to names in modules are
attribute references: in the expression "modname.funcname", "modname"
is a module object and "funcname" is an attribute of it. In this case
there happens to be a straightforward mapping between the module's
attributes and the global names defined in the module: they share the
same namespace! [1]
Usually, the local scope references the local names of the (textually)
current function. Outside functions, the local scope references the
same namespace as the global scope: the module's namespace. Class
definitions place yet another namespace in the local scope.
def scope_test():
def do_local():
spam = "local spam"
def do_nonlocal():
nonlocal spam
spam = "nonlocal spam"
def do_global():
global spam
spam = "global spam"
scope_test()
print("In global scope:", spam)
You can also see that there was no previous binding for *spam* before
the "global" assignment.
Classes introduce a little bit of new syntax, three new object types,
and some new semantics.
class ClassName:
<statement-1>
.
.
.
<statement-N>
*Attribute references* use the standard syntax used for all attribute
references in Python: "obj.name". Valid attribute names are all the
names that were in the class's namespace when the class object was
created. So, if the class definition looked like this:
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
x = MyClass()
creates a new *instance* of the class and assigns this object to the
local variable "x".
def __init__(self):
self.data = []
x = MyClass()
x.counter = 1
while x.counter < 10:
x.counter = x.counter * 2
print(x.counter)
del x.counter
x.f()
In the "MyClass" example, this will return the string "'hello world'".
However, it is not necessary to call a method right away: "x.f" is a
method object, and can be stored away and called at a later time. For
example:
xf = x.f
while True:
print(xf())
What exactly happens when a method is called? You may have noticed
that "x.f()" was called without an argument above, even though the
function definition for "f()" specified an argument. What happened to
the argument? Surely Python raises an exception when a function that
requires an argument is called without any --- even if the argument
isn't actually used...
Actually, you may have guessed the answer: the special thing about
methods is that the instance object is passed as the first argument of
the function. In our example, the call "x.f()" is exactly equivalent
to "MyClass.f(x)". In general, calling a method with a list of *n*
arguments is equivalent to calling the corresponding function with an
argument list that is created by inserting the method's instance
object before the first argument.
class Dog:
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.kind # shared by all dogs
'canine'
>>> e.kind # shared by all dogs
'canine'
>>> d.name # unique to d
'Fido'
>>> e.name # unique to e
'Buddy'
As discussed in A Word About Names and Objects, shared data can have
possibly surprising effects with involving *mutable* objects such as
lists and dictionaries. For example, the *tricks* list in the
following code should not be used as a class variable because just a
single list would be shared by all *Dog* instances:
class Dog:
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks # unexpectedly shared by all dogs
['roll over', 'play dead']
class Dog:
>>> d = Dog('Fido')
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')
>>> e.add_trick('play dead')
>>> d.tricks
['roll over']
>>> e.tricks
['play dead']
>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east
Clients should use data attributes with care --- clients may mess up
invariants maintained by the methods by stamping on their data
attributes. Note that clients may add data attributes of their own to
an instance object without affecting the validity of the methods, as
long as name conflicts are avoided --- again, a naming convention can
save a lot of headaches here.
class C:
f = f1
def g(self):
return 'hello world'
h = g
Now "f", "g" and "h" are all attributes of class "C" that refer to
function objects, and consequently they are all methods of instances
of "C" --- "h" being exactly equivalent to "g". Note that this
practice usually only serves to confuse the reader of a program.
class Bag:
def __init__(self):
self.data = []
Each value is an object, and therefore has a *class* (also called its
*type*). It is stored as "object.__class__".
9.5. Inheritance
================
class DerivedClassName(BaseClassName):
<statement-1>
.
.
.
<statement-N>
The name "BaseClassName" must be defined in a scope containing the
derived class definition. In place of a base class name, other
arbitrary expressions are also allowed. This can be useful, for
example, when the base class is defined in another module:
class DerivedClassName(modname.BaseClassName):
class Mapping:
def __init__(self, iterable):
self.items_list = []
self.__update(iterable)
class MappingSubclass(Mapping):
Note that the mangling rules are designed mostly to avoid accidents;
it still is possible to access or modify a variable that is considered
private. This can even be useful in special circumstances, such as in
the debugger.
Notice that code passed to "exec()" or "eval()" does not consider the
classname of the invoking class to be the current class; this is
similar to the effect of the "global" statement, the effect of which
is likewise restricted to code that is byte-compiled together. The
same restriction applies to "getattr()", "setattr()" and "delattr()",
as well as when referencing "__dict__" directly.
class Employee:
pass
By now you have probably noticed that most container objects can be
looped over using a "for" statement:
>>> s = 'abc'
>>> it = iter(s)
>>> it
<iterator object at 0x00A1DB50>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
next(it)
StopIteration
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
self.index = self.index - 1
return self.data[self.index]
9.9. Generators
===============
def reverse(data):
for index in range(len(data)-1, -1, -1):
yield data[index]
Anything that can be done with generators can also be done with class-
based iterators as described in the previous section. What makes
generators so compact is that the "__iter__()" and "__next__()"
methods are created automatically.
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".
Examples:
-[ Footnotes ]-
[1] Except for one thing. Module objects have a secret read-only
attribute called "__dict__" which returns the dictionary used to
implement the module's namespace; the name "__dict__" is an
attribute but not a global name. Obviously, using this violates
the abstraction of namespace implementation, and should be
restricted to things like post-mortem debuggers.