allpy
diff allpy/base.py @ 1091:afed1fd8920c
Added backreferences to `Seqeunce`s from `Monomer`s (closes #49)
WARNING! Please note that `Sequence` API almost changed entirely!
WARNING! This commit immediately obsoletes classmethods `Monomer.from_code*`,
`Monomer.from_name` and `Sequence.from_monomers`.
Turns out, python can not pickle sets/dicts which have keys, which inderecly
reference the set/dict itself: http://bugs.python.org/issue9269 -- which is
excatly what we have in abundance after this change.
To allow pickling added `__getstate__` to `Monomer` to return all attributes,
except `sequence` and `__setstate__` to `Sequence`, which runs through all
monomers and returns the `sequence` attribute back to where it belongs.
WARNING! This MAY result in unexpected behaviour in some cases. (Which should
be rare enough).
author | Daniil Alexeyevsky <dendik@kodomo.fbb.msu.ru> |
---|---|
date | Sat, 02 Jun 2012 19:33:42 +0400 |
parents | 4b1a6a7bbeea |
children | 41a167bbf150 |
line diff
1.1 --- a/allpy/base.py Sat Jun 02 19:29:40 2012 +0400 1.2 +++ b/allpy/base.py Sat Jun 02 19:33:42 2012 +0400 1.3 @@ -29,6 +29,9 @@ 1.4 by_name = {} 1.5 """A mapping from full monomer name to Monomer subclass.""" 1.6 1.7 + sequence = None 1.8 + """A sequence the monomer belongs to.""" 1.9 + 1.10 @classmethod 1.11 def _subclass(cls, name='', code1='', code3='', is_modified=False): 1.12 """Create new subclass of Monomer for given monomer type.""" 1.13 @@ -67,23 +70,6 @@ 1.14 for code1, is_modified, code3, name in codes: 1.15 cls._subclass(name, code1, code3, is_modified) 1.16 1.17 - @classmethod 1.18 - def from_code1(cls, code1): 1.19 - """Create new monomer from 1-letter code.""" 1.20 - monomer = cls.by_code1[code1.upper()]() 1.21 - monomer.input_code1 = code1 1.22 - return monomer 1.23 - 1.24 - @classmethod 1.25 - def from_code3(cls, code3): 1.26 - """Create new monomer from 3-letter code.""" 1.27 - return cls.by_code3[code3.upper()]() 1.28 - 1.29 - @classmethod 1.30 - def from_name(cls, name): 1.31 - """Create new monomer from full name.""" 1.32 - return cls.by_name[name.strip().capitalize()]() 1.33 - 1.34 def __repr__(self): 1.35 return "<Monomer %s>" % str(self.code1) 1.36 1.37 @@ -101,6 +87,30 @@ 1.38 def __ne__(self, other): 1.39 return not (self == other) 1.40 1.41 + def __getstate__(self): 1.42 + """Overcome difficulties with pickle. 1.43 + 1.44 + Pickle is unable to store `set`s/`dict`s that have objects referencing 1.45 + back the `set`/`dict` itself, which `sequence` in monomer does. 1.46 + ( http://bugs.python.org/issue9269 ) 1.47 + 1.48 + To sidestep the bug we store the monomer WITHOUT `sequence` attribute. 1.49 + 1.50 + See also `Sequence.__setstate__`. 1.51 + """ 1.52 + state = {} 1.53 + state.update(vars(self)) 1.54 + if 'sequence' in state: 1.55 + del state['sequence'] 1.56 + return state 1.57 + 1.58 + def _obsolete_method(cls, *args, **kws): 1.59 + """OBSOLETE""" 1.60 + raise AttributeError("Call to obsolete method.") 1.61 + from_code1 = classmethod(_obsolete_method) 1.62 + from_code3 = classmethod(_obsolete_method) 1.63 + from_name = classmethod(_obsolete_method) 1.64 + 1.65 class MarkupContainerMixin(object): 1.66 """Common functions for alignment and sequence for dealing with markups. 1.67 """ 1.68 @@ -162,31 +172,45 @@ 1.69 """Description of object kind.""" 1.70 1.71 name = '' 1.72 + """Squence identifier.""" 1.73 + 1.74 description = '' 1.75 + """Detailed sequence description.""" 1.76 + 1.77 source = '' 1.78 + """Sequence source.""" 1.79 1.80 - def __init__(self, *args): 1.81 - list.__init__(self, *args) 1.82 + def __init__(self, sequence=(), name='', description='', source=''): 1.83 + list.__init__(self, sequence) 1.84 MarkupContainerMixin._init(self) 1.85 1.86 - @classmethod 1.87 - def from_monomers(cls, monomers=[], name=None, description=None, source=None): 1.88 - """Create sequence from a list of monomer objecst.""" 1.89 - result = cls(monomers) 1.90 - if name: 1.91 - result.name = name 1.92 - if description: 1.93 - result.description = description 1.94 - if source: 1.95 - result.source = source 1.96 - return result 1.97 + self.name = name 1.98 + self.description = description 1.99 + self.source = source 1.100 + 1.101 + def append_monomer(self, code1=None, code3=None, name=None): 1.102 + """Append a new monomer to the sequence. Return the new monomer.""" 1.103 + assert bool(code1) + bool(code3) + bool(name) == 1, \ 1.104 + "Please specify exactly one of: code1, code3, name" 1.105 + if code1: 1.106 + cls = self.types.Monomer.by_code1[code1.upper()] 1.107 + elif code3: 1.108 + cls = self.types.Monomer.by_code3[code3.upper()] 1.109 + elif name: 1.110 + cls = self.types.Monomer.by_name[name.strip().capitalize()] 1.111 + monomer = cls() 1.112 + monomer.sequence = self 1.113 + monomer.input_code1 = code1 1.114 + self.append(monomer) 1.115 + return monomer 1.116 1.117 @classmethod 1.118 def from_string(cls, string, name='', description='', source=''): 1.119 """Create sequences from string of one-letter codes.""" 1.120 - monomer = cls.types.Monomer.from_code1 1.121 - monomers = [monomer(letter) for letter in string] 1.122 - return cls.from_monomers(monomers, name, description, source) 1.123 + self = cls([], name=name, description=description, source=source) 1.124 + for letter in string: 1.125 + self.append_monomer(code1=letter) 1.126 + return self 1.127 1.128 def __repr__(self): 1.129 if self.name: 1.130 @@ -202,6 +226,26 @@ 1.131 """Hash sequence by identity.""" 1.132 return id(self) 1.133 1.134 + def __setstate__(self, state): 1.135 + """Overcome difficulties with pickle: add `monomer.sequence` after loading. 1.136 + 1.137 + Pickle is unable to store `set`s/`dict`s that have objects referencing 1.138 + back the `set`/`dict` itself, which `sequence` in monomer does. 1.139 + ( http://bugs.python.org/issue9269 ) 1.140 + 1.141 + To sidestep the bug we store the monomer WITHOUT `sequence` attribute. 1.142 + 1.143 + See also `Monomer.__getstate__`. 1.144 + """ 1.145 + vars(self).update(state) 1.146 + for monomer in self: 1.147 + monomer.sequence = self 1.148 + 1.149 + @classmethod 1.150 + def from_monomers(cls, *args, **kws): 1.151 + """OBSOLETE.""" 1.152 + raise AttributeError("Sequence.from_monomers is obsolete") 1.153 + 1.154 class Alignment(MarkupContainerMixin): 1.155 """Alignment. It is a list of Columns.""" 1.156