2017-05-31

Java 腦袋學 Python OOP

Python OOP 基本概念與 java 類似,但不需要預先定義 API。
class Point:
    '''This is a doc string'''

# 不需要 new 關鍵字
p = Point()
print(p) # <__main__.Point object at 0x01C5BD70>
print(p.__doc__) # This is a doc string

# 動態建立欄位
p.x = 3
p.y = 4

print('(%d, %d)' % (p.x, p.y)) # (3, 4)
import math
print(math.sqrt(p.x**2 + p.y**2)) # 5.0

__init__() 與 __str__()

當然也可以預先定義屬性(API),這裡的__init__() 就是 Java 的 constructor,並定義 __str__() 回傳的字串,也就是 Java 裡的 toString()。
class Point:
    '''This is a doc string'''
    def __init__(self, x=0, y=0): # 可以給預設值,也可以不要
        self.x = x
        self.y = y
    def __str__(self):
        return 'Point (%d, %d)' % (self.x, self.y)

p = Point() # 使用預設值
print(p) # Point (0, 0)
p.x = 3
p.y = 4
print(p) # Point (3, 4)

print(Point(1, 2)) # Point (1, 2) 使用自訂值

物件特徵

和 Java 一樣,Python 物也是 pass by reference 與 mutable。

class Point:
    '''This is a doc string'''
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
    def __str__(self):
        return 'Point (%d, %d)' % (self.x, self.y)

p = Point()
# print(p.z) # 存取未定義的屬性會得到 AttributeError
print(hasattr(p, 'x')) # True
print(hasattr(p, 'z')) # False

# try-except
try:
    z = p.z
except AttributeError:
    z = 0
print(z) # 0

# 型別檢查
print(type(p)) # <class '__main__.Point'>
print(isinstance(p, Point)) # True

克隆

class Point:
    '''This is a doc string'''
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
    def __str__(self):
        return 'Point (%d, %d)' % (self.x, self.y)

p = Point()
p2 = p
print(p is p2) # True
print(p == p2) # True
p.x = 5
print(p2) # Point (5, 0)

import copy
p3 = copy.copy(p) # 淺層複製

print(p is p3) # False,已經是不同物件(reference)
print(p == p3) # False,理論上應該是 True,但自訂物件預設的 == 是 is
p3.y = 10
print(p) # Point (5, 0)
print(p2) # Point (5, 0)
print(p3) # Point (5, 10)

p4 = copy.deepcopy(p) # 深層克隆

__eq__() 與 ==

透過自訂 __eq__() 來實做 == 的行為。
class Point:
    '''This is a doc string'''
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
    def __str__(self):
        return 'Point (%d, %d)' % (self.x, self.y)
    def __eq__(self, other):
        return (self.x, self.y) == (other.x, other.y)

p = Point()
import copy
p3 = copy.copy(p)

print(p is p3) # False
print(p == p3) # True,透過自訂 __eq__() 來實做 == 的行為,取代預設的 is

Class and instance attributes

class Point:
    '''This is a doc string'''
    original = 0 # class attribute
    def __init__(self, x=0, y=0):
        self.x = x # instance attributes
        self.y = y
    def __str__(self):
        return 'Point (%d, %d)' % (self.x, self.y)

p = Point(3, 4)
print(p) # Point (3, 4)
print(Point.original) 

Static and instance method

在 Python,method 可以同時是 static 與 instance。
class Point:
    '''This is a doc string'''
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
    def __str__(self):
        return 'Point (%d, %d)' % (self.x, self.y)
    def print(self):
        return self.__str__()

p = Point(3, 4)
print(Point.print(p)) # Point (3, 4) static 呼叫
print(p.print()) # Point (3, 4) instance 呼叫

運算子重載 Operator overloading

只要 class 定義了 __add__(),就可以對 object 使用 +。
class Point:
    '''This is a doc string'''
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
    def __str__(self):
        return 'Point (%d, %d)' % (self.x, self.y)
    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

p1 = Point(3, 4)
p2 = Point(2, 1)
print(p1) # Point (3, 4)
print(p2) # Point (2, 1)
print(p1 + p2) # Point (5, 5)
還有其他運算子重載可以定義(__sub__、__mul__、__mod__等),當然前提是情境必須「合適」。

上面的重載是算術運算子,也可以重載比較運算子,前提也是情境必須「合適」。

甚至可以針對不同型別進行運算。
class Point:
    '''This is a doc string'''
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y
    def __str__(self):
        return 'Point (%d, %d)' % (self.x, self.y)
    def __add__(self, other):
        if isinstance(other, Point):
            return Point(self.x + other.x, self.y + other.y)
        else:
            return Point(self.x + other, self.y)
    def __radd__(self, other):
        return Point(self.x, self.y + other)

p1 = Point(3, 4)
p2 = Point(2, 1)
print(p1) # Point (3, 4)
print(p2) # Point (2, 1)
print(p1 + p2) # Point (5, 5) 呼叫 __add__
print(p1 + 3) # Point (6, 4) 呼叫 __add__
print(5 + p1) # Point (3, 9) 呼叫 __radd__
甚至只要 class 定義了 __add__(),也就是支援 +,其他使用 + 的函式都可以用了,例如 sum()。
print(sum([Point(1, 2), Point(2, 3), Point(3, 4)])) # Point (6, 9)

繼承

目前只知道 method 有繼承下來,但 attribute 沒有,也不知道可不可以存取 super class 的 attribute。
class Point3D(Point):
    def __init__(self, x=0, y=0, z=0):
        self.x = x
        self.y = y
        self.z = z

p2 = Point3D(3, 4, 5)
print(p2) # Point (3, 4)

Debug or Reflection

可以使用 vars()、getattr() 與 setattr() 檢視或操作物件屬性。
p = Point(3, 4)
print(vars(p)) #{'x': 3, 'y': 4} dict of attributes and values
print(getattr(p, 'x')) # 3

setattr(p, 'x', 5)
print(getattr(p, 'x')) # 5
---
---
---

沒有留言:

張貼留言