2019/1/13

decorator in python

python 的 decorator 功能,使用的語法看起來很像 Java 的 annotation,是在 function 前面加上 @decorator,雖然名稱是 decorator,但實際上比 Decorator Design Pattern 的功能還要多一點。


decorator class


function decorator 使用起來,就是直接在 function 定義前面,加上 @my_decorator。


@my_decorator
def aFunction():
    print("inside aFunction()")

實際上,當 compiler 編譯 aFunction 時,會將 aFunction 整個 function 傳入 myDecorator,有取代原本 aFunction 的感覺,也有點像是 my_decorator(aFunction) 這樣的意思。


要實作 my_decorator 必須要實作 __init__ 以及 __call__ 這兩個 function,__init__會在一開始就被呼叫,而 __call__ 是在結束時被呼叫。


class my_decorator(object):

    def __init__(self, f):
        print("inside my_decorator.__init__()")
        f()

    def __call__(self):
        print("inside my_decorator.__call__()")

@my_decorator
def aFunction():
    print("inside aFunction()")

print("Finished decorating aFunction()")

aFunction()

執行結果如下,在程式中,因為 decorator 初始化的關係,會先呼叫 my_decorator.__init__(),在這個函數裡面,會取得 aFunction 的函數定義,所以可以在裡面呼叫 aFunction,在 aFunction 結束後,才列印了 Finished 的部分。


而最後是因為呼叫 aFunction(),所以轉為呼叫 my_decorator.__call__()。


inside my_decorator.__init__()
inside aFunction()
Finished decorating aFunction()
inside my_decorator.__call__()

也就是說,decorator 是在 init 的時候,就已經取得了 aFunction 的定義,而不是在呼叫 aFunction 時,才傳入 my_decorator。


decorator 其實就只是將 function 傳入另一個 function 的 syntax sugar。




在剛剛的例子中,我們在 __init__ 直接呼叫了 f(),但以下這個例子,才是比較重要的功能,他先將 f 儲存到變數中,然後等到 __call 才使用 f()


class entry_exit(object):

    def __init__(self, f):
        self.f = f

    def __call__(self):
        print("Entering", self.f.__name__)
        self.f()
        print("Exited", self.f.__name__)

@entry_exit
def func1():
    print("inside func1()")

@entry_exit
def func2():
    print("inside func2()")

func1()
func2()

所以可以在呼叫 func1() 的前面以及執行完成的後面,在 __call__裡面呼叫 self.f() 的前後加上自己需要增加的 code


Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2

function as decorator


剛剛是使用 class 作為 decorator,但可以直接定義一個 decorator function,以下的例子中,定義了 decorator function 以 f 為參數,在裡面定義了新的 new_f,取代了原本的 f


def entry_exit(f):
    def new_f():
        print("Entering", f.__name__)
        f()
        print("Exited", f.__name__)
    return new_f

@entry_exit
def func1():
    print("inside func1()")

@entry_exit
def func2():
    print("inside func2()")

func1()
func2()
print(func1.__name__)

執行結果為,因為 new_f 已經取代了 f,所以 func1.__name__ 的名稱結果是 new_f


Entering func1
inside func1()
Exited func1
Entering func2
inside func2()
Exited func2
new_f

如果覺得奇怪,還是可以刻意將 new_f 的名稱修改為跟 f 一樣


def entry_exit(f):
    def new_f():
        print("Entering", f.__name__)
        f()
        print("Exited", f.__name__)
    new_f.__name__ = f.__name__
    return new_f

Decorator with Arguments


因為 decorator 其實也是呼叫一個 function,所以可以直接在 decorator 上加上參數,以下是在 decorator class 裡面取得 decorator 參數的例子


class decorator_with_arguments(object):

    def __init__(self, arg1, arg2, arg3):
        print("Inside __init__()")
        self.arg1 = arg1
        self.arg2 = arg2
        self.arg3 = arg3

    def __call__(self, f):
        print("Inside __call__()")
        def wrapped_f(*args):
            print("Inside wrapped_f()")
            print("Decorator arguments:", self.arg1, self.arg2, self.arg3)
            f(*args)
            print("After f(*args)")
        return wrapped_f

