Preface :
In this chapter, we discuss Python classes, which can be used in a manner analogous to C structures but which can also be used in a full object-oriented manner. For the benefit of readers who aren’t object-oriented programmers, we’ll discuss the use of classes as structures in the first two subsections. The remainder of the chapter discusses OOP in Python. This is only a description of the constructs available in Python; it’s not an exposition on object-oriented programming itself.
This chapter covers :
Defining classes :
A class in Python is effectively a data type. All the data types built into Python are classes, and Python gives you powerful tools to manipulate every aspect of a class’s behavior. You define a class with the class statement :
- class MyClass:
- body
- Using a class instance as a structure or record
Class instances can be used as structures or records. Unlike C structures, the fields of an instance don’t need to be declared ahead of time but can be created on the fly. The following short example defines a class called Circle, creates a Circle instance, assigns to the radius field of the circle, and then uses that field to calculate the circumference of the circle :
Like Java and many other languages, the fields of an instance/structure are accessed and assigned to by using dot notation. You can initialize fields of an instance automatically by including an __init__ initialization method in the class body. This function is run every time an instance of the class is created, with that new instance as its first argument. The __init__ method is similar to a constructor in Java, but it doesn’t really construct anything—it initializes fields of the class. This example creates circles with a radius of 1 by default :
- class Circle:
- def __init__(self): # (1)
- self.radius = 1
- my_circle = Circle() # (2)
- print(2 * 3.14 * my_circle.radius) # (3)
- my_circle.radius = 5 # (4)
- print(2 * 3.14 * my_circle.radius) # (5)
Instance variables :
Instance variables are the most basic feature of OOP. Take a look at the Circle class again :
- class Circle:
- def __init__(self):
- self.radius = 1
If the variable doesn’t already exist, it’s created automatically. This is how __init__ creates the radius variable.
All uses of instance variables—both assignment and access—require explicit mention of the containing instance—that is, instance.variable. A reference to variable by itself is a reference not to an instance variable but rather to a local variable in the executing method. This is different from C++ or Java, where instance variables are referred to in the same manner as local method function variables. I rather like Python’s requirement for explicit mention of the containing instance, because it clearly distinguishes instance variables from local function variables :
Methods :
A method is a function associated with a particular class. You’ve already seen the special __init__ method, which is called on a new instance when that instance is first created. In the following example, we define another method, area, for the Circle class, which can be used to calculate and return the area for any Circle instance. Like most user-defined methods, area is called with a method invocation syntax that resembles instance variable access :
Method invocation syntax consists of an instance, followed by a period, followed by the method to be invoked on the instance. The previous syntax is sometimes calledbound method invocation. area() can also be invoked as an unbound method by accessing it through its containing class. This is less convenient and is almost never done. When a method is invoked in this manner, its first argument must be an instance of the class in which that method is defined :
Like __init__, the area() method is defined as a function within the body of the class definition. The first argument of any method is the instance it was invoked by or on, named self by convention. Methods can be invoked with arguments, if the method definitions accept those arguments.
This version of Circle adds an argument to the __init__ method, so that we can create circles of a given radius without needing to set the radius after a circle is created :
- class Circle:
- def __init__(self, radius):
- self.radius = radius
- def area(self):
- return self.radius * self.radius * 3.14159
All the standard Python function features—default argument values, extra arguments, keyword arguments, and so forth—can be used with methods. For example, we could have defined the first line of __init__ to be :
- def __init__(self, radius=1):
There’s nothing magical about method invocation in Python. It can be considered shorthand for normal function invocation. Given a method invocationinstance.method(arg1, arg2, . . .), Python transforms it into a normal function call by using the following rules :
Class variables :
A class variable is a variable associated with a class, not an instance of a class, and is accessed by all instances of the class, in order to keep track of some class-level information, such as how many instances of the class have been created at any point in time. Python provides class variables, although using them requires slightly more effort than in most other languages. Also, you need to watch out for an interaction between class and instance variables.
A class variable is created by an assignment in the class body, not in the __init__ function; after it has been created, it can be seen by all instances of the class. We can use a class variable to make a value for pi accessible to all instances of the Circle class :
- class Circle:
- pi = 3.14159
- def __init__(self, radius):
- self.radius = radius
- def area(self):
- return self.radius * self.radius * Circle.pi
This is exactly how we would expect a class variable to act. It’s associated with and contained in the class that defines it. Notice in this example that we’re accessingCircle.pi before any circle instances have been created. Obviously, Circle.pi exists independently of any specific instances of the Circle class.
You can also access a class variable from a method of a class, through the class name. We do so in the definition of Circle.area(), where the area function makes specific reference to Circle.pi. In operation, this has the desired effect; the correct value for pi is obtained from the class and used in the calculation :
You may object to hardcoding the name of a class inside that class’s methods. You can avoid doing so through use of the special __class__ attribute, available to all Python class instances. This attribute returns the class of which the instance is a member, for example :
The class named Circle is represented internally by an abstract data structure, and that data structure is exactly what is obtained from the __class__ attribute of c, an instance of the Circle class. This lets us obtain the value of Circle.pi from c without ever explicitly referring to the Circle class name :
Of course, we could use this internally in the area method to get rid of the explicit reference to the Circle class; replace Circle.pi with self.__class__.pi.
- An oddity with class variables
There’s a bit of an oddity with class variables that can trip you up if you aren’t aware of it. When Python is looking up an instance variable, if it can’t find an instance variable of that name, it will then try to find and return the value in a class variable of the same name. Only if it can’t find an appropriate class variable will it signal an error. This does make it efficient to implement default values for instance variables; just create a class variable with the same name and appropriate default value, and avoid the time and memory overhead of initializing that instance variable every time a class instance is created. But this also makes it easy to inadvertently refer to an instance variable rather than a class variable, without signaling an error. Let’s look at how this operates in conjunction with the previous example.
First, we can refer to the variable c.pi, even though c doesn’t have an associated instance variable named pi. Python will first try to look for such an instance variable, but when it can’t find it, it will then look for a class variable pi in Circle and find it :
(This may or may not be what you want; it’s convenient but can be prone to error, so be careful.)
Now, what happens if we attempt to use c.pi as a true class variable, by changing it from one instance with the intent that all instances should see the change? Again, we’ll use the earlier definition for Circle :
This doesn’t work as it would for a true class variable—c1 now has its own copy of pi, distinct from the Circle.pi accessed by c2. This is because the assignment to c1.picreates an instance variable in c1; it doesn’t affect the class variable Circle.pi in any way. Subsequent lookups of c1.pi return the value in that instance variable, whereas subsequent lookups of c2.pi look for an instance variable pi in c2, fail to find it, and resort to returning the value of the class variable Circle.pi. If you want to change the value of a class variable, access it through the class name, not through the instance variable self.
Static methods and class methods :
Python classes can also have methods that correspond explicitly to static methods in a language such as Java. In addition, Python has class methods, which are a bit more advanced.
- Static methods
Just as in Java, you can invoke static methods even though no instance of that class has been created, although you can call them using a class instance. To create a static method, use the @staticmethod decorator, as shown in listing below :
Now, interactively type the following :
Also notice that documentation strings are used. In a real module, you’d probably put in more informative strings, indicating in the class docstring what methods are available and including usage information in the method docstrings :
- Class methods
Class methods are similar to static methods in that they can be invoked before an object of the class has been instantiated or by using an instance of the class. But class methods are implicitly passed the class they belong to as their first parameter, so you can code them more simply, as in listing below :
The @classmethod decorator is used before the method def (1). The class parameter is traditionally cls (2). You can use cls instead of self.__class__ (3).
By using a class method instead of a static method, we don’t have to hardcode the class name into total_area. That means any subclasses of Circle can still call total_areaand refer to their own members, not those in Circle :
Inheritance :
Inheritance in Python is easier and more flexible than inheritance in compiled languages such as Java and C++ because the dynamic nature of Python doesn’t force as many restrictions on the language. To see how inheritance is used in Python, we start with the Circle class given previously and generalize. We might want to define an additional class for squares :
- class Square:
- def __init__(self, side=1):
- self.side = side
- class Square:
- def __init__(self, side=1, x=0, y=0):
- self.side = side
- self.x = x
- self.y = y
- class Circle:
- def __init__(self, radius=1, x=0, y=0):
- self.radius = radius
- self.x = x
- self.y = y
- class Shape:
- def __init__(self, x, y):
- self.x = x
- self.y = y
- class Square(Shape): #Says Square inherits from Shape
- def __init__(self, side=1, x=0, y=0):
- super().__init__(x, y) #Must call __init__ method of Shape
- self.side = side
- class Circle(Shape): #Says Circle inherits from Shape
- def __init__(self, r=1, x=0, y=0):
- super().__init__(x, y) # Must call __init__ method of Shape
- self.radius = r
Instead of using super(), we could call Shape’s __init__ by explicitly naming the inherited class using Shape.__init__(self, x, y), which would also call the Shapeinitialization function with the instance being initialized. This wouldn’t be as flexible in the long run, because it hardcodes the inherited class’s name, which could be a problem later if the design and the inheritance hierarchy change. On the other hand, the use of super can be tricky in more complex cases. Because the two methods don’t exactly mix well, clearly document whichever approach you use in your code.
Inheritance comes into effect when you attempt to use a method that isn’t defined in the base classes but is defined in the superclass. To see this, let’s define another method in the Shape class called move, which will move a shape by a given displacement. It will modify the x and y coordinates of the shape by an amount determined by arguments to the method. The definition for Shape now becomes :
- class Shape:
- def __init__(self, x, y):
- self.x = x
- self.y = y
- def move(self, delta_x, delta_y):
- self.x = self.x + delta_x
- self.y = self.y + delta_y
The Circle class didn’t define a move method immediately itself, but because it inherits from a class that implements move, all instances of Circle can make use of move.
Inheritance with class and instance variables :
Inheritance allows an instance to inherit attributes of the class. Instance variables are associated with object instances, and only one instance variable of a given name exists for a given instance. To see this, consider the following example. Using these class definitions :
execute the following code :
The object c in this example is an instance of class C. C inherits from P, but c doesn’t inherit from some invisible instance of class P. It inherits methods and class variables directly from P. Because there is only one instance (c), any reference to the instance variable x in a method invocation on c must refer to c.x. This is true regardless of which class defines the method being invoked on c. As you can see, when they’re invoked on c, both set_p and print_p, defined in class P, refer to the same variable referred to by set_c and print_c when they’re invoked on c.
In general, this is what is desired for instance variables because it makes sense that references to instance variables of the same name should refer to the same variable. Occasionally, somewhat different behavior is desired, which you can achieve using private variables. These are explained in the next subsection.
Class variables are inherited, but you should take care to avoid name clashes and be aware of a generalization of the same behavior you saw in the earlier subsection on class variables. In our example, a class variable z is defined for the superclass P. It can be accessed in three different ways: through the instance c, through the derived class C, or directly through the superclass P :
But if we try setting it through the class C, a new class variable will be created for the class C. This has no effect on P’s class variable itself (as accessed through P). But future accesses through the class C or its instance c will see this new variable rather than the original :
Similarly, if we try setting z through the instance c, a new instance variable will be created, and we’ll end up with three different variables :
Supplement :
* [Quick Python] 15. Classes and object-oriented programming - Part 2
沒有留言:
張貼留言