UNDERSTANDING WHAT CLASSES ARE, WHEN TO USE THEM, AND HOW THEY CAN BE USEFUL
The class is a fundamental building block in Python. It is the underpinning for not only many popular programs and libraries, but the Python standard library as well. Understanding what classes are, when to use them, and how they can be useful is essential. The class in Python is a mixture of the class mechanisms found in C++ and Modula-3, provide all the standard features of Object Oriented Programming. In a Python program all data is reprezented by objects or by relations between objects.
Every object has:
• identity: is an integer (or long integer) which is guaranteed to be unique and constant for this object during its lifetime. Two objects with non-overlapping lifetimes may have the same identity value. This is the address of the object in memory.
Objects are created at runtime, and can be modified further after creation. Object types:
The type of an object is itself an object. This type object is uniquely defined and is always the same for all instances of a given type. To get the type of an object, you can use the built-in type() function. Passing an object as the only parameter will return the type object of that object. type() will only return the immediate type of the object, but won’t be able to tell you about type inheritance. To cover this you can use isinstance() function. There are 2 types of objects:
immutable: int, long, float, complex, str/unicode bytes, tuple, frozenset
mutable: list, dict, set, byte-array
class ClassName: statement-1... statement-n
In practice, the statements inside a class definition will usually be function definitions, but other statements are allowed. 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. In particular, function definitions bind the name of the new function here.
Class objects Class objects support two kinds of operations: attribute references and instantiation.
1. 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 Car: name = "XXX"def f(self):return "Car Class"
• Car.name and Car.f are valid attribute references,returning a string and a function object. 2. The instantiation operation creates an empty object.
car = Car() # creates a new instance of the class and assigns this object to the local variable car.
• the only operations understood by instance objects are attribute references. • there are two kinds of valid attribute names: data attributes and methods.
Class and instance Variables Instance variables are for data unique to each instance and class variables are for attributes and methods shared by all instances of the class.
class Car: mark = ‘XXX’ # class variable shared by all instancesdef __init__(self, color): self.color = color # instance variable unique to each instance
Shared data can have possibly surprising effects with involving mutable objects such as lists and dictionaries
Typically, an object goes through the following phases during it’s lifetime:
1. Definition: Python defines its classes with keyword class which is defined in Python interpreter.
2. Initialization: When an instance of the class is created, __init__ method defined in the class is called. It initializes the attributes for newly created class instance. A namespace is also allocated for object’s instance variables.
3. Access and Manipulation: Methods defined in a class can be used for accessing or modifying the state of an object. These are accessors and manipulators respectively. A class instance is used to call these methods
4. Destruction: Every object that gets created, needs to be destroyed. This is done with Python garbage collection
Inheritance and composition
Inheritance is used to indicate that one class will get most or all of its features from a parent class. There are three ways that the parent and child classes can interact:
1. Actions on the child imply an action on the parent.
2. Actions on the child override the action on the parent.
3. Actions on the child alter the action on the parent.
1. implicit actions that happen when you define a function in the parent, but not in the child
2. you want to override the function in the child, effectively replacing the functionality
3. special case of overriding where you want to alter the behavior before or after the Parent class's version runs Another way to do the exact same thing is composition, just to use other classes and modules, rather than rely on implicit inheritance. If you look at the three ways to exploit inheritance, two of the three involve writing new code to replace or alter functionality. This can easily be replicated by just calling functions in a module.
1. Use composition to package code into modules that are used in many different unrelated places and situations.
2. Use inheritance only when there are clearly related reusable pieces of code that fit under a single common concept or if you have to because of something you're using.
NewClass vs ClassicClass
• A New Class is the recommended way to create a class in modern Python.
• A Classic Class or old-style class is a class as it existed in Python 2.1 and before. They have been retained for backwards compatibility. The syntax for the two types looks the same:
class Old1:...class Old2(Old1, UserDict): # Assuming UserDict is still old-style…class New1(object):...class New2(New1):…
• New Style classes can use descriptors (including __slots__), and Old Style classes cannot • Python method resolution order works different
A method is a function that is stored as a class attribute. Example:
class Car(object):def __init__(self, name): self.name = name def get_name(self):return self.name >>> Car.get_name(Car(“XXX”))
• it binds all the methods from the class Car to any instance of this class. This means that the attribute get_name of an instance of Car is a bound method: a method for which the first argument will be the instance itself.(example) • In Python 3, the functions attached to a class are not considered as unbound method anymore, but as simple functions, that are bound to an object if required. So the principle stays the same, the model is just simplified.
Static methods are a special case of methods.
class Car(object):@staticmethoddef calculate_power(kw):return 1.34 * kw def power(self):return self.calculate_power(self.kw)
Writing calculate_power as a non-static method would work too, but it would provide it a self argument that would not be used. Here, the decorator @staticmethod buys us several things: • Python doesn't have to instantiate a bound-method for each Car object we instantiate. Bound methods are objects too, and creating them has a cost. Having a static method avoids that. • It eases the readability of the code: seeing @staticmethod, we know that the method does not depend on the state of object itself; • It allows us to override the calculate_power method in a subclass. If we used a function calculate_power defined at the top-level of our module, a class inheriting from Car wouldn't be able to change the way we calculate power for our car without overriding power itself.
Class method are methods that are not bound to an object, but are bound to a class:
class Car(object): name=’XXX’@classmethoddef get_name(cls):return cls.name
• it will be always bound to the class it is attached too, and its first argument will be the class itself (classes are objects too). Well class methods are mostly useful for two types of methods: • Factory methods, that are used to create an instance for a class using for example some sort of pre-processing. If we use a @staticmethod instead, we would have to hardcode the Car class name in our function, making any class inheriting from Car unable to use our factory for its own use • Static methods calling static methods: if you split a static methods in several static methods, you shouldn't hard-code the class name but use class methods. Using this way to declare a method, the Car name is never directly referenced and inheritance and method overriding will work flawlessly
An abstract method is a method defined in a base class, but that may not provide any implementation. In Java, it would describe the methods of an interface.
class Car(object):def calculate_power(self):raise NotImplenetedError
• Any class inheriting from Car should implement and override the calculate_power method, otherwise an exception would be raised. • If you write a class that inherits from Car and forget to implement calculate_power, the error will only be raised when you'll try to use that method. • There's a way to triggers this way earlier, when the object is being instantiated, using the abc module that's provided with Python. Using abc and its special class, as soon as you'll try to instantiate BaseCar or any class inheriting from it, you'll get a TypeError.
class BaseCar(object): __metaclass__ = abc.ABCMeta@abc.abstractmethod def get_color(self):"Method that should do something"
self and __init__ The first argument of every class method, including the __init__() method, is always a reference to the current instance of the class. By convention, this argument is named self. self is not a reserved word in Python, merely a naming convention. In all class methods, self refers to the instance whose method was called. But in the specific case of the __init__() method, the instance whose method was called is also the newly created object. You need to specify self explicitly when defining the method, you do not specify it when calling the method; Python will add it for you automatically.
class Person:def __init__(self, name): self.name = name def sayHi(self):print "Hello, my name is", self.name
The __init__() method is called immediately after an instance of the class is created. It would be tempting — but technically incorrect — to call this the “constructor” of the class. It is Incorrect, because the object has already been constructed by the time the __init__() method is called, and you already have a valid reference to the new instance of the class.
Is called to create a new instance of class cls. It is a static method that takes the class of which an instance was requested as its first argument. The remaining arguments are those passed to the object constructor expression (the call to the class). The return value of __new__() should be the new object instance (usually an instance of cls). __new__() is intended mainly to allow subclasses of immutable types (like int, str, or tuple) to customize instance creation. It is also commonly overridden in custom metaclasses in order to customize class creation.
Is called when the instance is about to be destroyed (is also called a destructor). If a base class has a __del__() method, the derived class’s __del__() method, if any, must explicitly call it to ensure proper deletion of the base class part of the instance. Note that it is possible (though not recommended!) for the __del__() method to postpone destruction of the instance by creating a new reference to it. It may then be called at a later time when this new reference is deleted. It is not guaranteed that __del__() methods are called for objects that still exist when the interpreter exits. Example, how you can count instance with class attributes. All we have to do is:
• to create a class attribute, which we call "counter" in our example
• to increment this attribute by 1 every time a new instance will be create
• to decrement the attribute by 1 every time an instance will be destroyed?
Python's class attributes and object attributes are stored in separate dictionaries:
• object.__dict__: holds object attributes
• class.__dict__: holds class attributes and methods
By default Python uses a dict to store an object’s instance attributes. Which is usually fine, and it allows fully dynamic things like setting arbitrary new attributes at runtime. However, for small classes that have a few fixed attributes known at “compile time”, the dict is a waste of RAM, and this makes a real difference when you’re creating a million of them. You can tell Python not to use a dict, and only allocate space for a fixed set of attributes, by settings __slots__ on the class to a fixed list of attribute names:
class Image(object): __slots__ = ['id', 'caption', 'url']def __init__(self, id, caption, url): self.id = id self.caption = caption self.url = url
You can find other intersesting articles on similar topics here.