@decorator_with_arguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
    print('sayHello arguments:', a1, a2, a3, a4)

print("After decoration")

print("Preparing to call sayHello()")
sayHello("say", "hello", "argument", "list")
print("after first sayHello() call")
sayHello("a", "different", "set of", "arguments")
print("after second sayHello() call")

執行結果如下,在 __init__,可以取得 decorator 的參數,並在 __call__ 裡面使用這些參數


Inside __init__()
Inside __call__()
After decoration

Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call

Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call

function decorator with arguments


同樣的 function decorator 也可以使用參數


def decorator_function_with_arguments(arg1, arg2, arg3):
    def wrap(f):
        print("Inside wrap()")
        def wrapped_f(*args):
            print("Inside wrapped_f()")
            print("Decorator arguments:", arg1, arg2, arg3)
            f(*args)
            print("After f(*args)")
        return wrapped_f
    return wrap

@decorator_function_with_arguments("hello", "world", 42)
def sayHello(a1, a2, a3, a4):
    print('sayHello arguments:', a1, a2, a3, a4)

print("After decoration")

print("Preparing to call sayHello()")
sayHello("say", "hello", "argument", "list")
print("after first sayHello() call")
sayHello("a", "different", "set of", "arguments")
print("after second sayHello() call")

Inside wrap()
After decoration

Preparing to call sayHello()
Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: say hello argument list
After f(*args)
after first sayHello() call

Inside wrapped_f()
Decorator arguments: hello world 42
sayHello arguments: a different set of arguments
After f(*args)
after second sayHello() call

因為被 decorator 修飾的原始 function,無法在實作 decorator 時就預先知道參數的個數,因此在定義 wrapper function 時,是定義為 def wrapped_f(*args):。


其中 *args 代表不固定個數的參數,如果要支援有參數名稱的參數,則可以使用 **kwargs


多個 decorator


@a
@b
@c
def f ():
    pass

如果有好幾個 decorator,那麼實際上呼叫的順序,是以下這樣


f = a(b(c(f)))

處理時間的 decorator


以下這是計算函數處理時間的 decorator


from time import time

def timer(func):
    def wraper(*args, **kwargs):
        before = time()
        result = func(*args, **kwargs)
        after = time()
        print("elapsed: ", after - before)
        return result
    return wraper

@timer
def add(x, y = 10):
    return x + y

@timer
def sub(x, y = 10):
    return x - y

print("add(10)",       add(10))
print("add(20, 30)",   add(20, 30))

執行結果為


elapsed:  9.5367431640625e-07
add(10) 20
elapsed:  1.1920928955078125e-06
add(20, 30) 50

函數參數的型別檢查 pylimit


pylimit2


利用裝飾器給python的函式加上型別限制


pylimit


python 因為是動態資料型別語言,變數不需要定義資料型別,隨時會改變,但是在實作時,常常會遇到一個問題,就是實作的 function 只能處理某些資料型別的參數,程式如果寫錯,很容易就 crash。


上面的兩個 pylimit 套件,就是利用 decorator 的做法,在裡面加上 function 參數的資料型別定義,然後就可以在呼叫該函數時,進行資料型別檢查,這樣就不需要在實作時,寫上很多 type() 檢查的程式碼。


References


Decorators


Python Decorator(裝飾器)


[Python] Decorator 介紹


理解 Python 裝飾器看這一篇就夠了


萬惡的 Python Decorator 究竟是什麼?


Python Decorator 入門教學

2019/1/7

python 簡易 websocket server


Websocket Server 是一個簡單的 websocket server,沒有任何其他的關聯的套件,只需要 python 標準的 sdk。


由 github 下載原始檔 zip 後,將 server.py 以及 websocket_server 目錄複製到 project 裡面。


