ProtoBuf開發(fā)者指南
譯者: | gashero |
---|
- 1 概覽
- 1.1 什么是protocol buffer
- 1.2 他們?nèi)绾喂ぷ?/li>
- 1.3 為什么不用XML?
- 1.4 聽起來(lái)像是為我的解決方案,如何開始?
- 1.5 一點(diǎn)歷史
- 2 語(yǔ)言指導(dǎo)
- 2.1 定義一個(gè)消息類型
- 2.2 值類型
- 2.3 可選字段與缺省值
- 2.4 枚舉
- 2.5 使用其他消息類型
- 2.6 嵌套類型
- 2.7 更新一個(gè)數(shù)據(jù)類型
- 2.8 擴(kuò)展
- 2.9 包
- 2.10 定義服務(wù)
- 2.11 選項(xiàng)
- 2.12 生成你的類
- 3 代碼風(fēng)格指導(dǎo)
- 3.1 消息與字段名
- 3.2 枚舉
- 3.3 服務(wù)
- 4 編碼
- 4.1 一個(gè)簡(jiǎn)單的消息
- 4.2 基于128的Varints
- 4.3 消息結(jié)構(gòu)
- 4.4 更多的值類型
- 4.5 內(nèi)嵌消息
- 4.6 可選的和重復(fù)的元素
- 4.7 字段順序
- 5 ProtocolBuffer基礎(chǔ):C++
- 6 ProtocolBuffer基礎(chǔ):Java
- 7 ProtocolBuffer基礎(chǔ):Python
- 7.1 為什么使用ProtocolBuffer?
- 7.2 哪里可以找到例子代碼
- 7.3 定義你的協(xié)議格式
- 7.4 編譯你的ProtocolBuffer
- 7.5 ProtocolBuffer API
- 7.5.1 枚舉
- 7.5.2 標(biāo)準(zhǔn)消息方法
- 7.5.3 解析與串行化
- 7.6 寫消息
- 7.7 讀消息
- 7.8 擴(kuò)展ProtocolBuffer
- 7.9 高級(jí)使用
- 8 參考概覽
- 9 C++代碼生成
- 10 C++ API
- 11 Java代碼生成
- 12 Java API
- 13 Python代碼生成
- 13.1 編譯器的使用
- 13.2 包
- 13.3 消息
- 13.4 字段
- 13.4.1 簡(jiǎn)單字段
- 13.4.2 簡(jiǎn)單消息字段
- 13.4.3 重復(fù)字段
- 13.4.4 重復(fù)消息字段
- 13.4.5 枚舉類型
- 13.4.6 擴(kuò)展
- 13.5 服務(wù)
- 13.5.1 接口
- 13.5.2 存根(Stub)
- 14 Python API
- 15 其他語(yǔ)言
1 概覽
歡迎來(lái)到protocol buffer的開發(fā)者指南文檔,一種語(yǔ)言無(wú)關(guān)、平臺(tái)無(wú)關(guān)、擴(kuò)展性好的用于通信協(xié)議、數(shù)據(jù)存儲(chǔ)的結(jié)構(gòu)化數(shù)據(jù)串行化方法。
本文檔面向希望使用protocol buffer的Java、C++或Python開發(fā)者。這個(gè)概覽介紹了protocol buffer,并告訴你如何開始,你隨后可以跟隨編程指導(dǎo)( http://code.google.com/apis/protocolbuffers/docs/tutorials.html )深入了解protocol buffer編碼方式( http://code.google.com/apis/protocolbuffers/docs/encoding.html )。API參考文檔( http://code.google.com/apis/protocolbuffers/docs/reference/overview.html )同樣也是提供了這三種編程語(yǔ)言的版本,不夠協(xié)議語(yǔ)言( http://code.google.com/apis/protocolbuffers/docs/proto.html )和樣式( http://code.google.com/apis/protocolbuffers/docs/style.html )指導(dǎo)都是編寫 .proto 文件。
1.1 什么是protocol buffer
ProtocolBuffer是用于結(jié)構(gòu)化數(shù)據(jù)串行化的靈活、高效、自動(dòng)的方法,有如XML,不過(guò)它更小、更快、也更簡(jiǎn)單。你可以定義自己的數(shù)據(jù)結(jié)構(gòu),然后使用代碼生成器生成的代碼來(lái)讀寫這個(gè)數(shù)據(jù)結(jié)構(gòu)。你甚至可以在無(wú)需重新部署程序的情況下更新數(shù)據(jù)結(jié)構(gòu)。
1.2 他們?nèi)绾喂ぷ?/h2>
你首先需要在一個(gè) .proto 文件中定義你需要做串行化的數(shù)據(jù)結(jié)構(gòu)信息。每個(gè)ProtocolBuffer信息是一小段邏輯記錄,包含一系列的鍵值對(duì)。這里有個(gè)非常簡(jiǎn)單的 .proto 文件定義了個(gè)人信息:
message Person {
required string name=1;
required int32 id=2;
optional string email=3;
enum PhoneType {
MOBILE=0;
HOME=1;
WORK=2;
}
message PhoneNumber {
required string number=1;
optional PhoneType type=2 [default=HOME];
}
repeated PhoneNumber phone=4;
}
有如你所見(jiàn),消息格式很簡(jiǎn)單,每個(gè)消息類型擁有一個(gè)或多個(gè)特定的數(shù)字字段,每個(gè)字段擁有一個(gè)名字和一個(gè)值類型。值類型可以是數(shù)字(整數(shù)或浮點(diǎn))、布 爾型、字符串、原始字節(jié)或者其他ProtocolBuffer類型,還允許數(shù)據(jù)結(jié)構(gòu)的分級(jí)。你可以指定可選字段,必選字段和重復(fù)字段。你可以在( http://code.google.com/apis/protocolbuffers/docs/proto.html )找到更多關(guān)于如何編寫 .proto 文件的信息。
一旦你定義了自己的報(bào)文格式(message),你就可以運(yùn)行ProtocolBuffer編譯器,將你的 .proto 文件編譯成特定語(yǔ)言的類。這些類提供了簡(jiǎn)單的方法訪問(wèn)每個(gè)字段(像是 query() 和 set_query() ),像是訪問(wèn)類的方法一樣將結(jié)構(gòu)串行化或反串行化。例如你可以選擇C++語(yǔ)言,運(yùn)行編譯如上的協(xié)議文件生成類叫做 Person 。隨后你就可以在應(yīng)用中使用這個(gè)類來(lái)串行化的讀取報(bào)文信息。你可以這么寫代碼:
Person person;
person.set_name("John Doe");
person.set_id(1234);
person.set_email("jdoe@example.com");
fstream.output("myfile",ios::out | ios::binary);
person.SerializeToOstream(&output);
然后,你可以讀取報(bào)文中的數(shù)據(jù):
fstream input("myfile",ios::in | ios:binary);
Person person;
person.ParseFromIstream(&input);
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;
你可以在不影響向后兼容的情況下隨意給數(shù)據(jù)結(jié)構(gòu)增加字段,舊有的數(shù)據(jù)會(huì)忽略新的字段。所以如果使用ProtocolBuffer作為通信協(xié)議,你可以無(wú)須擔(dān)心破壞現(xiàn)有代碼的情況下擴(kuò)展協(xié)議。
你可以在API參考( http://code.google.com/apis/protocolbuffers/docs/reference/overview.html )中找到完整的參考,而關(guān)于ProtocolBuffer的報(bào)文格式編碼則可以在( http://code.google.com/apis/protocolbuffers/docs/encoding.html )中找到。
1.3 為什么不用XML?
ProtocolBuffer擁有多項(xiàng)比XML更高級(jí)的串行化結(jié)構(gòu)數(shù)據(jù)的特性,ProtocolBuffer:
- 更簡(jiǎn)單
- 小3-10倍
- 快20-100倍
- 更少的歧義
- 可以方便的生成數(shù)據(jù)存取類
例如,讓我們看看如何在XML中建模Person的name和email字段:
<person>
<name>John Doe</name>
<email>jdoe@example.com</email>
</person>
對(duì)應(yīng)的ProtocolBuffer報(bào)文則如下:
#ProtocolBuffer的文本表示
#這不是正常時(shí)使用的二進(jìn)制數(shù)據(jù)
person {
name: "John Doe"
email: "jdoe@example.com"
}
當(dāng)這個(gè)報(bào)文編碼到ProtocolBuffer的二進(jìn)制格式( http://code.google.com/apis/protocolbuffers/docs/encoding.html )時(shí)(上面的文本僅用于調(diào)試和編輯),它只需要28字節(jié)和100-200ns的解析時(shí)間。而XML的版本需要69字節(jié)(除去空白)和5000-10000ns的解析時(shí)間。
當(dāng)然,操作ProtocolBuffer也很簡(jiǎn)單:
cout << "Name: " << person.name() << endl;
cout << "E-mail: " << person.email() << endl;
而XML的你需要:
cout << "Name: "
<< person.getElementsByTagName("name")->item(0)->innerText()
<< endl;
cout << "E-mail: "
<< person.getElementsByTagName("email")->item(0)->innerText()
<< end;
當(dāng)然,ProtocolBuffer并不是在任何時(shí)候都比XML更合適,例如ProtocolBuffer無(wú)法對(duì)一個(gè)基于標(biāo)記文本的文檔建模,因?yàn)? 你根本沒(méi)法方便的在文本中插入結(jié)構(gòu)。另外,XML是便于人類閱讀和編輯的,而ProtocolBuffer則不是。還有XML是自解釋的,而 ProtocolBuffer僅在你擁有報(bào)文格式定義的 .proto 文件時(shí)才有意義。
1.4 聽起來(lái)像是為我的解決方案,如何開始?
下載包( http://code.google.com/p/protobuf/downloads/ ),包含了Java、Python、C++的ProtocolBuffer編譯器,用于生成你需要的IO類。構(gòu)建和安裝你的編譯器,跟隨README的指令就可以做到。
一旦你安裝好了,就可以跟著編程指導(dǎo)( http://code.google.com/apis/protocolbuffers/docs/tutorials.html )來(lái)選擇語(yǔ)言-隨后就是使用ProtocolBuffer創(chuàng)建一個(gè)簡(jiǎn)單的應(yīng)用了。
1.5 一點(diǎn)歷史
ProtocolBuffer最初是在Google開發(fā)的,用以解決索引服務(wù)器的請(qǐng)求、響應(yīng)協(xié)議。在使用ProtocolBuffer之前,有一種格式用以處理請(qǐng)求和響應(yīng)數(shù)據(jù)的編碼和解碼,并且支持多種版本的協(xié)議。而這最終導(dǎo)致了丑陋的代碼,有如:
if (version==3) {
...
}else if (version>4) {
if (version==5) {
...
}
...
}
通信協(xié)議因此變得越來(lái)越復(fù)雜,因?yàn)殚_發(fā)者必須確保,發(fā)出請(qǐng)求的人和接受請(qǐng)求的人必須同時(shí)兼容,并且在一方開始使用新協(xié)議時(shí),另外一方也要可以接受。
ProtocolBuffer設(shè)計(jì)用于解決這一類問(wèn)題:
- 很方便引入新字段,而中間服務(wù)器可以忽略這些字段,直接傳遞過(guò)去而無(wú)需理解所有的字段。
- 格式可以自描述,并且可以在多種語(yǔ)言中使用(C++、Java等)
然而用戶仍然需要手寫解析代碼。
隨著系統(tǒng)的演化,他需要一些其他的功能:
- 自動(dòng)生成編碼和解碼代碼,而無(wú)需自己編寫解析器。
- 除了用于簡(jiǎn)短的RPC(Remote Procedure Call)請(qǐng)求,人們使用ProtocolBuffer來(lái)做數(shù)據(jù)存儲(chǔ)格式(例如BitTable)。
- RPC服務(wù)器接口可以作為 .proto 文件來(lái)描述,而通過(guò)ProtocolBuffer的編譯器生成存根(stub)類供用戶實(shí)現(xiàn)服務(wù)器接口。
ProtocolBuffer現(xiàn)在已經(jīng)是Google的混合語(yǔ)言數(shù)據(jù)標(biāo)準(zhǔn)了,現(xiàn)在已經(jīng)正在使用的有超過(guò)48,162種報(bào)文格式定義和超過(guò)12,183個(gè) .proto 文件。他們用于RPC系統(tǒng)和持續(xù)數(shù)據(jù)存儲(chǔ)系統(tǒng)。
2 語(yǔ)言指導(dǎo)
本指導(dǎo)描述了如何使用ProtocolBuffer語(yǔ)言來(lái)定義結(jié)構(gòu)化數(shù)據(jù)類型,包括 .proto 文件的語(yǔ)法和如何生成存取類。
這是一份指導(dǎo)手冊(cè),一步步的例子使用文檔中的多種功能,查看入門指導(dǎo)( http://code.google.com/apis/protocolbuffers/docs/tutorials.html )選擇你的語(yǔ)言。
2.1 定義一個(gè)消息類型
@waiting …
2.2 值類型
@waiting …
2.3 可選字段與缺省值
@waiting …
2.4 枚舉
@waiting …
2.5 使用其他消息類型
@waiting …
2.6 嵌套類型
@waiting …
2.7 更新一個(gè)數(shù)據(jù)類型
@waiting …
2.8 擴(kuò)展
@waiting …
2.9 包
@waiting …
2.10 定義服務(wù)
@waiting …
2.11 選項(xiàng)
@waiting …
2.12 生成你的類
@waiting …
3 代碼風(fēng)格指導(dǎo)
本文檔提供了 .proto 文件的代碼風(fēng)格指導(dǎo)。按照慣例,你將會(huì),你將會(huì)生成一些便于閱讀和一致的ProtocolBuffer定義文件。
3.1 消息與字段名
使用駱駝風(fēng)格的大小寫命名,即單詞首字母大寫,來(lái)做消息名。使用GNU的全部小寫,使用下劃線分隔的方式定義字段名:
message SongServerRequest {
required string song_name=1;
}
使用這種命名方式得到的名字如下:
C++:
const string& song_name() {...}
void set_song_name(const string& x) {...}
Java:
public String getSongName() {...}
public Builder setSongName(String v) {...}
3.2 枚舉
使用駱駝風(fēng)格做枚舉名,而用全部大寫做值的名字:
enum Foo {
FIRST_VALUE=1;
SECOND_VALUE=2;
}
每個(gè)枚舉值最后以分號(hào)結(jié)尾,而不是逗號(hào)。
3.3 服務(wù)
如果你的 .proto 文件定義了RPC服務(wù),你可以使用駱駝風(fēng)格:
service FooService {
rpc GetSomething(FooRequest) returns (FooResponse);
}
4 編碼
本文檔描述了ProtocolBuffer的串行化二進(jìn)制數(shù)據(jù)格式定義。你如果僅僅是在應(yīng)用中使用ProtocolBuffer,并不需要知道這些,但是這些會(huì)對(duì)你定義高效的格式有所幫助。
4.1 一個(gè)簡(jiǎn)單的消息
@waiting …
4.2 基于128的Varints
@waiting …
4.3 消息結(jié)構(gòu)
@waiting …
4.4 更多的值類型
@waiting …
4.5 內(nèi)嵌消息
@waiting …
4.6 可選的和重復(fù)的元素
@waiting …
4.7 字段順序
@waiting …
5 ProtocolBuffer基礎(chǔ):C++
@waiting …
6 ProtocolBuffer基礎(chǔ):Java
@waiting …
7 ProtocolBuffer基礎(chǔ):Python
本指南給Python程序員一個(gè)快速使用的ProtocolBuffer的指導(dǎo)。通過(guò)一些簡(jiǎn)單的例子來(lái)在應(yīng)用中使用ProtocolBuffer,它向你展示了如何:
- 定義 .proto 消息格式文件
- 使用ProtocolBuffer編譯器
- 使用Python的ProtocolBuffer編程接口來(lái)讀寫消息
這并不是一個(gè)在Python中使用ProtocolBuffer的完整指導(dǎo)。更多細(xì)節(jié)請(qǐng)參考手冊(cè)信息,查看語(yǔ)言指導(dǎo)( http://code.google.com/apis/protocolbuffers/docs/proto.html ),Python API( http://code.google.com/apis/protocolbuffers/docs/reference/python/index.html ),和編碼手冊(cè)( http://code.google.com/apis/protocolbuffers/docs/encoding.html )。
7.1 為什么使用ProtocolBuffer?
下面的例子”地址本”應(yīng)用用于讀寫人的聯(lián)系信息。每個(gè)人有name、ID、email,和聯(lián)系人電話號(hào)碼。
如何串行化和讀取結(jié)構(gòu)化數(shù)據(jù)呢?有如下幾種問(wèn)題:
- 使用Python的pickle,這是語(yǔ)言內(nèi)置的缺省方法,不過(guò)沒(méi)法演化,也無(wú)法讓其他語(yǔ)言支持。
- 你可以發(fā)明一種數(shù)據(jù)編碼方法,例如4個(gè)整數(shù)”12:3-23:67″,這是簡(jiǎn)單而靈活的方法,不過(guò)你需要自己寫解析器代碼,且只適用于簡(jiǎn)單的數(shù)據(jù)。
- 串行化數(shù)據(jù)到XML。這種方法因?yàn)榭勺x性和多種語(yǔ)言的兼容函數(shù)庫(kù)而顯得比較吸引人,不過(guò)這也不是最好的方法,因?yàn)閄ML浪費(fèi)空間是臭名昭著的,編碼解碼也很浪費(fèi)時(shí)間。而XML DOM樹也是很復(fù)雜的。
ProtocolBuffer提供了靈活、高效、自動(dòng)化的方法來(lái)解決這些問(wèn)題。通過(guò)ProtocolBuffer,只需要寫一個(gè) .proto 數(shù)據(jù)結(jié)構(gòu)描述文件,就可以編譯到幾種語(yǔ)言的自動(dòng)編碼解碼類。生成的類提供了setter和getter方法來(lái)控制讀寫細(xì)節(jié)。最重要的是 ProtocolBuffer支持后期擴(kuò)展協(xié)議,而又確保舊格式可以兼容。
7.2 哪里可以找到例子代碼
源碼發(fā)行包中已經(jīng)包含了,在”example”文件夾。
7.3 定義你的協(xié)議格式
想要?jiǎng)?chuàng)建你的地址本應(yīng)用,需要開始于一個(gè) .proto 文件。定義一個(gè) .proto 文件很簡(jiǎn)單:添加一個(gè)消息到數(shù)據(jù)結(jié)構(gòu),然后指定一個(gè)和一個(gè)類型到每一個(gè)字段,如下是本次例子使用的 addressbook.proto
package tutorial;
message Person {
required string name=1;
required int32 id=2;
optional string email=3;
enum PhoneType {
MOBILE=0;
HOME=1;
WORK=2;
}
message PhoneNumber {
required string number=1;
optional PhoneType type=2 [default=HOME];
}
repeated PhoneNumber phone=4;
}
message AddressBook {
repeated Person person=1;
}
有如你所見(jiàn)的,語(yǔ)法類似于C++或Java。讓我們分塊理解他們。
@waiting …
7.4 編譯你的ProtocolBuffer
現(xiàn)在已經(jīng)擁有了 .proto 文件,下一步就是編譯生成相關(guān)的訪問(wèn)類。運(yùn)行編譯器 protoc 編譯你的 .proto 文件。
- 如果還沒(méi)安裝編譯器則下載并按照README的安裝。
- 運(yùn)行編譯器,指定源目錄和目標(biāo)目錄,定位你的 .proto 文件到源目錄,然后執(zhí)行:
protoc -I=$SRC_DIR --python_out=$DST_DIR addressbook.proto
因?yàn)樾枰褂肞ython類,所以 –python_out 選項(xiàng)指定了特定的輸出語(yǔ)言。
這個(gè)步驟會(huì)生成 addressbook_pb2.py 到目標(biāo)目錄。
7.5 ProtocolBuffer API
不像生成的C++和Java代碼,Python生成的類并不會(huì)直接為你生成存取數(shù)據(jù)的代碼。而是(有如你在 addressbook_pb2.py 中見(jiàn)到的)生成消息描述、枚舉、和字段,還有一些神秘的空類,每個(gè)對(duì)應(yīng)一個(gè)消息類型:
class Person(message.Message):
__metaclass__=reflection.GeneratedProtocolMessageType
class PhoneNumber(message.Message):
__metaclass__=reflection.GeneratedProtocolMessageType
DESCRIPTION=_PERSON_PHONENUMBER
DESCRIPTOR=_PERSON
class AddressBook(message.Message):
__metaclass__=reflection.GeneratedProtocolMessageType
DESCRIPTOR=_ADDRESSBOOK
這里每個(gè)類最重要的一行是 __metaclass__=reflection.GeneratedProtocolMessageType 。通過(guò)Python的元類機(jī)制工作,你可以把他們看做是生成類的模板。在載入時(shí), GeneratedProtocolMessageType 元類使用特定的描述符創(chuàng)建Python方法。隨后你就可以使用完整的功能了。
最后就是你可以使用 Person 類來(lái)操作相關(guān)字段了。例如你可以寫:
import addressbook_pb2
person=addressbook_pb2.Person()
person.id=1234
person.name="John Doe"
person.email="jdoe@example.com"
phone=person.phone.add()
phone.number="555-4321"
phone.type=addressbook_pb2.Person.HOME
需要注意的是這些賦值屬性并不是簡(jiǎn)單的增加新字段到Python對(duì)象,如果你嘗試給一個(gè) .proto 文件中沒(méi)有定義的字段賦值,就會(huì)拋出 AttributeError 異常,如果賦值類型錯(cuò)誤會(huì)拋出 TypeError 。在給一個(gè)字段賦值之前讀取會(huì)返回缺省值:
person.no_such_field=1 #raise AttributeError
person.id="1234" #raise TypeError
更多相關(guān)信息參考( http://code.google.com/apis/protocolbuffers/docs/reference/python-generated.html )。
7.5.1 枚舉
枚舉在元類中定義為一些符號(hào)常量對(duì)應(yīng)的數(shù)字。例如常量 addressbook_pb2.Person.WORK 擁有值2。
7.5.2 標(biāo)準(zhǔn)消息方法
每個(gè)消息類包含一些其他方法允許你檢查和控制整個(gè)消息,包括:
- IsInitialized() :檢查是否所有必須(required)字段都已經(jīng)被賦值了。
- __str__() :返回人類可讀的消息表示,便于調(diào)試。
- CopyFrom(other_msg) :使用另外一個(gè)消息的值來(lái)覆蓋本消息。
- Clear() :清除所有元素的值,回到初識(shí)狀態(tài)。
這些方法是通過(guò)接口 Message 實(shí)現(xiàn)的,更多消息參考( http://code.google.com/apis/protocolbuffers/docs/reference/python/google.protobuf.message.Message-class.html )。
7.5.3 解析與串行化
最后,每個(gè)ProtocolBuffer類有些方法用于讀寫消息的二進(jìn)制數(shù)據(jù)( http://code.google.com/apis/protocolbuffers/docs/encoding.html )。包括:
- SerializeToString() :串行化,并返回字符串。注意是二進(jìn)制格式而非文本。
- ParseFromString(data) :解析數(shù)據(jù)。
他們是成對(duì)使用的,提供二進(jìn)制數(shù)據(jù)的串行化和解析。另外參考消息API參考( http://code.google.com/apis/protocolbuffers/docs/reference/python/google.protobuf.message.Message-class.html )了解更多信息。
Note
ProtocolBuffer與面向?qū)ο笤O(shè)計(jì)
ProtocolBuffer類只是用于存取數(shù)據(jù)的,類似于C++中的結(jié)構(gòu)體,他們并沒(méi)有在面向?qū)ο蠓矫孀龊芎玫脑O(shè)計(jì)。如果你想要給這些類添加更多 的行為,最好的方法是包裝(wrap)。包裝同樣適合于復(fù)用別人寫好的 .proto 文件。這種情況下,你可以把ProtocolBuffer生成類包裝的很適合于你的應(yīng)用,并隱藏一些數(shù)據(jù)和方法,暴露有用的函數(shù)等等。 你不可以通過(guò)繼承來(lái)給自動(dòng)生成的類添加行為。 這會(huì)破壞他們的內(nèi)部工作機(jī)制。
7.6 寫消息
現(xiàn)在開始嘗試使用ProtocolBuffer的類。第一件事是讓地址本應(yīng)用可以記錄聯(lián)系人的細(xì)節(jié)信息。想要做這些需要先創(chuàng)建聯(lián)系人實(shí)例,然后寫入到輸出流。
這里的程序從文件讀取地址本,添加新的聯(lián)系人信息,然后寫回新的地址本到文件。
#! /usr/bin/python
import addressbook_pb2
import sys
#這個(gè)函數(shù)使用用戶輸入填充聯(lián)系人信息
def PromptForAddress(person):
person.id=int(raw_input("Enter person ID number: "))
person.name=raw_input("Enter name: ")
email=raw_input("Enter email address (blank for none): ")
if email!="":
person.email=email
while True:
number=raw_input("Enter a phone number (or leave blank to finish): ")
if number=="":
break
phone_number=person.phone.add()
phone_number.number=number
type=raw_input("Is this a mobile, home, or work phone? ")
if type=="mobile":
phone_number.type=addressbook_pb2.Person.MOBILE
elif type=="home":
phone_number.type=addressbook_pb2.Person.HOME
elif type=="work":
phone_number.type=addressbook_pb2.Person.WORK
else:
print "Unknown phone type; leaving as default value."
#主函數(shù),從文件讀取地址本,添加新的聯(lián)系人,然后寫回到文件
if len(sys.argv)!=2:
print "Usage:",sys.argv[0],"ADDRESS_BOOK_FILE"
sys.exit(-1)
address_book=addressbook_pb2.AddressBook()
#讀取已經(jīng)存在的地址本
try:
f=open(sys.argv[1],"fb")
address_book.ParseFromString(f.read())
f.close()
except OSError:
print sys.argv[1]+": Count open file. Creating a new one."
#添加地址
PromptFromAddress(address_book.person.add())
#寫入到文件
f=open(sys.argv[1],"wb")
f.write(address_book.SerializeToString())
f.close()
7.7 讀消息
當(dāng)然,一個(gè)無(wú)法讀取的地址本是沒(méi)什么用處的,這個(gè)例子讀取剛才創(chuàng)建的文件并打印所有信息:
#! /usr/bin/python
import addressbook_pb2
import sys
#遍歷地址本中所有的人并打印出來(lái)
def ListPeople(address_book):
for person in address_book.person:
print "Person ID:",person.id
print " Name:",person.name
if person.HasField("email"):
print " E-mail:",person.email
for phone_number in person.phone:
if phone_number.type==addressbook_pb2.Person.MOBILE:
print " Mobile phone #:",
elif phone_number.type==addressbook_pb2.Person.HOME:
print " Home phone #:",
elif phone_number.type==addressbook_pb2.Person.WORK:
print " Work phone #:",
print phone_number.number
#主函數(shù),從文件讀取地址本
if len(sys.argv)!=2:
print "Usage:",sys.argv[0],"ADDRESS_BOOK_FILE"
sys.exit(-1)
address_book=addressbook_pb2.AddressBook()
#讀取整個(gè)地址本文件
f=open(sys.argv[1],"rb")
address_book.ParseFromString(f.read())
f.close()
ListPeople(address_book)
7.8 擴(kuò)展ProtocolBuffer
在你發(fā)不了代碼以后,可能會(huì)想要改進(jìn)ProtocolBuffer的定義。如果你想新的數(shù)據(jù)結(jié)構(gòu)向后兼容,而你的舊數(shù)據(jù)可以向前兼容,那么你就找對(duì)了東西了,不過(guò)有些規(guī)則需要遵守。在新版本的ProtocolBuffer中:
- 必須不可以改變已經(jīng)存在的標(biāo)簽的數(shù)字。
- 必須不可以增加或刪除必須(required)字段。
- 可以刪除可選(optional)或重復(fù)(repeated)字段。
- 可以添加新的可選或重復(fù)字段,但是必須使用新的標(biāo)簽數(shù)字,必須是之前的字段所沒(méi)有用過(guò)的。
這些規(guī)則也有例外( http://code.google.com/apis/protocolbuffers/docs/proto.html#updating ),不過(guò)很少使用。
如果你遵從這些規(guī)則,舊代碼會(huì)很容易的讀取新的消息,并簡(jiǎn)單的忽略新的字段。而對(duì)舊的被刪除的可選字段也會(huì)簡(jiǎn)單的使用他們的缺省值,被刪除的重復(fù)字 段會(huì)自動(dòng)為空。新的代碼也會(huì)透明的讀取舊的消息。然而,需要注意的是新的可選消息不會(huì)在舊的消息中顯示,所以你需要使用 has_ 嚴(yán)格的檢查他們是否存在,或者在 .proto 文件中提供一個(gè)缺省值。如果沒(méi)有缺省值,就會(huì)有一個(gè)類型相關(guān)的默認(rèn)缺省值:對(duì)于字符串就是空字符串;對(duì)于布爾型則是false;對(duì)于數(shù)字類型默認(rèn)為0。同 時(shí)要注意的是如果你添加了新的重復(fù)字段,你的新代碼不會(huì)告訴你這個(gè)字段為空(新代碼)也不會(huì),也不會(huì)(舊代碼)包含 has_ 標(biāo)志。
7.9 高級(jí)使用
ProtocolBuffer不僅僅提供了數(shù)據(jù)結(jié)構(gòu)的存取和串行化。查看Python API參考( http://code.google.com/apis/protocolbuffers/docs/reference/python/index.html )了解更多功能。
一個(gè)核心功能是通過(guò)消息類的映射(reflection)提供的。你可以通過(guò)它遍歷消息的所有字段,和管理他們的值。關(guān)于映射的一個(gè)很有用的地方是 轉(zhuǎn)換到其他編碼,如XML或JSON。一個(gè)使用映射的更高級(jí)的功能是尋找同類型兩個(gè)消息的差異,或者開發(fā)出排序、正則表達(dá)式等功能。使用你的創(chuàng)造力,還可 以用ProtocolBuffer實(shí)現(xiàn)比你以前想象的更多的問(wèn)題。
映射是通過(guò)消息接口提供的。
8 參考概覽
@waiting …
9 C++代碼生成
@waiting …
10 C++ API
@waiting …
11 Java代碼生成
@waiting …
12 Java API
@waiting …
13 Python代碼生成
本頁(yè)提供了Python生成類的相關(guān)細(xì)節(jié)。你可以在閱讀本文檔之前查看語(yǔ)言指導(dǎo)。
Python的ProtocolBuffer實(shí)現(xiàn)與C++和Java的略有不同,編譯器只輸出構(gòu)建代碼的描述符來(lái)生成類,而由Python的元類來(lái)執(zhí)行工作。本文檔描述了元類開始生效以后的東西。
13.1 編譯器的使用
ProtocolBuffer通過(guò)編譯器的 –python_out= 選項(xiàng)來(lái)生成Python的相關(guān)類。這個(gè)參數(shù)實(shí)際上是指定輸出的Python類放在哪個(gè)目錄下。編譯器會(huì)為每個(gè) .proto 文件生成一個(gè)對(duì)應(yīng)的 .py 文件。輸出文件名與輸入文件名相關(guān),不過(guò)有兩處修改:
- 擴(kuò)展名 .proto 改為 .py 。
- 路徑名的修改。
如果你按照如下調(diào)用編譯器:
protoc --proto_path=src --python_out=build/gen src/foo.proto src/bar/baz.proto
編譯器會(huì)自動(dòng)讀取兩個(gè) .proto 文件然后產(chǎn)生兩個(gè)輸出文件。在需要時(shí)編譯器會(huì)自動(dòng)創(chuàng)建目錄,不過(guò) –python_out 指定的目錄不會(huì)自動(dòng)創(chuàng)建。
需要注意的是,如果 .proto 文件名或路徑包含有無(wú)法在Python中使用的模塊名(如連字符),就會(huì)被自動(dòng)轉(zhuǎn)換為下劃線。所以文件 foo-bar.proto 會(huì)變成 foo_bar_pb2.py 。
Note
在每個(gè)文件后綴的 _pb2.py 中的2代表ProtocolBuffer版本2。版本1僅在Google內(nèi)部使用,但是你仍然可以在以前發(fā)布的一些代碼中找到它。自動(dòng)版本2開 始,ProtocolBuffer開始使用完全不同的接口了,從此Python也沒(méi)有編譯時(shí)類型檢查了,我們加上這個(gè)版本號(hào)來(lái)標(biāo)志Python文件名。
13.2 包
Python代碼生成根本不在乎包的名字。因?yàn)镻ython使用目錄名來(lái)做包名。
13.3 消息
先看看一個(gè)簡(jiǎn)單的消息聲明:
message Foo {}
ProtocolBuffer編譯器會(huì)生成類Foo,它是 google.protobuf.Message 的子類。這個(gè)實(shí)體類,不含有虛擬方法。不像C++和Java,Python生成類對(duì)優(yōu)化選項(xiàng)不感冒;實(shí)際上Python的生成代碼已經(jīng)為代碼大小做了優(yōu)化。
你不能繼承Foo的子類。生成類被設(shè)計(jì)不可以被繼承,否則會(huì)被打破一些設(shè)計(jì)。另外,繼承本類也是不好的設(shè)計(jì)。
Python的消息類沒(méi)有特定的公共成員,而是定義接口,極其嵌套的字段、消息和枚舉類型。
一個(gè)消息可以在另外一個(gè)消息中聲明,例如 message Foo { message Bar {}} 。在這種情況下,Bar類定義為Foo的一個(gè)靜態(tài)成員,所以你可以通過(guò) Foo.Bar 來(lái)引用。
13.4 字段
對(duì)于消息類型中的每一個(gè)字段,都有對(duì)應(yīng)的同名成員。
13.4.1 簡(jiǎn)單字段
如果你有一個(gè)簡(jiǎn)單字段(包括可選的和重復(fù)的),也就是非消息字段,你可以通過(guò)簡(jiǎn)單字段的方式來(lái)管理,例如foo字段的類型是int32,你可以:
message.foo=123
print message.foo
注意設(shè)置foo的值,如果類型錯(cuò)誤會(huì)拋出TypeError。
如果foo在賦值之前就讀取,就會(huì)使用缺省值。想要檢查是否已經(jīng)賦值,可以用 HasField() ,而清除該字段的值用 ClearField() 。例如:
assert not message.HasField("foo")
message.foo=123
assert message.HasField("foo")
message.ClearField("foo")
assert not message.HasField("foo")
13.4.2 簡(jiǎn)單消息字段
消息類型工作方式略有不同。你無(wú)法為一個(gè)嵌入消息字段賦值。而是直接操作這個(gè)消息的成員。因?yàn)閷?shí)例化上層消息時(shí),其包含的子消息同時(shí)也實(shí)例化了,例如定義:
message Foo {
optional Bar bar=1;
}
message bar {
optional int32 i=1;
}
你不可以這么做,因?yàn)椴荒茏鱿㈩愋妥侄蔚馁x值:
foo=Foo()
foo.bar=Bar() #WRONG!
而是可以直接對(duì)消息類型字段的成員賦值:
foo=Foo()
assert not foo.HasField("bar")
foo.bar.i=1
assert foo.HasField("bar")
注意簡(jiǎn)單的讀取消息類型字段的未賦值成員只不過(guò)是打印其缺省值:
foo=Foo()
assert not foo.HasField("bar")
print foo.bar.i #打印i的缺省值
assert not foo.HasField("bar")
13.4.3 重復(fù)字段
重復(fù)字段表現(xiàn)的像是Python的序列類型。如果是嵌入的消息,你無(wú)法為字段直接賦值,但是你可以管理。例如給定的定義:
message Foo {
repeated int32 nums=1;
}
你就可以這么做:
foo=Foo()
foo.nums.append(15)
foo.nums.append(32)
assert len(foo.nums)==2
assert foo.nums[0]==15
assert foo.nums[1]==32
for i in foo.nums:
print i
foo.nums[1]=56
assert foo.nums[1]==56
作為一種簡(jiǎn)單字段,清除該字段必須使用 ClearField() 。
13.4.4 重復(fù)消息字段
重復(fù)消息字段工作方式與重復(fù)字段很像,除了 add() 方法用于返回新的對(duì)象以外。例如如下定義:
message Foo {
repeated Bar bar=1;
}
message Bar {
optional int32 i=1;
}
你可以這么做:
foo=Foo()
bar=foo.bars.add()
bar.i=15
bar=foo.bars.add()
bar.i=32
assert len(foo.bars)==2
assert foo.bars[0].i==15
assert foo.bars[1].i==32
for bar in foo.bars:
print bar.i
foo.bars[1].i=56
assert foo.bars[1].i==56
13.4.5 枚舉類型
@waiting …
13.4.6 擴(kuò)展
@waiting …
13.5 服務(wù)
13.5.1 接口
一個(gè)簡(jiǎn)單的接口定義:
service Foo {
rpc Bar(FooRequest) returns(FooResponse);
}
ProtocolBuffer的編譯器會(huì)生成類 Foo 來(lái)展示這個(gè)服務(wù)。 Foo 將會(huì)擁有每個(gè)服務(wù)定義的方法。在這種情況下 Bar 方法的定義是:
def Bar(self,rpc_controller,request,done)
參數(shù)等效于 Service.CallMethod() ,除了隱含的 method_descriptor 參數(shù)。
這些生成的方法被定義為可以被子類重載。缺省實(shí)現(xiàn)只是簡(jiǎn)單的調(diào)用 controller.SetFailed() 而拋出錯(cuò)誤信息告之尚未實(shí)現(xiàn)。然后調(diào)用done回調(diào)。在實(shí)現(xiàn)你自己的服務(wù)時(shí),你必須繼承生成類,然后重載各個(gè)接口方法。
Foo繼承了 Service 接口。ProtocolBuffer編譯器會(huì)自動(dòng)聲響相關(guān)的實(shí)現(xiàn)方法:
- GetDescriptor :返回服務(wù)的 ServiceDescriptor 。
- CallMethod :檢測(cè)需要調(diào)用哪個(gè)方法,并且直接調(diào)用。
- GetRequestClass 和 GetResponseClass :返回指定方法的請(qǐng)求和響應(yīng)類。
13.5.2 存根(Stub)
ProtocolBuffer編譯器也會(huì)為每個(gè)服務(wù)接口提供一個(gè)存根實(shí)現(xiàn),用于客戶端發(fā)送請(qǐng)求到服務(wù)器。對(duì)于Foo服務(wù),存根實(shí)現(xiàn)是 Foo_Stub 。
Foo_Stub 是Foo的子類,他的構(gòu)造器是一個(gè) RpcChannel 。存根會(huì)實(shí)現(xiàn)調(diào)用每個(gè)服務(wù)方法的 CallMethod() 。
ProtocolBuffer哭并不包含RPC實(shí)現(xiàn)。然而,它包含了你構(gòu)造服務(wù)類的所有工具,不過(guò)選擇RPC實(shí)現(xiàn)則隨你喜歡。你只需要提供 RpcChannel 和 RpcController 的實(shí)現(xiàn)即可。
14 Python API
@waiting …
15 其他語(yǔ)言
http://www.shnenglu.com/liquidx