import capnp addressbook = capnp.load('addressbook.capnp') message = capnp.MallocMessageBuilder() addressBook = message.initRoot(addressbook.AddressBook) people = addressBook.initPeople(2) alice = people[0] alice.id = 123 alice.name = 'Alice' alice.email = 'al...@example.com' alicePhones = alice.initPhones(1) alicePhones[0].number = "555-1212" alicePhones[0].type = 'mobile' alice.employment.school = "MIT" bob = people[1] bob.id = 456 bob.name = 'Bob' bob.email = 'b...@example.com' bobPhones = bob.initPhones(2) bobPhones[0].number = "555-4567" bobPhones[0].type = 'home' bobPhones[1].number = "555-7654" bobPhones[1].type = addressbook.Person.PhoneNumber.Type.WORK bob.employment.unemployed = None
--
You received this message because you are subscribed to the Google Groups "Cap'n Proto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to capnproto+...@googlegroups.com.
Visit this group at http://groups.google.com/group/capnproto.
Timer unit: 1e-06 s
File: example.py
Function: writeAddressBook at line 8
Total time: 0.000119 s
Line # Hits Time Per Hit % Time Line Contents
==============================================================
8 @profile
9 def writeAddressBook(fd):
10 1 7 7.0 5.9 message = capnp.MallocMessageBuilder()
11 1 10 10.0 8.4 addressBook = message.initRoot(addressbook.AddressBook)
12 1 21 21.0 17.6 people = addressBook.init('people', 2)
13
14 1 1 1.0 0.8 alice = people[0]
15 1 6 6.0 5.0 alice.id = 123
16 1 3 3.0 2.5 alice.name = 'Alice'
17 1 2 2.0 1.7 alice.email = 'al...@example.com'
18 1 6 6.0 5.0 alicePhones = alice.init('phones', 1)
19 1 2 2.0 1.7 alicePhones[0].number = "555-1212"
20 1 2 2.0 1.7 alicePhones[0].type = 'mobile'
21 1 11 11.0 9.2 alice.employment.school = "MIT"
22
23 1 1 1.0 0.8 bob = people[1]
24 1 3 3.0 2.5 bob.id = 456
25 1 1 1.0 0.8 bob.name = 'Bob'
26 1 3 3.0 2.5 bob.email = 'b...@example.com'
27 1 6 6.0 5.0 bobPhones = bob.init('phones', 2)
28 1 1 1.0 0.8 bobPhones[0].number = "555-4567"
29 1 2 2.0 1.7 bobPhones[0].type = 'home'
30 1 1 1.0 0.8 bobPhones[1].number = "555-7654"
31 1 3 3.0 2.5 bobPhones[1].type = addressbook.Person.PhoneNumber.Type.WORK
32 1 6 6.0 5.0 bob.employment.unemployed = None # This is definitely bad, syntax will change at some point
33
34 1 21 21.0 17.6 capnp.writePackedMessageToFd(fd, message)
File: example.py
Function: printAddressBook at line 37
Total time: 0.000125 s
Line # Hits Time Per Hit % Time Line Contents
==============================================================
37 @profile
38 def printAddressBook(fd):
39 1 15 15.0 12.0 message = capnp.PackedFdMessageReader(f.fileno())
40 1 4 4.0 3.2 addressBook = message.getRoot(addressbook.AddressBook)
41
42 3 20 6.7 16.0 for person in addressBook.people:
43 2 24 12.0 19.2 print(person.name, ':', person.email)
44 5 30 6.0 24.0 for phone in person.phones:
45 3 12 4.0 9.6 print(phone.type, ':', phone.number)
46
47 2 6 3.0 4.8 which = person.employment.which()
48 2 2 1.0 1.6 print(which)
49
50 2 2 1.0 1.6 if which == addressbook.Person.Employment.Which.UNEMPLOYED:
51 1 1 1.0 0.8 print('unemployed')
52 1 1 1.0 0.8 elif which == addressbook.Person.Employment.Which.EMPLOYER:
53 print('employer:', person.employment.employer)
54 1 1 1.0 0.8 elif which == addressbook.Person.Employment.Which.SCHOOL:
55 1 5 5.0 4.0 print('student at:', person.employment.school)
56 elif which == addressbook.Person.Employment.Which.SELF_EMPLOYED:
57 print('self employed')
58 2 2 1.0 1.6 print()
Timer unit: 1e-06 s
File: example.py
Function: writeAddressBook at line 6
Total time: 0.000158 s
Line # Hits Time Per Hit % Time Line Contents
==============================================================
6 @profile
7 def writeAddressBook(fd):
8 1 14 14.0 8.9 message = capnp.MallocMessageBuilder()
9 1 23 23.0 14.6 addressBook = message.initRootAddressBook()
10 1 11 11.0 7.0 people = addressBook.initPeople(2)
11
12 1 10 10.0 6.3 alice = people[0]
13 1 5 5.0 3.2 alice.id = 123
14 1 6 6.0 3.8 alice.name = 'Alice'
15 1 2 2.0 1.3 alice.email = 'al...@example.com'
16 1 4 4.0 2.5 alicePhones = alice.initPhones(1)
17 1 12 12.0 7.6 alicePhones[0].number = "555-1212"
18 1 6 6.0 3.8 alicePhones[0].type = addressbook.Person.PhoneNumber.Type.MOBILE
19 1 5 5.0 3.2 alice.employment.school = "MIT"
20
21 1 3 3.0 1.9 bob = people[1]
22 1 2 2.0 1.3 bob.id = 456
23 1 2 2.0 1.3 bob.name = 'Bob'
24 1 2 2.0 1.3 bob.email = 'b...@example.com'
25 1 2 2.0 1.3 bobPhones = bob.initPhones(2)
26 1 4 4.0 2.5 bobPhones[0].number = "555-4567"
27 1 4 4.0 2.5 bobPhones[0].type = addressbook.Person.PhoneNumber.Type.HOME
28 1 4 4.0 2.5 bobPhones[1].number = "555-7654"
29 1 4 4.0 2.5 bobPhones[1].type = addressbook.Person.PhoneNumber.Type.WORK
30 1 4 4.0 2.5 bob.employment.unemployed = capnp.Void.VOID # This is definitely bad, syntax will change at some point
31
32 1 29 29.0 18.4 capnp.writePackedMessageToFd(fd, message)
File: example.py
Function: printAddressBook at line 35
Total time: 0.000249 s
Line # Hits Time Per Hit % Time Line Contents
==============================================================
35 @profile
36 def printAddressBook(fd):
37 1 19 19.0 7.6 message = capnp.PackedFdMessageReader(f.fileno())
38 1 10 10.0 4.0 addressBook = message.getRootAddressBook()
39
40 3 39 13.0 15.7 for person in addressBook.people:
41 2 30 15.0 12.0 print(person.name, ':', person.email)
42 5 100 20.0 40.2 for phone in person.phones:
43 3 20 6.7 8.0 print(phone.type, ':', phone.number)
44
45 2 13 6.5 5.2 which = person.employment.which()
46 2 4 2.0 1.6 print(which)
47
48 2 3 1.5 1.2 if which == addressbook.Person.Employment.Which.UNEMPLOYED:
49 1 1 1.0 0.4 print('unemployed')
50 1 1 1.0 0.4 elif which == addressbook.Person.Employment.Which.EMPLOYER:
51 print('employer:', person.employment.employer)
52 1 1 1.0 0.4 elif which == addressbook.Person.Employment.Which.SCHOOL:
53 1 6 6.0 2.4 print('student at:', person.employment.school)
54 elif which == addressbook.Person.Employment.Which.SELF_EMPLOYED:
55 print('self employed')
56 2 2 1.0 0.8 print()
$time python addressbook.proto.py
python addressbook.proto.py 3.70s user 0.39s system 99% cpu 4.101 total
$export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp; time python addressbook.proto.py
python addressbook.proto.py 1.63s user 0.40s system 99% cpu 2.031 total
$time python addressbook.capnp.py
python addressbook.capnp.py 0.72s user 0.34s system 99% cpu 1.064 total
33 10000 128148 12.8 22.8 capnp.writePackedMessageToFd(fd, message)
vs
29 10000 148150 14.8 9.5 message_string = addressBook.SerializeToString()
30 10000 41984 4.2 2.7 fd.write(message_string) 13 10000 70373 7.0 12.5 people = addressBook.init('people', 2)
vs
11 10000 233784 23.4 14.9 alice = addressBook.person.add()
...
19 10000 121705 12.2 7.8 bob = addressBook.person.add()I've upgraded the library to work with v0.2 of the capnproto C++ library. Also I've uploaded the library to pypi (https://pypi.python.org/pypi/capnp/), so the installation process is now way easier. First make sure you have v0.2rc of the capnproto C++ library installed from http://capnproto.org/capnproto-c++-0.2.0-rc2.tar.gz. Then just run `pip install capnp`, and you should be good to go. This of course assumes you have pip already installed (http://www.pip-installer.org/en/latest/installing.html), and have a relatively new version of setuptools (run `pip install -U setuptools` if it gives you trouble).Also, 1 breaking change. I've simplified the codebase quite a lot by removing enum namespaces. What this means is things like `bobPhones[1].type = addressbook.Person.PhoneNumber.Type.WORK` are now gone, and instead you would have to use bobPhones[1].type = 'work'. The former method was a holdover of the C++ way of doing it, but I felt it was overly complex to support, as well as being un-pythonic and actually a lot slower than just using string literals (all those dots in addressbook.Person.PhoneNumber.Type.WORK work out to being dictionary lookups).
--
I wonder if it's worth making bobPhones[1].type be a string subclassthat enforces singleton-ness. Otherwise "bobPhones[0].type is
bobPhones[1].type" will be unreliable.
I wonder if it's worth making bobPhones[1].type be a string subclass
that enforces singleton-ness. Otherwise "bobPhones[0].type is
bobPhones[1].type" will be unreliable.
--Andy
I suppose I'm thinking that type.VALUE is type.VALUE and I'm expecting enums to work like that even if they're strings. This may be silly.
--Andy
I suppose I'm thinking that type.VALUE is type.VALUE and I'm expecting enums to work like that even if they're strings. This may be silly.
Overall, very nice work! I can't wait to make this part of the 0.3.0 release announcement.Some nitpicks:- The performance numbers seem odd to me. You say 4x faster than pure-python protobuf, 2x faster than c-extension protobuf. But, I'm pretty sure the c-extension protobuf is much more than 2x faster than the pure-python protobuf in most cases. The other problem with numbers like this is that performance numbers vary wildly depending on use case. So, I'd suggest not mentioning specific numbers unless you want to refer to benchmarks covering a wide variety of cases.
- On a related note, you say "The INFINITY TIMES faster part isn’t so true for python", but it should be just as true for Python as it is for C++. (It's an obviously questionable claim in both cases. :) )
- When I do "capnp.load('foo.capnp')", where does it look for the file? I think we want to search PYTHONPATH. In fact, I wonder if it would be possible to hook into the module loading mechanism such that people can actually use a regular python import statement to load a capnp schema...
- You write the title "Capnproto" in a lot of places. For consistency, it should be written "Cap'n Proto".
- The instructions for installing the C++ runtime should really direct people at a release version, not git head as it may be broken at any particular time. I'd suggest keeping this pointing at the most recent release version that you've tested against.
--
--
import capnp
capnp.add_import_hook()
import addressbook # it will search everywhere in sys.path for a file named addressbook.capnp import capnp
capnp.add_import_hook(['/usr/local/include/capnp'])
import schemaAwesome!Two things:- What happens if add_import_hook is called from multiple files?
- It may be common for people to want to have a schema and a Python module by the same name, e.g. "addressbook.capnp" and "addressbook.py". I wonder if it makes sense to require the import name to have a "_capnp" suffix or something. (I'm not sure about this, just throwing it out as an idea.)
import sys
sys.path.append('/usr/local/include')
import schema_capnp
sys.path.pop()Just a quick announcement of a few nifty features added in the python wrappers:- Andrew Lutomirski just recently added support for testing DynamicStructs concrete type through `isinstance`
- Andrew also added to_bytes/from_bytes, that will serialize a Cap'n Proto message to/from a byte string.
to_bytes_packed/from_bytes_packed coming soon.
- I've recently added to_dict/from_dict methods, which will serialize to/from a python dictionary.
Segfaults for me after a long pause, maybe because I tried it on a recursive type. You should use DynamicStruct::has to check for null pointers and avoid following them. (Perhaps "has" should be exposed publicly as well.)