在 server.py 的 message_received 加上一行廣播訊息的程式碼。


server.py


from websocket_server import WebsocketServer

# Called for every client connecting (after handshake)
def new_client(client, server):
    print("New client connected and was given id %d" % client['id'])
    server.send_message_to_all("Hey all, a new client has joined us")


# Called for every client disconnecting
def client_left(client, server):
    print("Client(%d) disconnected" % client['id'])


# Called when a client sends a message
def message_received(client, server, message):
    if len(message) > 200:
        message = message[:200]+'..'
    print("Client(%d) said: %s" % (client['id'], message))
    server.send_message_to_all("Client(%d) said: %s" % (client['id'], message))


PORT=9001
server = WebsocketServer(PORT)
server.set_fn_new_client(new_client)
server.set_fn_client_left(client_left)
server.set_fn_message_received(message_received)
server.run_forever()

另外製作一個 client.html


<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <title>html5 websocket特性</title>
        <style>
            body{
                overflow: hidden;
            }
            h2{
                margin-top: 30px;
                text-align: center;
                background-color: #393D49;
                color: #fff;
                ont-weight: normal;
            padding: 15px 0
            }
            #chat{
                text-align: center;
            }
            #win{
                margin-top: 20px;
                text-align: center;
            }
            #sse{
                margin-top: 10px;
                text-align: center;
            }
            #sse button{
                background-color: #009688;
                color: #fff;
                height: 40px;
                border: 0;
                border-radius: 3px 3px;
                padding-left: 10px;
                padding-right: 10px;
                cursor: pointer;
            }
        </style>
        <script src="http://code.jquery.com/jquery-1.12.4.min.js"></script>
    </head>
    <body>
        <h2>聊天室</h2>
            <div id="chat">
                <textarea id="history" cols="80" rows="20"></textarea>
            </div>

            <div id="win">
                <textarea id="messagewin" cols="80" rows="5"></textarea>
            </div>

            <div id="sse">
                <button onclick="sendMessage()">傳送對話</button>
            </div>

        <script type="text/javascript">
            var oHistory = $('#history');
            var oWin = $('#messagewin');

            if ("WebSocket" in window){
                console.log("您的瀏覽器支援 WebSocket!");
                var ws = new WebSocket("ws://127.0.0.1:9001");
                //var ws = new WebSocket("ws://localhost:9001");
                ws.onopen = function(){
                    console.log("websocket 已連線上");
                }

                ws.onmessage = function (evt) {
                    var dataReceive = evt.data;
                    console.log("資料已接收..."+dataReceive);
                    $('#history').val($('#history').val()+dataReceive+"\n");
                };

                ws.onclose = function() {
                    console.log("連線已關閉...");
                };

            }else{
                // 瀏覽器不支援 WebSocket
                console.log("您的瀏覽器不支援 WebSocket!");
            }

            function sendMessage(){
                var dataSend = oWin.val().trim();
                ws.send(dataSend);
                oWin.val('');
            }
        </script>
    </body>
</html>

將 client.html 放在任意一個 webserver 上,因為 client.html 會連接到 127.0.0.1 的 websocket server,以 python server.py 啟動 server 後,打開兩個 browser tab,打開 client.html



兩個視窗間,因為剛剛增加的那一行廣播訊息的 code,所以可以看到所有訊息。


References


python與html5 websocket開發聊天對話窗

2018/12/24

python 函數的可變參數 *args 和 **kwargs


一般函數的參數個數都是固定的,但如果遇到參數數量不固定的狀況,通常會將某些參數填上預設值,在 python function 可以支援兩種可變數量的參數 *args 和 **kwargs。


以下例子中的 fun 雖然定義了三個參數,但是後面兩個填上預設值,呼叫該函數時,就可以忽略 b 與 c,直接使用預設值。


def fun(a,b=2,c=3):
    print("a={}, b={}, c={}".format(a,b,c))

fun(1)

fun(1,22,33)

