本週作業:
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 0x7fc54cbaf280>
object2
## <__main__.FirstClass object at 0x7fc54cb90820>

物件變數(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             # 為區域變數

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 0x7fc54cbe6ac0>
c2 = circle.Circle(2); c2
## <circle.Circle object at 0x7fc54cbf74f0>
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 0x7fc54cc01550>)
Myclass.objmethod(obj)
## ('呼叫了物件 method', <__main__.Myclass object at 0x7fc54cc01550>)
# 呼叫類別方法
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_y()非私有,且位於類別Mine之內,故可透過此方法直接存取私有變數__y
        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

補充:@property裝飾器的實作:將『方法』轉換為『物件變數』

class Temperture:
  def __init__(self):
    self._temp_fahr = 0    # 華氏溫度
  
  @property
  def temp(self):
    return (self._temp_fahr - 32) *5 / 9
t = Temperture()
t._temp_fahr
## 0
t.temp
## -17.77777777777778
class Temperture:
  def __init__(self):
    self._temp_fahr = 0    # 華氏溫度
  
  @property
  def temp(self):
    return (self._temp_fahr - 32) *5 / 9
  
  @temp.setter
  def temp(self, new_temp):
    self._temp_fahr = new_temp * 9 / 5 + 32
t = Temperture()
t._temp_fahr
## 0
t.temp = 34
t._temp_fahr
## 93.2
t.temp
## 34.0

物件名稱的可視範圍與命名空間

"""cs 模組: 物件變數視野與命名空間展示模組."""
## 'cs 模組: 物件變數視野與命名空間展示模組.'
mv ="模組變數: mv"

def mf():
    return "模組函式: mf()"

class SC:
    scv = "父類別的類別變數: self.scv"
    __pscv = "父類別的私有類別變數: 無法從外部存取"
    def __init__(self):
        self.siv = "父類別定義的物件變數: self.siv " \
                   "(但必須透過 SC.siv 才能賦值)"
        self.__psiv = "父類別定義的私有物件變數: " \
                       "無法從外部存取"
    def sm(self):
        return "父類別的方法: self.sm()"
    def __spm(self):
        return "父類別的私有方法: 無法從外部存取"

class C(SC):
    cv = "類別變數: self.cv (但必須透過 C.cv才能賦值)"
    __pcv = "私有類別變數: self.__pcv " \
            "(但必須透過 C.__pcv才能賦值)"
    def __init__(self):
        SC.__init__(self)
        self.__piv = "私有物件變數: self.__piv"
    def m2(self):
        return "方法: self.m2()"
    def __pm(self):
        return "私有方法: self.__pm()"

    def m(self, p="參數: p"):
        lv = "區域變數: lv"
        self.iv = "物件變數: self.xi"
        print("直接存取區域、全域、和內建命名空間")
        print("區域命名空間:", list(locals().keys()))
        print(p) 
        print(lv)
        print("全域命名空間:", list(globals().keys()))
        print(mv)  
        print(mf())
        print("透過 'self' 存取物件, 類別, 父類別命名空間")
        print("物件命名空間:",dir(self))
        print(self.iv)        
        print(self.__piv)   
        print(self.siv)       
        print("類別命名空間:",dir(C))
        print(self.cv)      
        print(self.m2())    
        print(self.__pcv) 
        print(self.__pm())
        print("父類別命名空間:",dir(SC))
        print(self.sm())
        print(self.scv) 
import cs
c = cs.C()
c.m()
## 直接存取區域、全域、和內建命名空間
## 區域命名空間: ['self', 'p', 'lv']
## 參數: p
## 區域變數: lv
## 全域命名空間: ['__name__', '__doc__', '__package__', '__loader__', '__spec__', '__file__', '__cached__', '__builtins__', 'mv', 'mf', 'SC', 'C']
## 模組變數: mv
## 模組函式: mf()
## 透過 'self' 存取物件, 類別, 父類別命名空間
## 物件命名空間: ['_C__pcv', '_C__piv', '_C__pm', '_SC__pscv', '_SC__psiv', '_SC__spm', '__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__', 'cv', 'iv', 'm', 'm2', 'scv', 'siv', 'sm']
## 物件變數: self.xi
## 私有物件變數: self.__piv
## 父類別定義的物件變數: self.siv (但必須透過 SC.siv 才能賦值)
## 類別命名空間: ['_C__pcv', '_C__pm', '_SC__pscv', '_SC__spm', '__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__', 'cv', 'm', 'm2', 'scv', 'sm']
## 類別變數: self.cv (但必須透過 C.cv才能賦值)
## 方法: self.m2()
## 私有類別變數: self.__pcv (但必須透過 C.__pcv才能賦值)
## 私有方法: self.__pm()
## 父類別命名空間: ['_SC__pscv', '_SC__spm', '__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__', 'scv', 'sm']
## 父類別的方法: self.sm()
## 父類別的類別變數: self.scv

