Stop writing classes

使用 Function 代替 Class

  • 如果一個 class 通常只被 instantiated 或是直接使用一次, 應該只需要寫成 function 就好.

    1
    2
    3
    4
    5
    6
    7
    8
    
    class Greeting(object)
        def __init__(self, greeting='hello'):
            self.greeting = greeting
        def greet(self, name):
            return '%s! %s' % (self.greeting, name)
    
    greeting = Greeting('hola')
    print greeting.greet('bob')
    
  • 如果常使用同樣的 argument 來 instance class, 用 functool.partial 來簡化.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    def greet(name):
        ob = Greeting('hola')
        print ob.greet('bob')
        return
    
    def greet(greeting, target):
        return '%s! %s' % (self.greeting, name)
    
    import functools
    greet = functools.partial(greet, 'hola')
    greet('bob)
    
  • 當一個 class 只有 init 和一個 method, 就直接使用 function, 不要為了預留擴充性而使用 class, 等到真的要擴充的時候再 refactor 成 class 就好.

  • Namespace 應該力求單純 最好能做到 module → function/class

    1
    
    MuffinMail.MuffinHash.MuffinHash(foo=3)
    

Exception

  • 盡可能重複使用
    BookNotFoundError, CupNotFoundError 可以統一使用 NotFoundError
  • 使用 standard library 裡面已經有的
    NotFoundError 可以直接使用 standard lib 的 LookupError
  • “exception” 不應該是 Exception 名稱的一部分
    exception 已經是 exception object 了, 不需要額外再說一次: NotFoundException 應該直接用 NotFound
  • Sub Classing exception 可以降低 exception 數量 同時保持 catch 特定 exception 的能力

其他

  • 小心過度簡化反而降低 reusability
  • 用 class attributes 來作為 namespace 區隔是可以接受的 (like enum)

Example: Conway’s Game of Life

  • Class 版本

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    class Cell(object):
        def __init__(self, x, y, alive=True):
            self.x = x
            self.y = y
            self.alive = alive
            self.next = None
        def neighbors(self):
            yield (self.x + 1, self.y)
            yield (self.x + 1, self.y + 1)
            # ...
            yield (self.x + 1, self.y)
    
    class Board(object):
        def __init__(self):
            self.cells = {} # {(x,y): Cell()}
        def advance(self):
            for (x, y), cell in self.cells.items():
                if len(cell.neighbors) > 3:
                    cell.next = False
    
  • 簡化: Function 版本

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    def neighbors(point):
        x, y = point
        yield x + 1, y
        yield x - 1, y
        yield x, y + 1
        yield x, y - 1
        yield x + 1, y + 1
        yield x + 1, y - 1
        yield x - 1, y + 1
        yield x - 1, y - 1
    
    def advance(board)
        newstate = set()
        recalc = board | set(itertools.chain(*map(neightbors, board)))
        for point in recalc:
            count = sum((neigh in board)
                        for neigh in neighbors(point))
            if count == 3 or (count ==2 and point in board):
                newstate.add(point)
        return newstate
    glider = set([(0, 0), (1, 0),(2, 0),(0, 1),(1, 2)])
        for i in range(1000):
            glider = advance(glider)
    print glider
    
    • board 其實就是一個 dictionary, (e.g. self.cells)
    • alive 就是 boolean, 直接 check cell 是不是存在 dictionary 裡面就好了
    • 不需要 next, 直接 create new dictionary 就好了
  • Attention: 新的做法中無法存取舊的數值. 那些不需要 class 的 operation, 等於是不需要保持這些資料的狀況, 就是簡化的時候.