執行結果


a=1, b=2, c=3
a=1, b=22, c=33

*args是可變的positional arguments列表,**kwargs是可變的keyword arguments列表。兩個可以同時使用,但在使用時,*args必須在**kwargs的前面,因為positional arguments,有位置順序的對應,必須位於keyword arguments之前。


以下的例子,是定義 fun 有一個必要參數 a,以及可變的 *args


def fun(a, *args):
    print("a={}".format(a))
    for arg in args:
        print('Optional argument: {}'.format( arg ) )

fun(1,22,33)

執行結果為


a=1
Optional argument: 22
Optional argument: 33

如果同時加上 **kwargs


def fun(a, *args, **kwargs):
    print("a={}".format(a))
    for arg in args:
        print('Optional argument: {}'.format( arg ) )

    for k, v in kwargs.items():
        print('Optional kwargs argument key: {} value {}'.format(k, v))

fun(1,22,33, k1=44, k2=55)

執行結果為


a=1
Optional argument: 22
Optional argument: 33
Optional kwargs argument key: k1 value 44
Optional kwargs argument key: k2 value 55

除了在定義函數的部分,在呼叫函數時,也可以使用 *args 及 **kwargs


print("")
args = [1,2,3,4]
fun(*args)

print("")
kwargs = {'k1':10, 'k2':11}
fun(1, **kwargs)

print("")
fun(1, *args, **kwargs)

執行結果為


a=1
Optional argument: 2
Optional argument: 3
Optional argument: 4

a=1
Optional kwargs argument key: k1 value 10
Optional kwargs argument key: k2 value 11

a=1
Optional argument: 1
Optional argument: 2
Optional argument: 3
Optional argument: 4
Optional kwargs argument key: k1 value 10
Optional kwargs argument key: k2 value 11

References


Python定義的函數(或調用)中參數args 和*kwargs的用法


[Python] 令新手驚呆的 **kwargs


PYTHON中如何使用ARGS和*KWARGS


理解 Python 中的 *args 和 **kwargs

2018/12/10

python 如何處理 json


json: JavaScript Object Notation 是 javascript 的子集合,是一種輕量級的資料交換格式,比 XML 簡單,也容易閱讀及編寫,處理過程分為序列化及反序列化兩個部分,分別是 Marshalling/Encoder 及 Unmarshalling/Decoder。


Marshalling/Encoder 就是將 python 的資料物件轉換為 json 字串,使用 json.dumps


Unmarshalling/Decoder 是將 json 字串轉換為 python 物件,使用 json.loads


使用 dumps, loads 的簡單範例


這是 python 的 dict 資料型別,因為 dict 沒有字串的表示方式,透過 repr 將 dict 轉換為可以列印的資料


json_obj = {
            'name' : 'Apple',
            'shares' : 100,
            'price' : 542.1
        }

logging.info( "json_obj type = "+str(type(json_obj))+ ", data =" + repr(json_obj) )

dict 可以透過 json.dumps 轉換為 json 字串


json_string = json.dumps(json_obj)

logging.info( "json_string type="+ str(type(json_string))+ ", data=" + json_string )

再透過 json.loads 將 json 字串轉換為 dict


json_object = json.loads(json_string)

        logging.info( "json_object type="+str(type(json_object))+", data="+repr(json_object) )

測試結果


json_obj type = <class 'dict'>, data ={'name': 'Apple', 'shares': 100, 'price': 542.1}

json_string type=<class 'str'>, data={"name": "Apple", "shares": 100, "price": 542.1}

json_object type=<class 'dict'>, data={'name': 'Apple', 'shares': 100, 'price': 542.1}

由 python 資料轉換為 json 的對照表為


python json
dict object
list, tuple array
str, unicode string
int, long, float number
True true
False false
None null

由 json 轉換為 python 資料的對照表為


json python
object dict
array list
string unicode
number(int) int,long
number(real) float
true True
false False
null None

