-- Decorators
decorators/time.measure.start.py
-----------------------------------------------------------------------
from time import sleep, time
def f():
sleep(.3)
def g():
sleep(.5)
t = time()
f()
print('f took: ', time() - t) # f took: 0.3003859519958496
t = time()
g()
print('g took:', time() - t) # g took: 0.5005719661712646
-----------------------------------------------------------------------
decorators/time.measure.dry.py
-----------------------------------------------------------------------
from time import sleep, time
def f():
sleep(.3)
def g():
sleep(.5)
def measure(func):
t = time()
func()
print(func.__name__, 'took:', time() - t)
measure(f) # f took: 0.30041074752807617
measure(g) # g took: 0.5006198883056641
-----------------------------------------------------------------------
decorators/time.measure.arguments.py
-----------------------------------------------------------------------
from time import sleep, time
def f(sleep_time=0.1):
sleep(sleep_time)
def measure(func, *args, **kwargs):
t = time()
func(*args, **kwargs)
print(func.__name__, 'took:', time() - t)
measure(f, sleep_time=0.3) # f took: 0.3004162311553955
measure(f, 0.2) # f took: 0.20028162002563477
-----------------------------------------------------------------------
decorators/time.measure.deco1.py
-----------------------------------------------------------------------
from time import sleep, time
def f(sleep_time=0.1):
sleep(sleep_time)
def measure(func):
def wrapper(*args, **kwargs):
t = time()
func(*args, **kwargs)
print(func.__name__, 'took:', time() - t)
return wrapper
f = measure(f) # decoration point
f(0.2) # f took: 0.2002875804901123
f(sleep_time=0.3) # f took: 0.3003721237182617
print(f.__name__) # wrapper <- ouch!
-----------------------------------------------------------------------
decorators/syntax.py
-----------------------------------------------------------------------
def func(arg1, arg2, ...):
pass
func = decorator(func)
# is equivalent to the following:
@decorator
def func(arg1, arg2, ...):
pass
-----------------------------------------------------------------------
decorators/syntax.py
-----------------------------------------------------------------------
def func(arg1, arg2, ...):
pass
func = deco1(deco2(func))
# is equivalent to the following:
@deco1
@deco2
def func(arg1, arg2, ...):
passWhen applying multiple
-----------------------------------------------------------------------
the closer the decorator to the function, the sooner it is applied.
decorators/syntax.py
-----------------------------------------------------------------------
def func(arg1, arg2, ...):
pass
func = decoarg(argA, argB)(func)
# is equivalent to the following:
@decoarg(argA, argB)
def func(arg1, arg2, ...):
pass
-----------------------------------------------------------------------
decorators/time.measure.deco2.py
-----------------------------------------------------------------------
from time import sleep, time
from functools import wraps
def measure(func):
@wraps(func)
def wrapper(*args, **kwargs):
t = time()
func(*args, **kwargs)
print(func.__name__, 'took:', time() - t)
return wrapper
@measure
def f(sleep_time=0.1):
"""I'm a cat. I love to sleep! """
sleep(sleep_time)
f(sleep_time=0.3) # f took: 0.30039525032043457
print(f.__name__, ':', f.__doc__)
# f : I'm a cat. I love to sleep!
-----------------------------------------------------------------------
decorators/two.decorators.py
-----------------------------------------------------------------------
from time import sleep, time
from functools import wraps
def measure(func):
@wraps(func)
def wrapper(*args, **kwargs):
t = time()
result = func(*args, **kwargs)
print(func.__name__, 'took:', time() - t)
return result
return wrapper
def max_result(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if result > 100:
print('Result is too big ({0}). Max allowed is 100.'
.format(result))
return result
return wrapper
@measure
@max_result
def cube(n):
return n ** 3
print(cube(2))
print(cube(5))
-----------------------------------------------------------------------
$ python two.decorators.py
A decorator factory
decorators/decorators.factory.py
-----------------------------------------------------------------------
from functools import wraps
def max_result(threshold):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if result > threshold:
print(
'Result is too big ({0}). Max allowed is {1}.'
.format(result, threshold))
return result
return wrapper
return decorator
@max_result(75)
def cube(n):
return n ** 3
print(cube(5))
-----------------------------------------------------------------------
$ python decorators.factory.py
decorators/decorators.factory.py
-----------------------------------------------------------------------
@max_result(75)
def cube(n):
return n ** 3
@max_result(100)
def square(n):
return n ** 2
@max_result(1000)
def multiply(a, b):
return a * b
-----------------------------------------------------------------------
-- Object-oriented programming
The simplest Python class
Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which are data structures that contain data, in the form of attributes, and code, in the form of functions known as methods.
oop/simplest.class.py
class Simplest(): # when empty, the braces are optional
pass
print(type(Simplest)) # what type is this object?
simp = Simplest() # we create an instance of Simplest: simp
print(type(simp)) # what type is simp?
# is simp an instance of Simplest?
print(type(simp) == Simplest) # There's a better way for this
$ python oop/simplest.class.py
Class and object namespaces
oop/class.namespaces.py
-----------------------------------------------------------------------
class Person():
species = 'Human'
print(Person.species) # Human
Person.alive = True # Added dynamically!
print(Person.alive) # True
man = Person()
print(man.species) # Human (inherited)
print(man.alive) # True (inherited)
Person.alive = False
print(man.alive) # False (inherited)
man.name = 'Darth'
man.surname = 'Vader'
print(man.name, man.surname) # Darth Vader
-----------------------------------------------------------------------
Attribute shadowing
oop/class.attribute.shadowing.py
-----------------------------------------------------------------------
class Point():
x = 10
y = 7
p = Point()
print(p.x) # 10 (from class attribute)
print(p.y) # 7 (from class attribute)
p.x = 12 # p gets its own 'x' attribute
print(p.x) # 12 (now found on the instance)
print(Point.x) # 10 (class attribute still the same)
del p.x # we delete instance attribute
print(p.x) # 10 (now search has to go again to find class attr)
p.z = 3 # let's make it a 3D point
print(p.z) # 3
print(Point.z)
# AttributeError: type object 'Point' has no attribute 'z'
-----------------------------------------------------------------------
I, me, and myself – using the self variable
oop/class.self.py
-----------------------------------------------------------------------
class Square():
side = 8
def area(self): # self is a reference to an instance
return self.side ** 2
sq = Square()
print(sq.area()) # 64 (side is found on the class)
print(Square.area(sq)) # 64 (equivalent to sq.area())
sq.side = 10
print(sq.area()) # 100 (side is found on the instance)
-----------------------------------------------------------------------
oop/class.price.py
-----------------------------------------------------------------------
class Price():
def final_price(self, vat, discount=0):
"""Returns price after applying vat and fixed discount."""
return (self.net_price * (100 + vat) / 100) - discount
p1 = Price()
p1.net_price = 100
print(Price.final_price(p1, 20, 10)) # 110 (100 * 1.2 - 10)
print(p1.final_price(20, 10)) # equivalent
-----------------------------------------------------------------------
Initializing an instance
oop/class.init.py
-----------------------------------------------------------------------
class Rectangle():
def __init__(self, sideA, sideB):
self.sideA = sideA
self.sideB = sideB
def area(self):
return self.sideA * self.sideB
r1 = Rectangle(10, 4)
print(r1.sideA, r1.sideB) # 10 4
print(r1.area()) # 40
r2 = Rectangle(7, 3)
print(r2.area()) # 21
-----------------------------------------------------------------------
OOP is about code reuse
By now it should be pretty clear: OOP is all about code reuse.
Inheritance and composition
oop/class.inheritance.py
-----------------------------------------------------------------------
class Engine():
def start(self):
pass
def stop(self):
pass
class ElectricEngine(Engine): # Is-A Engine
pass
class V8Engine(Engine): # Is-A Engine
pass
class Car():
engine_cls = Engine
def __init__(self):
self.engine = self.engine_cls() # Has-A Engine
def start(self):
print(
'Starting engine {0} for car {1}... Wroom, wroom!'
.format(
self.engine.__class__.__name__,
self.__class__.__name__)
)
self.engine.start()
def stop(self):
self.engine.stop()
class RaceCar(Car): # Is-A Car
engine_cls = V8Engine
class CityCar(Car): # Is-A Car
engine_cls = ElectricEngine
class F1Car(RaceCar): # Is-A RaceCar and also Is-A Car
engine_cls = V8Engine
car = Car()
racecar = RaceCar()
citycar = CityCar()
f1car = F1Car()
cars = [car, racecar, citycar, f1car]
for car in cars:
car.start()
""" Prints:
Starting engine Engine for car Car... Wroom, wroom!
Starting engine V8Engine for car RaceCar... Wroom, wroom!
Starting engine ElectricEngine for car CityCar... Wroom, wroom!
Starting engine V8Engine for car F1Car... Wroom, wroom!
"""
-----------------------------------------------------------------------
oop/class.issubclass.isinstance.py
-----------------------------------------------------------------------
car = Car()
racecar = RaceCar()
f1car = F1Car()
cars = [(car, 'car'), (racecar, 'racecar'), (f1car, 'f1car')]
car_classes = [Car, RaceCar, F1Car]
for car, car_name in cars:
for class_ in car_classes:
belongs = isinstance(car, class_)
msg = 'is a' if belongs else 'is not a'
print(car_name, msg, class_.__name__)
""" Prints:
car is a Car
car is not a RaceCar
car is not a F1Car
racecar is a Car
racecar is a RaceCar
racecar is not a F1Car
f1car is a Car
f1car is a RaceCar
f1car is a F1Car
"""
-----------------------------------------------------------------------
oop/class.issubclass.isinstance.py
-----------------------------------------------------------------------
for class1 in car_classes:
for class2 in car_classes:
is_subclass = issubclass(class1, class2)
msg = '{0} a subclass of'.format(
'is' if is_subclass else 'is not')
print(class1.__name__, msg, class2.__name__)
""" Prints:
Car is a subclass of Car
Car is not a subclass of RaceCar
Car is not a subclass of F1Car
RaceCar is a subclass of Car
RaceCar is a subclass of RaceCar
RaceCar is not a subclass of F1Car
F1Car is a subclass of Car
F1Car is a subclass of RaceCar
F1Car is a subclass of F1Car
"""
-----------------------------------------------------------------------
Accessing a base class
oop/super.duplication.py
-----------------------------------------------------------------------
class Book:
def __init__(self, title, publisher, pages):
self.title = title
self.publisher = publisher
self.pages = pages
class Ebook(Book):
def __init__(self, title, publisher, pages, format_):
self.title = title
self.publisher = publisher
self.pages = pages
self.format_ = format_
-----------------------------------------------------------------------
oop/super.explicit.py
-----------------------------------------------------------------------
class Book:
def __init__(self, title, publisher, pages):
self.title = title
self.publisher = publisher
self.pages = pages
class Ebook(Book):
def __init__(self, title, publisher, pages, format_):
Book.__init__(self, title, publisher, pages)
self.format_ = format_
ebook = Ebook('Learning Python', 'Packt Publishing', 360, 'PDF')
print(ebook.title) # Learning Python
print(ebook.publisher) # Packt Publishing
print(ebook.pages) # 360
print(ebook.format_) # PDF
-----------------------------------------------------------------------
oop/super.implicit.py
-----------------------------------------------------------------------
class Book:
def __init__(self, title, publisher, pages):
self.title = title
self.publisher = publisher
self.pages = pages
class Ebook(Book):
def __init__(self, title, publisher, pages, format_):
super().__init__(title, publisher, pages)
# Another way to do the same thing is:
# super(Ebook, self).__init__(title, publisher, pages)
self.format_ = format_
ebook = Ebook('Learning Python', 'Packt Publishing', 360, 'PDF')
print(ebook.title) # Learning Python
print(ebook.publisher) # Packt Publishing
print(ebook.pages) # 360
print(ebook.format_) # PDF
-----------------------------------------------------------------------
Multiple inheritance
oop/multiple.inheritance.py
-----------------------------------------------------------------------
class Shape:
geometric_type = 'Generic Shape'
def area(self): # This acts as placeholder for the interface
raise NotImplementedError
def get_geometric_type(self):
return self.geometric_type
class Plotter:
def plot(self, ratio, topleft):
# Imagine some nice plotting logic here...
print('Plotting at {}, ratio {}.'.format(
topleft, ratio))
class Polygon(Shape, Plotter): # base class for polygons
geometric_type = 'Polygon'
class RegularPolygon(Polygon): # Is-A Polygon
geometric_type = 'Regular Polygon'
def __init__(self, side):
self.side = side
class RegularHexagon(RegularPolygon): # Is-A RegularPolygon
geometric_type = 'RegularHexagon'
def area(self):
return 1.5 * (3 ** .5 * self.side ** 2)
class Square(RegularPolygon): # Is-A RegularPolygon
geometric_type = 'Square'
def area(self):
return self.side * self.side
hexagon = RegularHexagon(10)
print(hexagon.area()) # 259.8076211353316
print(hexagon.get_geometric_type()) # RegularHexagon
hexagon.plot(0.8, (75, 77)) # Plotting at (75, 77), ratio 0.8.
square = Square(12)
print(square.area()) # 144
print(square.get_geometric_type()) # Square
square.plot(0.93, (74, 75)) # Plotting at (74, 75), ratio 0.93.
-----------------------------------------------------------------------
Method resolution order
oop/multiple.inheritance.py
-----------------------------------------------------------------------
print(square.__class__.__mro__)
# prints:
# (<class '__main__.Square'>, <class '__main__.RegularPolygon'>,
# <class '__main__.Polygon'>, <class '__main__.Shape'>,
# <class '__main__.Plotter'>, <class 'object'>)
-----------------------------------------------------------------------
oop/mro.simple.py
-----------------------------------------------------------------------
class A:
label = 'a'
class B(A):
label = 'b'
class C(A):
label = 'c'
class D(B, C):
pass
d = D()
print(d.label) # Hypothetically this could be either 'b' or 'c'
-----------------------------------------------------------------------
oop/mro.py
-----------------------------------------------------------------------
class A:
label = 'a'
class B(A):
pass # was: label = 'b'
class C(A):
label = 'c'
class D(B, C):
pass
d = D()
print(d.label) # 'c'
print(d.__class__.mro()) # notice another way to get the MRO
# prints:
# [<class '__main__.D'>, <class '__main__.B'>,
# <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
-----------------------------------------------------------------------
Static and class methods
Static methods
oop/static.methods.py
-----------------------------------------------------------------------
class String:
@staticmethod
def is_palindrome(s, case_insensitive=True):
# we allow only letters and numbers
s = ''.join(c for c in s if c.isalnum()) # Study this!
# For case insensitive comparison, we lower-case s
if case_insensitive:
s = s.lower()
for c in range(len(s) // 2):
if s[c] != s[-c -1]:
return False
return True
@staticmethod
def get_unique_words(sentence):
return set(sentence.split())
print(String.is_palindrome(
'Radar', case_insensitive=False)) # False: Case Sensitive
print(String.is_palindrome('A nut for a jar of tuna')) # True
print(String.is_palindrome('Never Odd, Or Even!')) # True
print(String.is_palindrome(
'In Girum Imus Nocte Et Consumimur Igni') # Latin! Show-off!
) # True
print(String.get_unique_words(
'I love palindromes. I really really love them!'))
# {'them!', 'really', 'palindromes.', 'I', 'love'}
-----------------------------------------------------------------------
Class methods
oop/class.methods.factory.py
-----------------------------------------------------------------------
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def from_tuple(cls, coords): # cls is Point
return cls(*coords)
@classmethod
def from_point(cls, point): # cls is Point
return cls(point.x, point.y)
p = Point.from_tuple((3, 7))
print(p.x, p.y) # 3 7
q = Point.from_point(p)
print(q.x, q.y) # 3 7
-----------------------------------------------------------------------
oop/class.methods.split.py
-----------------------------------------------------------------------
class String:
@classmethod
def is_palindrome(cls, s, case_insensitive=True):
s = cls._strip_string(s)
# For case insensitive comparison, we lower-case s
if case_insensitive:
s = s.lower()
return cls._is_palindrome(s)
@staticmethod
def _strip_string(s):
return ''.join(c for c in s if c.isalnum())
@staticmethod
def _is_palindrome(s):
for c in range(len(s) // 2):
if s[c] != s[-c -1]:
return False
return True
@staticmethod
def get_unique_words(sentence):
return set(sentence.split())
print(String.is_palindrome('A nut for a jar of tuna')) # True
print(String.is_palindrome('A nut for a jar of beans')) # False
-----------------------------------------------------------------------
Private methods and name mangling
oop/private.attrs.py
-----------------------------------------------------------------------
class A:
def __init__(self, factor):
self._factor = factor
def op1(self):
print('Op1 with factor {}...'.format(self._factor))
class B(A):
def op2(self, factor):
self._factor = factor
print('Op2 with factor {}...'.format(self._factor))
obj = B(100)
obj.op1() # Op1 with factor 100...
obj.op2(42) # Op2 with factor 42...
obj.op1() # Op1 with factor 42... <- This is BAD
-----------------------------------------------------------------------
oop/private.attrs.fixed.py
-----------------------------------------------------------------------
class A:
def __init__(self, factor):
self.__factor = factor
def op1(self):
print('Op1 with factor {}...'.format(self.__factor))
class B(A):
def op2(self, factor):
self.__factor = factor
print('Op2 with factor {}...'.format(self.__factor))
obj = B(100)
obj.op1() # Op1 with factor 100...
obj.op2(42) # Op2 with factor 42...
obj.op1() # Op1 with factor 100... <- Wohoo! Now it's GOOD!
-----------------------------------------------------------------------
oop/private.attrs.py
print(obj.__dict__.keys())
# dict_keys(['_factor'])
oop/private.attrs.fixed.py
print(obj.__dict__.keys())
# dict_keys(['_A__factor', '_B__factor'])
The property decorator
oop/property.py
-----------------------------------------------------------------------
class PersonWithAccessors:
def __init__(self, age):
self._age = age
def get_age(self):
return self._age
def set_age(self):
if 18 <= age <= 99:
self._age = age
else:
raise ValueError('Age must be within [18, 99]')
class PersonPythonic:
def __init__(self, age):
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self, age):
if 18 <= age <= 99:
self._age = age
else:
raise ValueError('Age must be within [18, 99]')
person = PersonPythonic(39)
print(person.age) # 39 - Notice we access as data attribute
person.age = 42 # Notice we access as data attribute
print(person.age) # 42
person.age = 100 # ValueError: Age must be within [18, 99]
-----------------------------------------------------------------------
Operator overloading
oop/operator.overloading.py
-----------------------------------------------------------------------
class Weird:
def __init__(self, s):
self._s = s
def __len__(self):
return len(self._s)
def __bool__(self):
return '42' in self._s
weird = Weird('Hello! I am 9 years old!')
print(len(weird)) # 24
print(bool(weird)) # False
weird2 = Weird('Hello! I am 42 years old!')
print(len(weird2)) # 25
print(bool(weird2)) # True
-----------------------------------------------------------------------
Polymorphism – a brief overview
-- Writing a custom iterator
iterators/iterator.py
Chap 6 - OOP, Decorators, and Iterators
-- Decorators
decorators/time.measure.start.py
-----------------------------------------------------------------------
from time import sleep, time
def f():
sleep(.3)
def g():
sleep(.5)
t = time()
f()
print('f took: ', time() - t) # f took: 0.3003859519958496
t = time()
g()
print('g took:', time() - t) # g took: 0.5005719661712646
-----------------------------------------------------------------------
decorators/time.measure.dry.py
-----------------------------------------------------------------------
from time import sleep, time
def f():
sleep(.3)
def g():
sleep(.5)
def measure(func):
t = time()
func()
print(func.__name__, 'took:', time() - t)
measure(f) # f took: 0.30041074752807617
measure(g) # g took: 0.5006198883056641
-----------------------------------------------------------------------
decorators/time.measure.arguments.py
-----------------------------------------------------------------------
from time import sleep, time
def f(sleep_time=0.1):
sleep(sleep_time)
def measure(func, *args, **kwargs):
t = time()
func(*args, **kwargs)
print(func.__name__, 'took:', time() - t)
measure(f, sleep_time=0.3) # f took: 0.3004162311553955
measure(f, 0.2) # f took: 0.20028162002563477
-----------------------------------------------------------------------
decorators/time.measure.deco1.py
-----------------------------------------------------------------------
from time import sleep, time
def f(sleep_time=0.1):
sleep(sleep_time)
def measure(func):
def wrapper(*args, **kwargs):
t = time()
func(*args, **kwargs)
print(func.__name__, 'took:', time() - t)
return wrapper
f = measure(f) # decoration point
f(0.2) # f took: 0.2002875804901123
f(sleep_time=0.3) # f took: 0.3003721237182617
print(f.__name__) # wrapper <- ouch!
-----------------------------------------------------------------------
decorators/syntax.py
-----------------------------------------------------------------------
def func(arg1, arg2, ...):
pass
func = decorator(func)
# is equivalent to the following:
@decorator
def func(arg1, arg2, ...):
pass
-----------------------------------------------------------------------
decorators/syntax.py
-----------------------------------------------------------------------
def func(arg1, arg2, ...):
pass
func = deco1(deco2(func))
# is equivalent to the following:
@deco1
@deco2
def func(arg1, arg2, ...):
passWhen applying multiple
-----------------------------------------------------------------------
the closer the decorator to the function, the sooner it is applied.
decorators/syntax.py
-----------------------------------------------------------------------
def func(arg1, arg2, ...):
pass
func = decoarg(argA, argB)(func)
# is equivalent to the following:
@decoarg(argA, argB)
def func(arg1, arg2, ...):
pass
-----------------------------------------------------------------------
decorators/time.measure.deco2.py
-----------------------------------------------------------------------
from time import sleep, time
from functools import wraps
def measure(func):
@wraps(func)
def wrapper(*args, **kwargs):
t = time()
func(*args, **kwargs)
print(func.__name__, 'took:', time() - t)
return wrapper
@measure
def f(sleep_time=0.1):
"""I'm a cat. I love to sleep! """
sleep(sleep_time)
f(sleep_time=0.3) # f took: 0.30039525032043457
print(f.__name__, ':', f.__doc__)
# f : I'm a cat. I love to sleep!
-----------------------------------------------------------------------
decorators/two.decorators.py
-----------------------------------------------------------------------
from time import sleep, time
from functools import wraps
def measure(func):
@wraps(func)
def wrapper(*args, **kwargs):
t = time()
result = func(*args, **kwargs)
print(func.__name__, 'took:', time() - t)
return result
return wrapper
def max_result(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if result > 100:
print('Result is too big ({0}). Max allowed is 100.'
.format(result))
return result
return wrapper
@measure
@max_result
def cube(n):
return n ** 3
print(cube(2))
print(cube(5))
-----------------------------------------------------------------------
$ python two.decorators.py
A decorator factory
decorators/decorators.factory.py
-----------------------------------------------------------------------
from functools import wraps
def max_result(threshold):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
if result > threshold:
print(
'Result is too big ({0}). Max allowed is {1}.'
.format(result, threshold))
return result
return wrapper
return decorator
@max_result(75)
def cube(n):
return n ** 3
print(cube(5))
-----------------------------------------------------------------------
$ python decorators.factory.py
decorators/decorators.factory.py
-----------------------------------------------------------------------
@max_result(75)
def cube(n):
return n ** 3
@max_result(100)
def square(n):
return n ** 2
@max_result(1000)
def multiply(a, b):
return a * b
-----------------------------------------------------------------------
-- Object-oriented programming
The simplest Python class
Object-oriented programming (OOP) is a programming paradigm based on the concept of "objects", which are data structures that contain data, in the form of attributes, and code, in the form of functions known as methods.
oop/simplest.class.py
class Simplest(): # when empty, the braces are optional
pass
print(type(Simplest)) # what type is this object?
simp = Simplest() # we create an instance of Simplest: simp
print(type(simp)) # what type is simp?
# is simp an instance of Simplest?
print(type(simp) == Simplest) # There's a better way for this
$ python oop/simplest.class.py
Class and object namespaces
oop/class.namespaces.py
-----------------------------------------------------------------------
class Person():
species = 'Human'
print(Person.species) # Human
Person.alive = True # Added dynamically!
print(Person.alive) # True
man = Person()
print(man.species) # Human (inherited)
print(man.alive) # True (inherited)
Person.alive = False
print(man.alive) # False (inherited)
man.name = 'Darth'
man.surname = 'Vader'
print(man.name, man.surname) # Darth Vader
-----------------------------------------------------------------------
Attribute shadowing
oop/class.attribute.shadowing.py
-----------------------------------------------------------------------
class Point():
x = 10
y = 7
p = Point()
print(p.x) # 10 (from class attribute)
print(p.y) # 7 (from class attribute)
p.x = 12 # p gets its own 'x' attribute
print(p.x) # 12 (now found on the instance)
print(Point.x) # 10 (class attribute still the same)
del p.x # we delete instance attribute
print(p.x) # 10 (now search has to go again to find class attr)
p.z = 3 # let's make it a 3D point
print(p.z) # 3
print(Point.z)
# AttributeError: type object 'Point' has no attribute 'z'
-----------------------------------------------------------------------
I, me, and myself – using the self variable
oop/class.self.py
-----------------------------------------------------------------------
class Square():
side = 8
def area(self): # self is a reference to an instance
return self.side ** 2
sq = Square()
print(sq.area()) # 64 (side is found on the class)
print(Square.area(sq)) # 64 (equivalent to sq.area())
sq.side = 10
print(sq.area()) # 100 (side is found on the instance)
-----------------------------------------------------------------------
oop/class.price.py
-----------------------------------------------------------------------
class Price():
def final_price(self, vat, discount=0):
"""Returns price after applying vat and fixed discount."""
return (self.net_price * (100 + vat) / 100) - discount
p1 = Price()
p1.net_price = 100
print(Price.final_price(p1, 20, 10)) # 110 (100 * 1.2 - 10)
print(p1.final_price(20, 10)) # equivalent
-----------------------------------------------------------------------
Initializing an instance
oop/class.init.py
-----------------------------------------------------------------------
class Rectangle():
def __init__(self, sideA, sideB):
self.sideA = sideA
self.sideB = sideB
def area(self):
return self.sideA * self.sideB
r1 = Rectangle(10, 4)
print(r1.sideA, r1.sideB) # 10 4
print(r1.area()) # 40
r2 = Rectangle(7, 3)
print(r2.area()) # 21
-----------------------------------------------------------------------
OOP is about code reuse
By now it should be pretty clear: OOP is all about code reuse.
Inheritance and composition
oop/class.inheritance.py
-----------------------------------------------------------------------
class Engine():
def start(self):
pass
def stop(self):
pass
class ElectricEngine(Engine): # Is-A Engine
pass
class V8Engine(Engine): # Is-A Engine
pass
class Car():
engine_cls = Engine
def __init__(self):
self.engine = self.engine_cls() # Has-A Engine
def start(self):
print(
'Starting engine {0} for car {1}... Wroom, wroom!'
.format(
self.engine.__class__.__name__,
self.__class__.__name__)
)
self.engine.start()
def stop(self):
self.engine.stop()
class RaceCar(Car): # Is-A Car
engine_cls = V8Engine
class CityCar(Car): # Is-A Car
engine_cls = ElectricEngine
class F1Car(RaceCar): # Is-A RaceCar and also Is-A Car
engine_cls = V8Engine
car = Car()
racecar = RaceCar()
citycar = CityCar()
f1car = F1Car()
cars = [car, racecar, citycar, f1car]
for car in cars:
car.start()
""" Prints:
Starting engine Engine for car Car... Wroom, wroom!
Starting engine V8Engine for car RaceCar... Wroom, wroom!
Starting engine ElectricEngine for car CityCar... Wroom, wroom!
Starting engine V8Engine for car F1Car... Wroom, wroom!
"""
-----------------------------------------------------------------------
oop/class.issubclass.isinstance.py
-----------------------------------------------------------------------
car = Car()
racecar = RaceCar()
f1car = F1Car()
cars = [(car, 'car'), (racecar, 'racecar'), (f1car, 'f1car')]
car_classes = [Car, RaceCar, F1Car]
for car, car_name in cars:
for class_ in car_classes:
belongs = isinstance(car, class_)
msg = 'is a' if belongs else 'is not a'
print(car_name, msg, class_.__name__)
""" Prints:
car is a Car
car is not a RaceCar
car is not a F1Car
racecar is a Car
racecar is a RaceCar
racecar is not a F1Car
f1car is a Car
f1car is a RaceCar
f1car is a F1Car
"""
-----------------------------------------------------------------------
oop/class.issubclass.isinstance.py
-----------------------------------------------------------------------
for class1 in car_classes:
for class2 in car_classes:
is_subclass = issubclass(class1, class2)
msg = '{0} a subclass of'.format(
'is' if is_subclass else 'is not')
print(class1.__name__, msg, class2.__name__)
""" Prints:
Car is a subclass of Car
Car is not a subclass of RaceCar
Car is not a subclass of F1Car
RaceCar is a subclass of Car
RaceCar is a subclass of RaceCar
RaceCar is not a subclass of F1Car
F1Car is a subclass of Car
F1Car is a subclass of RaceCar
F1Car is a subclass of F1Car
"""
-----------------------------------------------------------------------
Accessing a base class
oop/super.duplication.py
-----------------------------------------------------------------------
class Book:
def __init__(self, title, publisher, pages):
self.title = title
self.publisher = publisher
self.pages = pages
class Ebook(Book):
def __init__(self, title, publisher, pages, format_):
self.title = title
self.publisher = publisher
self.pages = pages
self.format_ = format_
-----------------------------------------------------------------------
oop/super.explicit.py
-----------------------------------------------------------------------
class Book:
def __init__(self, title, publisher, pages):
self.title = title
self.publisher = publisher
self.pages = pages
class Ebook(Book):
def __init__(self, title, publisher, pages, format_):
Book.__init__(self, title, publisher, pages)
self.format_ = format_
ebook = Ebook('Learning Python', 'Packt Publishing', 360, 'PDF')
print(ebook.title) # Learning Python
print(ebook.publisher) # Packt Publishing
print(ebook.pages) # 360
print(ebook.format_) # PDF
-----------------------------------------------------------------------
oop/super.implicit.py
-----------------------------------------------------------------------
class Book:
def __init__(self, title, publisher, pages):
self.title = title
self.publisher = publisher
self.pages = pages
class Ebook(Book):
def __init__(self, title, publisher, pages, format_):
super().__init__(title, publisher, pages)
# Another way to do the same thing is:
# super(Ebook, self).__init__(title, publisher, pages)
self.format_ = format_
ebook = Ebook('Learning Python', 'Packt Publishing', 360, 'PDF')
print(ebook.title) # Learning Python
print(ebook.publisher) # Packt Publishing
print(ebook.pages) # 360
print(ebook.format_) # PDF
-----------------------------------------------------------------------
Multiple inheritance
oop/multiple.inheritance.py
-----------------------------------------------------------------------
class Shape:
geometric_type = 'Generic Shape'
def area(self): # This acts as placeholder for the interface
raise NotImplementedError
def get_geometric_type(self):
return self.geometric_type
class Plotter:
def plot(self, ratio, topleft):
# Imagine some nice plotting logic here...
print('Plotting at {}, ratio {}.'.format(
topleft, ratio))
class Polygon(Shape, Plotter): # base class for polygons
geometric_type = 'Polygon'
class RegularPolygon(Polygon): # Is-A Polygon
geometric_type = 'Regular Polygon'
def __init__(self, side):
self.side = side
class RegularHexagon(RegularPolygon): # Is-A RegularPolygon
geometric_type = 'RegularHexagon'
def area(self):
return 1.5 * (3 ** .5 * self.side ** 2)
class Square(RegularPolygon): # Is-A RegularPolygon
geometric_type = 'Square'
def area(self):
return self.side * self.side
hexagon = RegularHexagon(10)
print(hexagon.area()) # 259.8076211353316
print(hexagon.get_geometric_type()) # RegularHexagon
hexagon.plot(0.8, (75, 77)) # Plotting at (75, 77), ratio 0.8.
square = Square(12)
print(square.area()) # 144
print(square.get_geometric_type()) # Square
square.plot(0.93, (74, 75)) # Plotting at (74, 75), ratio 0.93.
-----------------------------------------------------------------------
Method resolution order
oop/multiple.inheritance.py
-----------------------------------------------------------------------
print(square.__class__.__mro__)
# prints:
# (<class '__main__.Square'>, <class '__main__.RegularPolygon'>,
# <class '__main__.Polygon'>, <class '__main__.Shape'>,
# <class '__main__.Plotter'>, <class 'object'>)
-----------------------------------------------------------------------
oop/mro.simple.py
-----------------------------------------------------------------------
class A:
label = 'a'
class B(A):
label = 'b'
class C(A):
label = 'c'
class D(B, C):
pass
d = D()
print(d.label) # Hypothetically this could be either 'b' or 'c'
-----------------------------------------------------------------------
oop/mro.py
-----------------------------------------------------------------------
class A:
label = 'a'
class B(A):
pass # was: label = 'b'
class C(A):
label = 'c'
class D(B, C):
pass
d = D()
print(d.label) # 'c'
print(d.__class__.mro()) # notice another way to get the MRO
# prints:
# [<class '__main__.D'>, <class '__main__.B'>,
# <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
-----------------------------------------------------------------------
Static and class methods
Static methods
oop/static.methods.py
-----------------------------------------------------------------------
class String:
@staticmethod
def is_palindrome(s, case_insensitive=True):
# we allow only letters and numbers
s = ''.join(c for c in s if c.isalnum()) # Study this!
# For case insensitive comparison, we lower-case s
if case_insensitive:
s = s.lower()
for c in range(len(s) // 2):
if s[c] != s[-c -1]:
return False
return True
@staticmethod
def get_unique_words(sentence):
return set(sentence.split())
print(String.is_palindrome(
'Radar', case_insensitive=False)) # False: Case Sensitive
print(String.is_palindrome('A nut for a jar of tuna')) # True
print(String.is_palindrome('Never Odd, Or Even!')) # True
print(String.is_palindrome(
'In Girum Imus Nocte Et Consumimur Igni') # Latin! Show-off!
) # True
print(String.get_unique_words(
'I love palindromes. I really really love them!'))
# {'them!', 'really', 'palindromes.', 'I', 'love'}
-----------------------------------------------------------------------
Class methods
oop/class.methods.factory.py
-----------------------------------------------------------------------
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def from_tuple(cls, coords): # cls is Point
return cls(*coords)
@classmethod
def from_point(cls, point): # cls is Point
return cls(point.x, point.y)
p = Point.from_tuple((3, 7))
print(p.x, p.y) # 3 7
q = Point.from_point(p)
print(q.x, q.y) # 3 7
-----------------------------------------------------------------------
oop/class.methods.split.py
-----------------------------------------------------------------------
class String:
@classmethod
def is_palindrome(cls, s, case_insensitive=True):
s = cls._strip_string(s)
# For case insensitive comparison, we lower-case s
if case_insensitive:
s = s.lower()
return cls._is_palindrome(s)
@staticmethod
def _strip_string(s):
return ''.join(c for c in s if c.isalnum())
@staticmethod
def _is_palindrome(s):
for c in range(len(s) // 2):
if s[c] != s[-c -1]:
return False
return True
@staticmethod
def get_unique_words(sentence):
return set(sentence.split())
print(String.is_palindrome('A nut for a jar of tuna')) # True
print(String.is_palindrome('A nut for a jar of beans')) # False
-----------------------------------------------------------------------
Private methods and name mangling
oop/private.attrs.py
-----------------------------------------------------------------------
class A:
def __init__(self, factor):
self._factor = factor
def op1(self):
print('Op1 with factor {}...'.format(self._factor))
class B(A):
def op2(self, factor):
self._factor = factor
print('Op2 with factor {}...'.format(self._factor))
obj = B(100)
obj.op1() # Op1 with factor 100...
obj.op2(42) # Op2 with factor 42...
obj.op1() # Op1 with factor 42... <- This is BAD
-----------------------------------------------------------------------
oop/private.attrs.fixed.py
-----------------------------------------------------------------------
class A:
def __init__(self, factor):
self.__factor = factor
def op1(self):
print('Op1 with factor {}...'.format(self.__factor))
class B(A):
def op2(self, factor):
self.__factor = factor
print('Op2 with factor {}...'.format(self.__factor))
obj = B(100)
obj.op1() # Op1 with factor 100...
obj.op2(42) # Op2 with factor 42...
obj.op1() # Op1 with factor 100... <- Wohoo! Now it's GOOD!
-----------------------------------------------------------------------
oop/private.attrs.py
print(obj.__dict__.keys())
# dict_keys(['_factor'])
oop/private.attrs.fixed.py
print(obj.__dict__.keys())
# dict_keys(['_A__factor', '_B__factor'])
The property decorator
oop/property.py
-----------------------------------------------------------------------
class PersonWithAccessors:
def __init__(self, age):
self._age = age
def get_age(self):
return self._age
def set_age(self):
if 18 <= age <= 99:
self._age = age
else:
raise ValueError('Age must be within [18, 99]')
class PersonPythonic:
def __init__(self, age):
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self, age):
if 18 <= age <= 99:
self._age = age
else:
raise ValueError('Age must be within [18, 99]')
person = PersonPythonic(39)
print(person.age) # 39 - Notice we access as data attribute
person.age = 42 # Notice we access as data attribute
print(person.age) # 42
person.age = 100 # ValueError: Age must be within [18, 99]
-----------------------------------------------------------------------
Operator overloading
oop/operator.overloading.py
-----------------------------------------------------------------------
class Weird:
def __init__(self, s):
self._s = s
def __len__(self):
return len(self._s)
def __bool__(self):
return '42' in self._s
weird = Weird('Hello! I am 9 years old!')
print(len(weird)) # 24
print(bool(weird)) # False
weird2 = Weird('Hello! I am 42 years old!')
print(len(weird2)) # 25
print(bool(weird2)) # True
-----------------------------------------------------------------------
Polymorphism – a brief overview
-- Writing a custom iterator
iterators/iterator.py
-----------------------------------------------------------------------
class OddEven:
def __init__(self, data):
self._data = data
self.indexes = (list(range(0, len(data), 2)) +
list(range(1, len(data), 2)))
def __iter__(self):
return self
def __next__(self):
if self.indexes:
return self._data[self.indexes.pop(0)]
raise StopIteration
oddeven = OddEven('ThIsIsCoOl!')
print(''.join(c for c in oddeven)) # TIICO!hssol
oddeven = OddEven('HoLa') # or manually...
it = iter(oddeven) # this calls oddeven.__iter__ internally
print(next(it)) # H
print(next(it)) # L
print(next(it)) # o
print(next(it)) # a
-----------------------------------------------------------------------
-----------------------------------------------------------------------
class OddEven:
def __init__(self, data):
self._data = data
self.indexes = (list(range(0, len(data), 2)) +
list(range(1, len(data), 2)))
def __iter__(self):
return self
def __next__(self):
if self.indexes:
return self._data[self.indexes.pop(0)]
raise StopIteration
oddeven = OddEven('ThIsIsCoOl!')
print(''.join(c for c in oddeven)) # TIICO!hssol
oddeven = OddEven('HoLa') # or manually...
it = iter(oddeven) # this calls oddeven.__iter__ internally
print(next(it)) # H
print(next(it)) # L
print(next(it)) # o
print(next(it)) # a
-----------------------------------------------------------------------
No comments:
Post a Comment