Repository: ethereum/pyrlp

Issue:
Serializable.get_sedes() breaks inheritance when child defines different fields
That method [caches the sedes in the _sedes class variable](https://github.com/ethereum/pyrlp/blob/develop/rlp/sedes/lists.py#L226), so if you call get_sedes() on a parent class, any childs of that class on which get_sedes() has not been called yet will end up with the parent's sedes no matter what is defined in their fields attribute. 

```
class CustomSerializable(rlp.Serializable):
    pass

class A(rlp.Serializable):
    fields = [('foo', rlp.Serializable)]

class B(A):
    fields = [('foo', CustomSerializable)]

print(A.get_sedes())
print(B.get_sedes())

[<class 'rlp.sedes.lists.Serializable'>]
[<class 'rlp.sedes.lists.Serializable'>]
```

Discussion:

Patch:
diff --git a/.travis.yml b/.travis.yml
index b6580be..e936b90 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,12 +2,20 @@ language: python
 python:
   - "3.5"
 sudo: false
-env:
-  - TOXENV=py27
-  - TOXENV=py34
-  - TOXENV=py35
-  - TOXENV=pypy
-  #- TOXENV=pypy3
+matrix:
+  include:
+    - python: "2.7"
+      env: TOXENV=py27
+    - python: "3.4"
+      env: TOXENV=py34
+    - python: "3.5"
+      env: TOXENV=py35
+    - python: "3.6"
+      env: TOXENV=py36
+    - python: "pypy"
+      env: TOXENV=pypy
+    - python: "pypy3"
+      env: TOXENV=pypy3
 
 install:
  - pip install tox
diff --git a/rlp/sedes/lists.py b/rlp/sedes/lists.py
index ee7b3b4..434ecb0 100644
--- a/rlp/sedes/lists.py
+++ b/rlp/sedes/lists.py
@@ -157,7 +157,7 @@ class Serializable(object):
     """
 
     fields = tuple()
-    _sedes = None
+    _cached_sedes = {}
     _mutable = True
     _cached_rlp = None
 
@@ -222,9 +222,9 @@ class Serializable(object):
 
     @classmethod
     def get_sedes(cls):
-        if not cls._sedes:
-            cls._sedes = List(sedes for _, sedes in cls.fields)
-        return cls._sedes
+        if cls not in cls._cached_sedes:
+            cls._cached_sedes[cls] = List(sedes for _, sedes in cls.fields)
+        return cls._cached_sedes[cls]
 
     @classmethod
     def serialize(cls, obj):
diff --git a/tox.ini b/tox.ini
index 23f937a..71f7ae6 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,8 +1,11 @@
 [tox]
-envlist = py{27,34,35,py,py3}
+envlist = py{27,34,35,36,py,py3}
 
 [testenv]
-passenv = TRAVIS TRAVIS_JOB_ID TRAVIS_BRANCH
+passenv =
+    TRAVIS
+    TRAVIS_JOB_ID
+    TRAVIS_BRANCH
 setenv =
     PYTHONPATH = {toxinidir}:{toxinidir}/pyrlp
 commands = 
@@ -17,5 +20,6 @@ basepython =
     py27: python2.7
     py34: python3.4
     py35: python3.5
+    py36: python3.6
     pypy: pypy
     pypy3: pypy3


Test patch:
diff --git a/tests/test_serializable.py b/tests/test_serializable.py
index 92394c1..371c8b3 100644
--- a/tests/test_serializable.py
+++ b/tests/test_serializable.py
@@ -192,3 +192,59 @@ def test_make_mutable():
     assert test2.is_mutable()
     assert test1a.is_mutable()
     assert test1b.is_mutable()
+
+
+def test_inheritance():
+    class Parent(Serializable):
+        fields = (
+            ('field1', big_endian_int),
+            ('field2', big_endian_int),
+        )
+    class Child1(Parent):
+        fields = (
+            ('field1', big_endian_int),
+            ('field2', big_endian_int),
+            ('field3', big_endian_int),
+        )
+    class Child2(Parent):
+        fields = (
+            ('field1', big_endian_int),
+        )
+    class Child3(Parent):
+        fields = (
+            ('new_field1', big_endian_int),
+            ('field2', big_endian_int),
+        )
+    class Child4(Parent):
+        fields = (
+            ('field1', binary),
+            ('field2', binary),
+            ('field3', big_endian_int),
+        )
+
+    p = Parent(1, 2)
+    c1 = Child1(1, 2, 3)
+    c2 = Child2(1)
+    c3 = Child3(1, 2)
+    c4 = Child4(b'a', b'b', 5)
+
+    assert Parent.serialize(p) == [b'\x01', b'\x02']
+    assert Child1.serialize(c1) == [b'\x01', b'\x02', b'\x03']
+    assert Child2.serialize(c2) == [b'\x01']
+    assert Child3.serialize(c3) == [b'\x01', b'\x02']
+    assert Child4.serialize(c4) == [b'a', b'b', b'\x05']
+
+    with pytest.raises(AttributeError):
+        p.field3
+    with pytest.raises(AttributeError):
+        p.new_field1
+
+    with pytest.raises(AttributeError):
+        c2.field2
+    with pytest.raises(AttributeError):
+        c2.new_field1
+
+    with pytest.raises(AttributeError):
+        c3.field1
+    with pytest.raises(AttributeError):
+        c3.field3