多重繼承

多重繼承關係

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

抽象基礎類別(Abstract Base Class, ABC)

class Base:
  def foo(self):
    raise NotImplementedError()
  
  def bar(self):
    raise NotImplementedError()
  
  
class Concrete(Base):
  def foo(self):
    return "foo() 被呼叫"    # 實作foo方法,但忘記實作bar方法
b = Base()
# b.foo()
# NotImplementedError: 
c = Concrete()
c.foo()
# c.bar()
# NotImplementedError: 
## 'foo() 被呼叫'
from abc import ABC, abstractclassmethod

class Base(ABC):
  @abstractclassmethod
  def foo(self):
    pass
  
  @abstractclassmethod
  def bar(self):
    pass
  
class Concrete(Base):
  def foo(self):
    pass
issubclass(Concrete, Base)
## True
type(Concrete), type(Base)
## (<class 'abc.ABCMeta'>, <class 'abc.ABCMeta'>)
# c = Concrete()
# TypeError: Can't instantiate abstract class Concrete with abstract method bar
# b = Base()
# TypeError: Can't instantiate abstract class Base with abstract methods bar, foo
# class TypedListList(list):
#     def __init__(self, example_element, initial_list=[]):
#         self.type = type(example_element)
#         if not isinstance(initial_list, list):
#             raise TypeError("Second argument of TypedList must " 
#                             "be a list.")
#         for element in initial_list: 
#             self.__check(element)
#         super().__init__(initial_list)
# 
#     def __check(self, element):
#         if type(element) != self.type:
#             raise TypeError("Attempted to add an element of " 
#                             "incorrect type to a typed list.")
# 
#     def __setitem__(self, i, element):
#         self.__check(element)
#         super().__setitem__(i, element)

# >>> x = TypedListList("", 5 * [""])
# >>> x[2] = "Hello"
# >>> x[3] = "There"
# >>> print(x[2] + ' ' + x[3])
# >>> x + TypedListList("", 3 * ["str"])
# >>> x[:]
# >>> del x[2]
# >>> x[:]
# >>> x.sort()
# >>> x[:]

取得物件的型別type()

type(5)
## <class 'int'>
type(["NCCU", "Good"])
## <class 'list'>

比較與檢查型別

type("Hello") == type("Goodbye")
## True
type("Hello") == type(5)
## False
type('')
# <class 'str'>
## <class 'str'>
type(str)
## <class 'type'>
type(list)
## <class 'type'>
s = "Hello"
type(s) == str
## True
type(s) == list
## False

取得物件所屬的類別與繼承結構

class A:
  pass

class B(A):
  pass
b = B()
b
## <__main__.B object at 0x7fc52dc08e50>
B
## <class '__main__.B'>
type(B)
## <class 'type'>
type(b)
## <class '__main__.B'>
b.__class__
## <class '__main__.B'>
b.__class__ == B
## True
b.__class__.__name__
## 'B'
b.__class__.__base__  
## <class '__main__.A'>
class C:
  pass

class D(C):
  pass

class E(D):
  pass

x = 12
c = C()
d = D()
e = E()

isinstance(x, E)
## False
isinstance(c, E)
## False
isinstance(e, E)
## True
isinstance(e, D)
## True
isinstance(e, B)
## False
isinstance(d, E)
## False
issubclass(C, D)
## False
issubclass(E, D)
## True
issubclass(E, B)
## False
issubclass(e.__class__, D)
## True
issubclass(D, D)
## True