Class
		
		Vehicle
		
			when a class author creates an attribute with a single leading underscore, the author does not want users of the class to access the attribute directly
			the attribute with single leading underscore is still able to be accessed directly
		
import typing
class Vehicle(object):
    """Document String: Define a Vehicle class"""
 
    def __init__(self, brand: str, year: int):
        self._brand = brand;
        self._year = year;
 
    @property
    def brand(self) -> str:
        return self._brand
 
    @brand.setter
    def brand(self, b : str) -> None:
        self._brand = b;
 
    @brand.deleter
    def brand(self) -> None:
        del self._brand
 
    @property
    def year(self) -> int:
        return self._year
 
    @year.setter
    def year(self, y : int) -> None:
        self._year = y
 
    @year.deleter
    def year(self) -> None:
        del self._year
 
    def __str__(self) -> str:
        return self._brand+' '+str(self._year)
 
    def __getattr__(self, attr) -> str: # intercept that inexistent attribute
        return '~'
 
def main():
    v = Vehicle("Lincoln", 1998);
    print(v) # equals to print(v.__str__())
 
    v.brand = "Honda"
    v.year = 2016
    print(v)
 
    del v.year
    print(v)
 
if __name__ == "__main__":
	main()
		
		 
		Access Attributes
		
		
#!/usr/bin/python
from Vehicle import Vehicle
def main():
    print('test ...')
    v = Vehicle('Buick', 1998)
    print(v.brand, v.year) # use accessors to access attributes
    print(v._brand, v._year) # directly access class attributes
if __name__ == '__main__':
    main()
		
		 
		Private attributes
		
			Prevent access attributes directly, prefix the name of the attribute with two underscore
			Name mangling with create an attribute, _ClassName__attribute
		
class Vehicle(object):
    """Document String: Define a Vehicle class"""
 
    def __init__(self, brand):
        self.__brand = brand;
 
    @property
    def brand(self):
        return self.__brand
 
    @brand.setter
    def brand(self, b):
        self.__brand = b;
 
    @brand.deleter
    def brand(self):
        del self.__brand
 
    def __str__(self):
        return self.__brand
 
    def __getattr__(self, attr): # intercept that inexistent attribute
        return '~'
 
def main():
    v = Vehicle("Lincoln");
 
    print("Brand: %s" % (v.brand)) # use getter to access attribute
    print("Brand: %s" % getattr(v, 'brand')) # use getattr() and getter
 
    print("Brand: %s" % v.__brand) # not able to access self.__brand
    print("Brand: %s" % v._Vehicle__brand) # name mangling
 
if __name__ == "__main__":
	main()
		
		 
		Add attributes
		
		
class Vehicle(object):
    """Document String: Define a Vehicle class"""
 
    def __init__(self, brand):
        self._brand = brand;
 
    @property
    def brand(self):
        return self._brand
 
    @brand.setter
    def brand(self, b):
        self._brand = b;
 
    @brand.deleter
    def brand(self):
        del self._brand
 
    @property
    def year(self):
        return self._year
 
    @year.setter
    def year(self, y):
        self._year = y
 
    @year.deleter
    def year(self):
        del self._year
 
    def __str__(self):
        return self._brand+' '+str(self._year)
 
    def __getattr__(self, attr): # intercept that inexistent attribute
        return '~'
 
def main():
    v = Vehicle("Lincoln");
 
    v.year = 1998
 
    print(v)
 
if __name__ == "__main__":
	main()
		
		 
		Control attributes with built-in functions
		
		
class Vehicle(object):
    """Document String: Define a Vehicle class"""
 
    def __init__(self, brand, year):
	#print "Create Vehicle Object ...";
        self._brand = brand;
        self._year = year;
 
    @property
    def brand(self):
        return self._brand
 
    @brand.setter
    def brand(self, b):
        self._brand = b;
 
    @brand.deleter
    def brand(self):
        del self._brand
 
    @property
    def year(self):
        return self._year
 
    @year.setter
    def year(self, y):
        self._year = y
 
    @year.deleter
    def year(self):
        del self._year
 
    def __str__(self):
        return str(self.brand)+" "+str(self.year)
 
    def __getattr__(self, attr): # intercept that inexistent attribute
        return '~'
 
def main():
    v = Vehicle("Lincoln", 1998);
    print(v)
 
    setattr(v, 'year', 2000); # use setattr() and setter
    setattr(v, 'brand', 'Buick');
    print(v)
 
    if hasattr(v, 'year'):
        print("Year Attribute: ", getattr(v, 'year')) # use getattr() and getter
 
    delattr(v, 'year');
    print("Year Attribute: ",getattr(v, 'year'))
 
if __name__ == "__main__":
    main()
		
		 
		Using default arguments with constructors
		
		
class Vehicle(object):
    """Document String: Define a Vehicle class"""
    def __init__(self, brand = "None", year = 0):
        self._brand = brand;
	self._year = year;
    @property
    def brand(self):
        return self._brand
    @brand.setter
    def brand(self, b):
        self._brand = b;
    @brand.deleter
    def brand(self):
        del self._brand
    @property
    def year(self):
        return self._year
    @year.setter
    def year(self, y):
        self._year = y
    @year.deleter
    def year(self):
        del self._year
    def __str__(self):
        return self._brand+' '+str(self._year)
    def __getattr__(self, attr): # intercept that inexistent attribute
        return '~'
def main():
    v = Vehicle("Lincoln", 1998); # Lincoln 1998
    print(v)
    v = Vehicle("Lincoln"); # Lincoln 0
    print(v)
    v = Vehicle(year = 1998) # None 1998
    print(v)
if __name__ == "__main__":
	main()
		
		 
		Class attributes and deconstructor
		
		
class Vehicle(object):
    """ Vehicle class """
    count = 0; # class attribute
    def __init__(self, brand):
        self.__brand = brand
        Vehicle.count += 1
    @property
    def brand(self):
        return self.__brand
    @brand.setter
    def brand(self, b):
        self.__brand = b
    @brand.deleter
    def brand(self):
        del self.__brand
    def __str__(self):
        return "Brand: %s" % self.__brand
    def __getattr__(self, attr):
        return "~"
    def __del__(self):
        Vehicle.count -= 1
        print("Delete %s, %d vehicles left ..." % (self.__brand, Vehicle.count))
def main():
    v1 = Vehicle("Lincoln")
    v2 = Vehicle("Buick")
    v3 = Vehicle("Acura")
    v4 = Vehicle("Honda")
    del v1
    del v2
if __name__ == '__main__':
    main()
		
		 
		Call its own method
		
		
class T(object):
    def __init__(self, n):
        self.n = n
    def show(self):
        return self.n
    def callShow(self):
        return self.show()
        #return T.show(self)
def main():
    t = T(10)
    print(t.show())
    print(t.callShow())
if __name__ == '__main__':
    main()
		
		 
		__slots__
		
			list the only attributes that objects of the class are allowed to have
		
class Vehicle(object):
    """Document String: Define a Vehicle class"""
 
    __slots__ = ['_brand', '_year', '_model']
 
    def __init__(self, brand, year):
        self._brand = brand;
        self._year = year;
 
    def __str__(self):
        return self._brand+' '+str(self._year)+' '+self._model
 
    def __getattr__(self, name):
        return 'None'
 
def main():
    v = Vehicle("Buick", 1998) # by default, only _brand and _year
    print(v)
 
    v._model = 'Century' # add attribute, _model
    print(v)
 
    v._color = 'White' # not allow to add _color which is not in __slots__
 
if __name__ == '__main__':
    main()
		
		 
		Reference
		
			
Python How to Program, Chapter 7