pprint 及 dumps 的 indent, sort_keys 參數


如果 json 的字串比較長,列印到 console 時,可能會比較難查閱需要的資料,這時候有兩種方式可以處理


使用 pprint 可以直接用 pprint(json_obj)


from pprint imort pprint

json_obj = {
            'name' : 'Apple',
            'shares' : 100,
            'price' : 542.1
        }

logging.info( "json_obj type = "+str(type(json_obj))+ ", data =" + repr(json_obj) )

pprint(json_obj)

執行結果為


{'name': 'Apple', 'price': 542.1, 'shares': 100}

在 dumps 加上 indent 參數


json_string = json.dumps(json_obj, indent=4)

執行結果為


json_string type=<class 'str'>, data={
    "name": "Apple",
    "shares": 100,
    "price": 542.1
}

在 dumps 加上 sort_keys 可在轉換 json 時,同時進行 key 的排序


json.dumps({"c": 0, "b": 0, "a": 0}, sort_keys=True)

執行結果


{"a": 0, "b": 0, "c": 0}

loads 的 object_pairs_hook 與 object_hook 參數


通常 json 會轉換為 python 的 dict 及 list,可使用 object_pairs_hook,將 json 轉換為 OrderedDict


s = '{"name": "ACME", "shares": 50, "price": 490.1}'
        from collections import OrderedDict
data = json.loads(s, object_pairs_hook=OrderedDict)

logging.info( "json_object type="+str(type(data))+", data="+repr(data) )

執行結果


json_object type=<class 'collections.OrderedDict'>, data=OrderedDict([('name', 'ACME'), ('shares', 50), ('price', 490.1)])

也可以用 object_hook 將 json 轉換為 python 物件


class JSONObject:
    def __init__(self, d):
        self.__dict__ = d

data2 = json.loads(s, object_hook=JSONObject)
logging.info( "json_object type="+str(type(data2))+", name="+data2.name+", shares="+str(data2.shares)+", price="+str(data2.price) )

執行結果


json_object type=<class '__main__.JSONObject'>, name=ACME, shares=50, price=490.1

skipkeys


在 dumps 如果遇到 key 不是字串時,會發生 TypeError,可使用 skipkeys 略過無法處理的 key


data =  { 'b' : 789 , 'c' : 456 ,( 1 , 2 ): 123 }
print( json.dumps(data,skipkeys = True) )

執行結果


{"b": 789, "c": 456}

自訂 python 物件的 轉換函數


如果是自訂的 python 類別,通常是無法直接透過 dumps 序列化,會發生 error


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
p = Point(2, 3)

print(p)

json.dumps(p)

執行結果


TypeError: Object of type 'Point' is not JSON serializable

解決方式:


首先定義兩個獨立的 util function,object2dict 是將物件轉換為 dict,dict2object 則是將 dict 轉換為 object,在 dict2object 裡面會使用 dict 的 __module__ 及 __class__ 這兩個資料,用來正確找到 class 的定義


def object2dict(obj):
    d = {
        '__class__': obj.__class__.__name__,
        '__module__': obj.__module__
    }
    # d.update( vars(obj) )
    d.update(obj.__dict__)
    return d


def dict2object(d):
    if '__class__' in d:
        module_name = d.pop( '__module__' )
        class_name = d.pop( '__class__' )
        logging.debug("module_name="+str(module_name)+", class_name="+class_name)

        # # from A import B
        import importlib
        objmodule = importlib.import_module(module_name)
        cls = getattr(objmodule, class_name)

        # objmodule = __import__(module_name)
        # cls = getattr(objmodule, class_name)

        # # use class directly
        # cls = classes[class_name]
        # # Make instance without calling __init__
        obj = cls.__new__(cls)
        for key, value in d.items():
            setattr(obj, key, value)
        return obj
    else:
        inst = d
    return inst

如果另外有一個類別定義在 test.point.py 裡面


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__( self ):
        return 'Point Object x : %d , y : %d' % ( self.x, self.y)

