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)

results matching ""

    No results matching ""