This video introduces a new statement called the Class definition
that creates a new Python class also called a type.
It also generalizes assignment statement syntax and
semantics to support attribute references as targets.
A user-defined class or type allows us to group several objects into a single object.
A Python class definition statement creates a new user-defined class.
Consider this program that checks to see if two rectangles intersect or overlap.
The formula used to check for intersection is not the main point of this lesson,
but you might want to figure out how it works yourself.
I'll run the program.
The program reports that the first rectangle
whose top left corner is at the point 5,10 with width 15,
and height 20 does overlap the second rectangle whose top left corner is at the point 10,
5 with width 20, and height 15.
Even though I think of this program as computing whether two rectangle objects intersect,
there are no explicit rectangle objects in the program.
Instead, the program uses eight integer objects to describe two implicit rectangles.
This obscures both the intent and implementation code of the program.
For example, the argument list to the rectangles
intersect function is so long that it is easy to make typos when calling the function,
and when defining it as well.
The program would be much easier to read, write,
and modify if there were actually two explicit rectangle objects.
I have used a class definition statement to create
a rectangle type and have rewritten the program to use this type.
Running this program also displays overlap.
I used four attributes for each rectangle object;
x, y, width, and height.
Where x and y are integer coordinates of the top left corner of the rectangle.
Here is a simplified syntax diagram for a class definition statement.
The identifier is called the Class name.
Here is the simplified semantic rule for class definition.
Evaluate the classes suite using a new local namespace and the global namespace.
Create a class object and use
the new local namespace as the namespace of the class object.
If the class name is not in the original local namespace add it.
Bind the class name in the original local namespace to the new class object.
I will use the Rectangle program to show you how the class definition semantics work,
and to highlight the difference between new local namespace,
and original local namespace.
When the program starts,
the local block is the main module
whose namespace is both the local and global namespace.
The prebound identifier print is bound in this namespace.
The first statement in the main module is a function definition.
So, the interpreter applies function definition semantics.
It creates a function object with its own namespace.
It adds the identifier main to the local namespace.
Binds main to the function object,
and binds a reference to the main module namespace in the main function namespace.
Since the next two statements are also
function definitions for create rectangle and rectangles intersect,
the interpreter repeats these function definition semantics for each of these functions.
The next statement is a class definition.
So the interpreter applies class definition semantics.
In step one, the interpreter creates a new empty namespace as the local namespace.
It uses this local namespace and the current global namespace to evaluate the suite.
Recall that the suite in a function definition is
not evaluated when the function definition is evaluated.
Instead, the suite of
a function definition is only evaluated when the function is called.
However, the semantics for class definition prescribes that
the suite of a class definition is evaluated when the class definition is evaluated.
Since the suite only contains a past statement and the pass statement does nothing,
the suite is done.
In step two, a new class object is created,
and its namespace is the new empty local namespace.
In step three, the class name rectangle is added to the original local namespace,
the namespace of the main module.
In step four, rectangle is bound in this original local namespace,
main module to the new class object.
The last statement in the main module is a function call to main,
so the interpreter applies the function call semantics.
The identifier main is dereferenced in
the local namespace to obtain the main function object.
The main function code is evaluated to obtain a result object.
The local block is now the main function,
and the local namespace is the main function namespace.
The first statement is an assignment statement
whose expression is a function call to create rectangle.
Familiar function call semantics are used to find the function object;
create rectangle in the global namespace.
Evaluate the four argument expressions five,10,15,
and 20, and put them in an argument list.
The interpreter then adds the parameters corner x, corner y, width,
and height to create rectangles namespace,
binds each parameter to its corresponding argument,
and evaluates create_rectangles code.
The local block is now the create rectangle function,
and the local namespace is the create rectangle function namespace.
The first statement of create rectangle is an assignment statement
whose expression is a function call to a function whose name is rectangle.
When function call semantics are applied,
the identifier rectangle is used in
the global namespace defined the rectangle object which is a class.
In Python, classes are a kind of function.
So far you have seen several functions;
built-in functions like len,
user-defined functions like create_rectangle,
and built-in methods like lower.
A class is another kind of function that returns a new object whose type is that class.
For example, a call to int with no arguments creates the int objects zero,
and a call to str with no arguments creates the empty string object.
A call to rectangle with no arguments also creates a rectangle object,
but it doesn't have a very nice-looking human readable form.
Most Python documentation uses the term
callable instead of function to describe classes and methods.
However, whether we say that a class is a function or a callable,
it is evaluated like a function call,
and it creates a new object whose type is that class.
The assignment statement in create_rectangle binds
the identifier rect to the new rectangle object that is created by that rectangle call.
The second statement is an assignment statement where
the target rep.x is an attribute reference.
In the method calls lesson,
you saw the syntax and semantics of an attribute reference
used as an expression, such as hello.lower.
In the alternate forms of the import statement lesson,
you used attribute references to access a function in a module,
time.sleep, and a module in a module, os.path.
However, you have not seen an attribute reference
used as the target of an assignment statement in this course.
The current syntax diagrams for assignment support a target
that is either an identifier or a subscription expression.
Here is a generalized syntax diagram for
the target in an assignment statement that also supports
an attribute reference as
a target and the existing syntax diagram for attribute reference.
Finally, here is the semantic rule for
assignment when the target is an attribute reference.
Evaluate the assignment statement expression to obtain a result object.
Evaluate the attribute reference expression to obtain the base object.
If the identifier is not in the namespace of the base object, add it.
Bind the identifier in the namespace of the base object to the result object.
I'll apply this semantic rule to the second assignment statement in create_rectangle.
In step one, the expression corner_x is
evaluated to obtain the result object integer five.
In step two, the expression rect is
evaluated to obtain the base object, which is a rectangle.
In step three, the identifier x is not in the namespace of the base object,
which is an empty rectangle object,
so x is added to this namespace.
In step four, the identifier x in the namespace of
the rectangle object is bound to the result object integer five.
A new attribute called x has been added to the rectangle object,
and this new attribute has been bound to int object five.
Python lets a programmer create new attributes anywhere in
the code that the object containing the attribute can be referenced.
The next three assignment statements are interpreted similarly.
Notice that the parameter names, width,
and height happened to be the same as
the attributes width and height, but this doesn't matter.
The other two attributes,
x and y, are not the same as the parameters,
corner_x and corner_y, which were used to access the argument objects.
The return statement returns the rectangle object to the calling function main,
which adds the identifier rect1 to
its namespace and binds rect1 to this rectangle object.
The next assignment statement in function main is interpreted similarly so
that the identifier rect2 is bound to a second rectangle object.
The next statement in the main function is an if statement,
whose condition calls the function rectangles intersect.
The identifier rectangles_intersect is dereferenced in the global namespace.
Its two argument expressions are evaluated to obtain the two rectangle objects,
and they are put into an argument list.
The interpreter then adds the parameters r1 and r2 to the rectangle intersects namespace,
binds them to the argument rectangles,
and evaluates the rectangles_intersect code.
The local block is now the rectangle_intersect function,
and the local namespace is the rectangles_intersect function namespace.
This function contains a single return statement with a complex expression.
Each attribute reference, such as r1.x,
is evaluated using the semantic rule for
attribute reference from the method calls lesson.
In step one, the expression r1 is
evaluated in the local namespace to obtain the first rectangle object.
In step two, the attribute x is in the namespace of this rectangle object.
So, it is dereferenced to obtain the integer object five.
Similarly, when the attribute reference r2.width is evaluated,
the expression r2 is evaluated in
the local namespace to obtain the second rectangle object.
The attribute width is in the namespace of this rectangle object.
So, it is dereferenced to obtain the integer object 20.
Evaluation of the expression in the return statement results in the Boolean object true.
So, true is returned to the calling function main,
and the string overlap is displayed.
A class definition is used to define a new Python type.
Attributes in the namespace of objects of this type refer to component objects.
A rectangle object has four components integer objects: two that
represent x and y coordinates and
two that represent the width and height of the rectangle.
Programs that create explicit composite object types are easier to read, write,
and modify than programs that use
many simpler objects to implicitly represent composite objects.
You have already seen that a sequence can be used to reference multiple objects.
I could use lists in the program instead of creating a rectangle type.
Here is a program with the same functionality that uses lists.
Although the list program is shorter,
the numerical indexes in
the rectangles_intersect function are much
harder to understand than the attribute names x,
y, width, and height.
In fact, it took me five tries to successfully translate
the different components from the pre-rectangle program to indexes before I got it right.
In the end, I made a chart to increase my confidence that the translation was correct.
If I wanted to add more functions to this program that operate on rectangles,
I would need to keep the chart to remember which index represents which component.
In a program where more functions are written that operate on composite objects,
the increased clarity of using attributes rather than lists becomes even more apparent.
This video introduced a new statement called the class definition,
that creates a new Python class or type.
It also generalized assignment statement syntax and
semantics to support attributes references as targets.