透過 object2dict 及 dict2object 就可以協助進行物件與 json 的轉換


from test import point
p = point.Point(2, 3)
logging.info(p)

json_str = json.dumps(p, default=object2dict)
logging.info(json_str)

o = json.loads(json_str, object_hook=dict2object)
logging.info( "json_object type="+str(type(o))+", data="+repr(o) )

執行結果


{"__class__": "Point", "__module__": "test.point", "x": 2, "y": 3}

module_name=test.point, class_name=Point
2018-09-03 16:01:00,274 INFO {95322,MainProcess} 

json_object type=<class 'test.point.Point'>, data=Point Object x : 2 , y : 3

有時會遇到 list of Point,這時需要修改 object2dict 讓他可以處理 obj 是 list 的狀況


def object2dict(obj):
    d = {}
    if isinstance(obj, list):
        return json.dumps(obj, default=object2dict)
    else:
        # d = {
        #   '__class__': obj.__class__.__name__,
        #   '__module__': obj.__module__
        # }
        d['__class__'] = obj.__class__.__name__
        d['__module__'] = obj.__module__
        # d.update( vars(obj) )
        d.update(obj.__dict__)
        return d

References


20.2. json — JSON encoder and decoder


Python處理JSON


6.2 讀寫JSON數據


Json概述以及python對json的相關操作

2018/12/3

一樣都是 PM,Project Manager 跟 Product Manager 有什麼不一樣


英文縮寫 PM 有兩種,一個是比較常聽到的 Project Manager,一個是 Product Manager,專業的 Product Manager 比較少見,大部分都是專案管理。另外還有人也將 Product Marketing 列入討論,不過這部分變成是 Product Manager 與 Marketing 的差異,管理跟行銷本質上就不一樣,這部分比較容易理解。


先看看不同的網路文章的意見:



一個 Project Manager 最大的任務就是將 Project 如期、如規格「正確」的執行,而這過程中,我想分為兩種層面的技能:技術與溝通。


技術層面上,PM 就是把業主/老闆心中想要的功能「翻譯」成工程師可以開發的規格、確認功能可行性、與業主/老闆確認規格、評估時間、排時程、確認優先順序、掌握開發進度、測試、驗收。溝通層面上,要去對專案所有關係人,進行不同協調,主要就是業主跟工程師之間的橋樑。


Product Manager 是以「使用者」出發、產品是否解決使用者的痛點、是否真正滿足使用者需求。你要討好的對象不是老闆、不是團隊成員、不是投資者、不是合作廠商,而是「使用者」。產品,就是一個隨時都有真實 user 在使用的東西。


所以做一個 Product Manager、或者待在一個做產品的團隊都可以深刻的感覺到,壞的情況是,當產品出問題或斷線,客服電話馬上進來、客服信箱馬上開始被罵;好的情況是,你也會收到使用者溫馨的回饋與感謝。



項目是局部優化工作。我們讓一組人為客戶創建(或改進已有的)功能,然後交給一個不知道為什麼變更、也不知道這個變更如何實現的團隊。變革的成本很高,需要花很長時間才能交到客戶手中。


圍繞產品(或服務)構建的組織有優勢,因為他們的組織結構可以監督整個工作過程,就是說,從想法到客戶,並基於客戶的反饋或反應想出新點子,形成閉環。我這樣說,是因為團隊為自己負責,對所提供的方案、交付質量、客戶體驗和滿意度有主人翁精神。進行看上去也許很小的變革,但是要允許想法快速地傳遞到客戶手中。



根據美國產品管理協會PDMA的定義,所謂的「產品管理(Product Management)」是指:在新產品開發的過程當中,通過不斷監控和調整市場組合的基本要素(其中包括產品及自身特色、溝通策略、配銷通路和價格),隨時確保產品或者服務能充分滿足客戶需求(Ensuring over time that a product or service profitably meets the needs of customers by continually monitoring and modifying the elements of the marketing mix, including: the product and its features, the communications strategy, distribution channels and price.)。而產品經理就是針對上述特定產品活動肩負所有責任的人。



