class
Initialization: __init___() is an initializer, not a constructor. The purpose of the double underscore in it is to configure an existing object by the time it is called.
Instance method: functions which can be called on objects. Need the self always as first argument. We don't need it when calling the instance method (syntactic sugar).
protected: by prefixing the name of your member with a single underscore (without it means is public), you’re telling others “don’t touch this, unless you’re a subclass”. Prefix a function with _ if it is implementation details which is not supposed to be called outside the class.
private:turns every member name prefixed with at least two underscores and suffixed with at most one underscore.
class Cup:
def __init__(self, color):
self._color = color # protected variable
self.__content = None # private variable
def fill(self, beverage):
self.__content = beverage
def empty(self):
self.__content = None
## try to access the private member
redCup = Cup("red")
redCup._Cup__content = "tea"
callable instances:
class Resolver:
def __call__(self, host): #this makes it a callable instance
resolve = Resolver()
resolve('sixty-north.com') #this is syntactic sugar of resolve.__call__('sixty-north.com')
classes are callable
def sequence_class(immutable):
return tuple if immutable else list
seq = sequence_class(immutable=True)
t = seq("Timbuktu") #classes are callable, class constructor
repr() and str()
PDB print command uses repr(). Good practice is to always override default repr() method. (developer oriented)
str() method like toString() in other language.Default str() calls repr() if you don't override str().
class Point2D:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return '({}, {})'.format(self.x, self.y)
def __repr__(self):
return 'Point2D(x={}, y={})'.format(self.x, self.y)
@staticmethod vs @classmethod
Generally, static methods no access needed to either class or instance objects. May be able to be moved to become a module-scope function.
Generally, class methods require access to the class object to call other class methods or the constructor.
named constructors: factory function that constructs objects with certain configuration.
import iso6346
class ShippingContainer:
HEIGHT_FT = 8.5 #class attribute and constant. Use capital letters for constant names
WIDTH_FT = 8.0
next_serial = 1337
@staticmethod
def _make_bic_code(owner_code, serial): # _means implementation details, should not direct access
return iso6346.create(owner_code=owner_code,
serial=str(serial).zfill(6))
@classmethod
def _get_next_serial(cls):
result = cls.next_serial
cls.next_serial += 1
return result
@classmethod #named constructors
# we need the flexible number of argument because this class method is inherited by RefrigeratedShippingContainer
# which needs extra variable celsius
def create_empty(cls, owner_code, length_ft, *args, **kwargs):
return cls(owner_code, length_ft, contents=None, *args, **kwargs)
@classmethod
def create_with_items(cls, owner_code, length_ft, items, *args, **kwargs):
return cls(owner_code, length_ft, contents=list(items), *args, **kwargs)
def __init__(self, owner_code, length_ft, contents):
self.contents = contents
self.length_ft = length_ft
self.bic = self._make_bic_code( # we use instance to call the static method to achieve dynamic dispatch
owner_code=owner_code,
serial=ShippingContainer._get_next_serial()
)
@property
def volume_ft3(self):
return self._calc_volume() #design pattern: template method. Allow specialised in derived class
def _calc_volume(self):
return ShippingContainer.HEIGHT_FT * ShippingContainer.WIDTH_FT * self.length_ft
class RefrigeratedShippingContainer(ShippingContainer):
MAX_CELSIUS = 4.0
FRIDGE_VOLUME_FT3 = 100
@staticmethod
def _make_bic_code(owner_code, serial):
return iso6346.create(owner_code=owner_code,
serial=str(serial).zfill(6),
category='R')
@staticmethod
def _c_to_f(celsius):
return celsius * 9/5 +32
@staticmethod
def _f_to_c(fahrenheit):
return (fahrenheit - 32) * 5/9
def __init__(self, owner_code, length_ft, contents, celsius):
super().__init__(owner_code, length_ft, contents)
self.celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
self._set_celsius(value)
def _set_celsius(self, value):
if value > RefrigeratedShippingContainer.MAX_CELSIUS:
raise ValueError("Temperature too hot!")
self._celsius = value # we need to use _celsius as the actual attribute, self.celsius is getter
@property
def fahrenheit(self):
return RefrigeratedShippingContainer._c_to_f(self.celsius)
@fahrenheit.setter
def fahrenheit(self, value):
self.celsius = RefrigeratedShippingContainer._f_to_c(value)
def _calc_volume(self):
return super()._calc_volume() - RefrigeratedShippingContainer.FRIDGE_VOLUME_FT3
class HeatedRefrigeratedShippingContainer(RefrigeratedShippingContainer):
MIN_CELSIUS = -20.0
def _set_celsius(self, value):
if value < HeatedRefrigeratedShippingContainer.MIN_CELSIUS:
raise ValueError("Temperature too cold!")
super()._set_celsius(value)