本週作業:
1. 研讀The Quick Python Book 3rd Edition-2018-英文版 – Ch10: Modules and scoping rules
2. 研讀The Quick Python Book 3rd Edition-2018-英文版 – Ch11: Python programs
3. 研讀The Quick Python Book 3rd Edition-2018-英文版 – Ch15: Classes and object-oriented programming
4. 研讀 @prooerty 裝飾器的功能
5. 研讀The Quick Python Book 3rd Edition-2018-英文版 – Ch17: Data type as Object




類別(Class)

class FirstClass:
    pass

object1 = FirstClass()   # 空物件
object2 = FirstClass()   # 空物件
object1
## <__main__.FirstClass object at 0x118033be0>
object2
## <__main__.FirstClass object at 0x118001d30>

物件變數(instance variable) and__init__()方法(method)

class Circle:
    '''一個空的類別'''
    pass
Circle.__doc__
## '一個空的類別'
my_circle1 = Circle()   # 每個物件都是獨立的
my_circle2 = Circle()
# 各自建立radius變數,在類別或物件上的變數皆稱為『屬性(attribute)』
my_circle1.radius = 5    
print(2 * 3.14 * my_circle1.radius)
## 31.400000000000002
my_circle2.radius = 8
print(2 * 3.14 * my_circle2.radius)
## 50.24
class Circle:
    def __init__(self):
        self.radius = 1
c = Circle()
c.radius
## 1
class Circle:
    def __init__(self, radius = 1):
        self.radius = radius
c1 = Circle(3)
c1.radius
## 3
c2 = Circle()
c2.radius  
## 1
class Circle:
    def __init__(self, radius):
        self.radius = radius    # 設定物件變數(屬性),
        radius = 10             # 為區域變數,與C++和JAVA不同

my_circle = Circle(5)
my_circle.radius
## 5

物件方法(method)

class Circle:
    def __init__(self, radius = 1):   # radius 函數參數
        self.radius = radius          # self.radius 物件變數(屬性)
    def area(self):
        return self.radius * self.radius * 3.14159
c = Circle(5)
c.area()
## 78.53975
c.radius = 1
c.area()
## 3.14159
Circle.area(c)
## 3.14159
Circle.__init__(self=c, radius=100)
c.radius
## 100

類別變數(class variable)

class Circle:
    pi = 3.14159      # pi為『類別變數』
    def __init__(self, radius):
        self.radius = radius
    def area(self):
        return self.radius * self.radius * Circle.pi   # 注意:Circle.pi
Circle.pi
## 3.14159
Circle.pi = 4
Circle.pi
## 4
Circle.pi = 3.14159
Circle.pi
## 3.14159
c = Circle(3)
c.__class__
## <class '__main__.Circle'>
c.__class__.pi
## 3.14159
Circle.__class__
## <class 'type'>
c1 = Circle(1)
c2 = Circle(2)
c1.pi = 10     # 注意:建立了c1的物件變數,並非更改類別變數
c1.pi
## 10
c2.pi          #注意:c1建立的物件變數pi,與類別變數pi不同
## 3.14159
Circle.pi
## 3.14159
Circle.pi = 100
c1.pi      # 找到c1的物件變數
## 10
c2.pi      # 找到Circle的類別變數
## 100
del c1.pi  # 刪除c1.pi物件變數
c1.pi
## 100

靜態方法(static method)

'''circle 模組:包含Circle類別'''
class Circle:
    '''Circle類別'''
    all_circles = []
    pi = 3.14159
    
    def __init__(self, r = 1):
        '''以給定半徑建立圓物件'''
        self.radius = r
        self.__class__.all_circles.append(self)
    
    def area(self):
        '''計算此物件面積'''
        return self.__class__.pi * self.radius * self.radius
    
    @staticmethod
    def total_area():
        '''用來計算all_circles這個list所有物件總面積的靜態方法(static method)'''
        total = 0
        for c in Circle.all_circles:
            total = total + c.area()
        return total
import circle
c1 = circle.Circle(1); c1
## <circle.Circle object at 0x117f8a518>
c2 = circle.Circle(2); c2
## <circle.Circle object at 0x117f86c50>
circle.Circle.total_area()
## 15.70795
c2.radius = 3
circle.Circle.total_area()
## 31.415899999999997
c2.total_area()
## 31.415899999999997

類別方法(class method)

"""circle_cm module: contains the Circle class."""
class Circle:
    '''Circle class'''
    all_circles = []    
    pi = 3.14159 

    def __init__(self, r=1):
        '''Create a Circle with the given radius'''
        self.radius = r
        self.__class__.all_circles.append(self)

    def area(self):
        '''determine the area of the Circle'''
        return self.__class__.pi * self.radius * self.radius

    @classmethod                            #1
    def total_area(cls):
        '''用來計算all_circles這個list所有物件總面積的類別方法(class method)'''
        total = 0
        for c in cls.all_circles:
            total = total + c.area()
        return total
import circle_cm
c1 = circle_cm.Circle(1)
c2 = circle_cm.Circle(2)
circle_cm.Circle.total_area()
## 15.70795
c2.radius = 3
circle_cm.Circle.total_area()
## 31.415899999999997
class Shiba:
    pee_length = 10

    def __init__(self, height, weight):
        self.height = height
        self.weight = weight

    @classmethod
    def pee(cls):
        print("pee" + "." * cls.pee_length)
Shiba.pee()
## pee..........
black_shiba = Shiba(30, 40)
black_shiba.pee()
## pee..........

靜態方法與類別方法的比較