Project Manager(專案經理):很重要,但通常是在食物鏈的底層
Product Marketing(產品行銷):很重要,但對於產品通常沒有實權
Product Manager(產品經理):通常在食物鏈頂層,但格局可大可小



行銷思維:盡可能讓對的受眾,精準接收到我想傳達的資訊,並做出行動。
專案管理思維:資源與風險的管理與控制,多方平衡。
產品思維:了解顧客的痛點,提出解決方法。
營運思維:流程觀與協作思維,讓各個部門順暢協作。



企業在新產品開發過程中,經常遭遇許多的瓶頸與風險,使得開發專案經常發生延遲、失敗的問題。歸咎其因,除了缺乏一套完整的新產品開發流程做好專案管理掌控之外,更缺少一位可以來整合各部門資源,掌控開發專案的品質、成本與時程,以增進產品競爭力的產品經理。


根據Wheelwright and Clark(1992)在《Creating Project Plans to Focus Product Development》的研究報告指出:企業的新產品開發專案分為以下四種類型:


1.衍生產品專案(Derivative Projects)


局部創新與改進的產品專案,包括對現有產品改良以提升功能、降低成本,或為滿足不同區隔市場客戶的需求而改變功能外型。開發過程的風險較低,專案時程較短,所需資源也比較有限。


2.突破性產品專案(Breakthrough Projects)


突破性產品專案與企業當前主流產品開發專案有很大差異,資源投入的需求量高,開發風險與不確定性很高,但短期間很難產生成果與利潤,因此開發過程中遭遇的阻力就比較大。管理的複雜度遠要高過於其他三種專案類型。


3.平台產品專案(Platform Projects)


平台產品的改變幅度遠高過於衍生產品,但卻不像突破性產品使用從未發展過的新技術與新材料。平台產品比較著重於系統上的創新,衍生產品或許只改變產品中的一項特質(如:成本、品質、產品表現等),而平台產品則針對全面的產品問題進行改善。


4.研究發展專案(R&D Projects)


研究發展專案不屬於商業化應用的範疇,但因為它是上述三種產品開發專案的先驅活動,而且也會佔用相當比例的資源。




Product 與 Project 的差異在於生命週期 以及 時程,任何 Product 產品/商品,其生命週期會比 Project 長,因為身為 Product,其概念就是企業內固定在銷售的一個商品項目,而 Product 開發本身,可以切割為多個 project。


在 美國專案管理學會 Project Management Institute PMI 的定義中,專案是指一項暫時性的任務、配置,以開創某獨特性的產品或服務。專案有起迄時間。專案團隊並不是一直存在的,而是為了某一特定目的或產品組成的。


『專案管理』是將管理知識、技術、工具、方法綜合運用到任何一個專案之上,使專案得以符合要求。 』專案經過啟動、規劃、執行、監控、結案這五大流程,而專案經理人則運用專案管理的九大知識對專案進行管理,讓專案可以如期、符合品質要求並在成本內完成。


簡單來說,Product 每一次版本的功能與規格制定,到開發最後測試完成,這樣一個週期,完成了一個版本,則本產品的本次版本的專案就已經結束。


換句話說,專案是一時的,產品則會存在很久。


因為生命週期不同,產品管理者需要承擔的責任會比較久,至於專案管理的部分,基本上只需要在專案結束時,能夠順利結案就可以了。專案管理者要承擔的責任,就是產品本身所有相關的事情,包含產品要解決的問題,解決的方法,開發的過程,版本功能的分割,除錯,到後續的問題處理,還有維護,甚至是產品的行銷,所有的面向都要承擔責任。


至於重要性,個人認為是都很重要,遇到的問題不同,也各有各的難處。更重要的是要找到正確的負責人,制度問題可以靠人去調整及解決,但是人如果錯了,就很難彌補問題。