Alexey Ulashchick
EPAM Systems
March 2014
1. Classes and Instances
2. Attribute accessing
3. Descriptors
4. OOP Principles
5. Desing Patterns
A user-defined prototype for an object that defines a set of attributes that characterize any object of the class
CLASS
OBJECT
A unique instance of a data structure that's defined by its class
class ClassName:
'Optional class documentation string'
pass
class Employee:
'Common base class for all employees'
empCount = 0
def __init__(self, name, salary):
self.name = name
self.salary = salary
Employee.empCount += 1
def displayCount(self):
print "Total Employee %d" % \
Employee.empCount
def displayEmployee(self):
print "Name : ", self.name, \
", Salary: ", self.salaryclass Inst:
def __init__(self, name):
self.name = name
def introduce(self):
print "Hello, my name is " + self.nameWhen creating an instance method, the first parameter is always self. You can name it anything you want, but the meaning will always be the same, and you should use self since it's the naming convention. self is (usually) passed hiddenly when calling an instance method; method has access to other instance variables and methods using self
class Cls:
name = 'World'
@classmethod
def introduce(cls):
print "Hello, I am %s!" % cls.nameIn class methods we pass the class itself as a first parameter.
Class method doesn't have access to instance
Should be called using class name:
Cls.introduce()class Cls:
@staticmethod
def introduce():
print "Hello, I am static method"Static method mounted to the class but doesn't have access neither to class, no instance.
Cls.introduce()__dict__ : Dictionary containing the class's namespace.
__doc__ : Class documentation string or None if undefined.
__name__: Class name.
__module__: Module name in which the class is defined. This attribute is "__main__" in interactive mode.
__bases__: A possibly empty tuple containing the base classes, in the order of their occurrence in the base class list
"This would create first object of Employee class"
emp1 = Employee("Zara", 2000)
"This would create second object of Employee class"
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print "Total Employee %d" % Employee.empCountgetattr(obj, name[, default]) : to access the attribute of object.
hasattr(obj,name) : to check if an attribute exists or not.
setattr(obj,name,value) : to set an attribute. If attribute does not exist, then it would be created.
delattr(obj, name) : to delete an attribute.
hasattr(emp1, 'salary') # Returns true if 'salary' attribute exists
getattr(emp1, 'salary') # Returns value of 'salary' attribute
getattr(emp1, 'salary', 0) # Returns value of 'salary' attribute when exists or 0
setattr(emp1, 'salary', 8) # Set attribute 'salary' at 8
delattr(emp1, 'salary') # Delete attribute 'salary'
getattr(emp1, 'empCount') # Search attribute in instance. When fail,
# continue search in classinstance
__dict__
instance class
__dict__
class ancestor
__dict__
class Car:
wheels = 4
def __init__(self, color):
self.color = color
car1 = Car('blue')
car2 = Car('red')
print car1.wheels # Prints 4
print car2.wheels # Prints 4
car1.wheels = 10 # Set wheels count to 10
print car1.wheels # Prints 10
print car2.wheels # 4
print Car.wheels # 4Encapsulation
Polymorphism
Inheritance
Object-oriented programming (OOP) is a programming language model organized around objects rather than "actions" and data rather than logic
class Person:
def __init__(self, name):
self._name = name
def set_name(self, name):
self._name = 'Mr. '+ name
def get_name(self, name):
return ['<', self._name, '>'].join(' ')class Deck(object):
def __init__(self):
self._cards = []
@property
def card( self ):
return self._cards
@card.setter
def card( self, aCard ):
self._cards.append( aCard )
@card.deleter
def card( self ):
self._cards.pop(-1)Since setter (and deleter) properties are created from the getter property, we must always define a getter property first
To be recognized as a descriptor, a class must implement any combination of the following three methods.
__get__(self, instance, owner) → object
__set__(self, instance, value)
__delete__(self, instance)
class Descriptor(object):
def __init__(self):
self._name = ''
def __get__(self, instance, owner):
print "Getting: %s" % self._name
return self._name
def __set__(self, instance, name):
print "Setting: %s" % name
self._name = name.title()
def __delete__(self, instance):
print "Deleting: %s" %self._name
del self._name
class Person(object):
name = Descriptor()Inheritance is when an object or class is based on another object or class, using the same implementation (inheriting from a class) specifying implementation to maintain the same behavior
Inheritance is used to indicate that one class will get most or all of its features from a parent class.
When you are doing this kind of specialization, there are three ways that the parent and child classes can interact:
Method defined in parent, but not in child
class Parent(object):
def implicit(self):
print "PARENT implicit()"
class Child(Parent):
pass
dad = Parent()
son = Child()
dad.implicit() # print PARENT implicit()
son.implicit() # print PARENT implicit()Child will just extend Parent and add some behavior
(Methods, Properties)
class Parent(object):
def override(self):
print "PARENT"
class Child(Parent):
def override(self):
print "CHILD"
dad = Parent()
son = Child()
dad.override() #print PARENT
son.override() #print CHILDThe problem with having functions called implicitly is sometimes you want the child to behave differently. In this case you want to override the function in the child, effectively replacing the functionality. To do this just define a function with the same name in Child.
class Parent(object):
def altered(self):
print "PARENT"
class Child(Parent):
def altered(self):
print "BEFORE ",
super(Child, self).altered()
print " AFTER",
dad = Parent()
son = Child()
dad.altered() #print PARENT
son.altered() #print BEFORE PARENT AFTERThe third way to use inheritance is a special case of overriding where you want to alter the behavior before or after the Parent class's version runs. You first override the function just like in the last example, but then you use a Python built-in function named super to get the Parent version to call
class A:
def show_message(self):
print "I’m class A"
class B:
def show_message(self):
print "I’m class B"
class C(A,B):
pass
instance = C()
instance.show_message()Class C that inherits from classes A and B at the same time.
In this case, whenever you have implicit actions on instance, Python has to look-up the possible function in the class hierarchy for both A and B subclasses, but it needs to do this in a consistent order. To do this Python uses "method resolution order" (MRO) and an algorithm called C3 to get it straight.
class A: x = 'a'
class B(A): pass
class C(A): x = 'c'
class D(B, C): pass
print D.x # print 'a'class A(object): x = 'a'
class B(A): pass
class C(A): x = 'c'
class D(B, C): pass
print D.x # print 'a’MRO: D - B - A - C – A
MRO: D - B – C – A
So when looking up D.x, A is the first base in resolution order to solve it, thereby hiding the definition in C
A forced to come in resolution order only once and after all of its subclasses
You able to use __class__.mro() in new-style classes to observe resolution order
class Singleton(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance
if __name__ == '__main__':
s1=Singleton()
s2=Singleton()
if s1 == s2:
print "Same"
else:
print "Different"Singleton pattern is a design pattern that restricts the instantiation of a class to one object. This is useful when exactly one object is needed to coordinate actions across the system.
class Person:
def change_name(self, value):
self.name = value
return self
def set_age(self, value):
self.age = value
return self
def introduce(self):
print "Hello, my name is", self.name, "and I am", self.age, "years old."
person = Person()
person.change_name("Peter").set_age(21).introduce()Each method returns an object, allowing the calls to be chained together in a single statement.
Chaining is syntactic sugar which eliminates the need for intermediate variables.
class HugeBusinessObject(object):
def __init__(self, name, surname, job_title):
self.name, self.surname, self.job_title = name, surname, job_title
def transform(self, condition):
# some transformation logic
pass
class Factory(object):
@staticmethod
def get_simple_person():
return HugeBusinessObject('Mike', 'Petrov', 'cleaner')
@staticmethod
def get_complex_person():
obj = HugeBusinessObject('Vasya', 'Ivanov', 'manager')
obj.transform()
return objPattern strive to aggregate object creation and initialization in separated factory classes.
def make_bold(fn):
def wrapped(*args, **kwargs):
return ''.join(['<b>', fn(*args, **kwargs), '</b>'])
return wrapped
class Dom:
def __init__(self, text):
self.text = text
@make_bold
def capitalized(self):
return self.text.upper()
print Dom('hello').capitalized() # <b>HELLO</b>
Decorator pattern (also known as Wrapper) is a design pattern that allows behavior to be added to an object
def memoize(f):
def wrapped(*args, **kwargs):
if hasattr(wrapped, '_cached_val'):
return wrapped._cached_val
result = f(*args, **kwargs)
wrapped._cached_val = result
return result
return wrapped
@memoize
def expensive_function():
print "Computing expensive function..."
Memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.
class StrategyExample :
def __init__(self, func=None) :
if func :
self.execute = func
def execute(self) :
print "Original execution"
def executeReplacement1() :
print "Strategy 1"
def executeReplacement2() :
print "Strategy 2"
if __name__ == "__main__" :
strat0 = StrategyExample()
strat1 = StrategyExample(executeReplacement1)
strat2 = StrategyExample(executeReplacement2)
strat0.execute()
strat1.execute()
strat2.execute()
Strategy pattern (also known as the policy pattern) is a software design pattern that enables an algorithm's behavior to be selected at runtime. The strategy pattern
class BaseTest(object):
def before(self):
# Initialization (open connection, crete huge instances, prepare data)
pass
def after(self):
# Finalization (clos connections, destory instances)
pass
class Test1(BaseTest):
def test_check_1(self):
pass
def test_check_2(self):
pass
def test_check_3(self):
pass
class Test1(BaseTest):
def test_check_1(self):
result = make_action()
assert.check_data(expected, result)
def test_check_2(self):
result = make_action_2()
assert.check_data(expected, result)
Test
Service
API
Testing object