class Some:
    def service(x, y):
        print('do service...', x + y)
        
s = Some()
Some.service(1, 2)
## do service... 3
s.service(1, 2)
#TypeError: service() takes 2 positional arguments but 3 were given
class Some: 
    @staticmethod
    def service(x, y):
        print('do service...', x + y)
s = Some()
Some.service(1, 2)
## do service... 3
s.service(1, 2)
## do service... 3
class Some:
    def __init__(self, x):
        self.x = x
    
    def service(self, y):     # 物件方法
        print('do service...', self.x + y)


class Other:
    pass
o = Other()
o.x = 100
Some.service(o, 200)    # 任何具有x屬性的物件都可以使用
## do service... 300
class Some:
    def __init__(self, x):
        self.x = x
  
    @classmethod
    def service(clz, y):
        print('do service...', self.x + y)
Some.service(o, 200)
# TypeError: service() takes 2 positional arguments but 3 were given
class Myclass:
    def objmethod(self):                      # 物件方法必須有self參數,此參數指向物件本身
        return ('呼叫了物件 method', self)     #   物件方法可以透過self參數來修改物件本身,也可以透過self.__class__來存取或修改所屬的類別屬性
    @classmethod
    def classmethod(cls):                     # 類別方法必須有cls參數,此參數指向類別本身
        return ('呼叫了類別 method', cls)     # 所以類別方法只能修改類別本身,並無法修改以該類別建立之物件
    @staticmethod
    def staticmethod():                       # 靜態方法因為沒有參數指向類別或是物件本身
                                              # 故無法修改物件或類別的狀態,只能處理使用者傳入的參數
        return ('呼叫了靜態 method')
obj = Myclass()
# 呼叫物件方法
obj.objmethod()
## ('呼叫了物件 method', <__main__.Myclass object at 0x117f91e10>)
Myclass.objmethod(obj)
## ('呼叫了物件 method', <__main__.Myclass object at 0x117f91e10>)
# 呼叫類別方法
obj.classmethod()    # 呼叫類別方法時(無論是透過Myclass或是obj,或自動把類別物件傳入cls)
## ('呼叫了類別 method', <class '__main__.Myclass'>)
# 呼叫靜態方法
obj.staticmethod()    # 不需傳入self或cls參數,故靜態方法類似一般的函數
## '呼叫了靜態 method'

類別的繼承(inheritance)

class Shape:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def move(self, delta_x, delta_y):
        self.x = self.x + delta_x
        self.y = self.y + delta_y

class Circle(Shape):                                     
    def __init__(self, r=1, x=0, y=0):
        super().__init__(x, y)          # 必須執行父類別的初始化方法              
        self.radius = r
c = Circle(1)
c.move(3, 4)
c.x
## 3
c.y
## 4

類別變數與物件變數的繼承

class P:
    z = "Hello"
    def set_p(self):
        self.x = "Class P"
    def print_p(self):
         print(self.x)
class C(P):
    def set_c(self):
        self.x = "Class C"
    def print_c(self):
        print(self.x)
c = C()
c.set_p()
c.print_p()
## Class P
c.print_c()
## Class P
c.set_c()
c.print_c()
## Class C
c.print_p()
## Class C
c.z; C.z; P.z
## 'Hello'
## 'Hello'
## 'Hello'
C.z = "Bonjour"
c.z; C.z; P.z
## 'Bonjour'
## 'Bonjour'
## 'Hello'
c.z = "Ciao"
c.z; C.z; P.z
## 'Ciao'
## 'Bonjour'
## 'Hello'

私有變數(private variable)與私有方法(private method)

class Mine:
    def __init__(self):
        self.x = 2
        self.__y = 3              # 私有變數
    def print_y(self):
        print(self.__y)

m = Mine()

print(m.x)
## 2
print(m.__y)
# Traceback (innermost last):
#   File "<stdin>", line 1, in ?
# AttributeError: 'Mine' object has no attribute '__y'
m.print_y()
## 3
dir(m)    # 注意 __Mine__y名稱,為『名稱修飾』的結果
## ['_Mine__y', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'print_y', 'x']
m._Mine__y
## 3
class Test:
    def __init__(self):
        self.foo = 11
        self._bar = 23
        self.__baz = 23

class ExtendTest(Test):
    def __init__(self):
        super().__init__()
        self.foo = '已覆寫'
        self._bar = '已覆寫'
        self.__baz = '已覆寫'
t2 = ExtendTest()
t2.foo
## '已覆寫'
t2._bar
## '已覆寫'
t2.__baz
# AttributeError: 'ExtendTest' object has no attribute '__baz'
dir(t2)
## ['_ExtendTest__baz', '_Test__baz', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_bar', 'foo']
t2._ExtendTest__baz
## '已覆寫'
t2._Test__baz
## 23

多重繼承

多重繼承關係

class E:
    pass

class F:
    pass

class G:
    pass

class D(G):
    pass

class C:
    pass

class B(E, F):
    pass
    
class A(B, C, D):
    pass

a = A()
a.__class__
## <class '__main__.A'>
A.__mro__
## (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.F'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>)
A.__bases__
## (<class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>)
class A:
    def __init__(self):
        print('class A')
class B:
    def __init__(self):
        print("class B")
class C(A, B):
    def __init__(self):
        super().__init__()
        print('class C')

x = C()
## class A
## class C
class A:
    def __init__(self):
        super().__init__()
        print('class A')
class B:
    def __init__(self):
        super().__init__()
        print("class B")
class C(A, B):
    def __init__(self):
        super().__init__()
        print('class C')
x = C()
## class B
## class A
## class C