Skip to content

Commit

Permalink
fix for issue #1992: Confusing attribute names in PairwiseAligner (#2001
Browse files Browse the repository at this point in the history
)

* replaced match/mismatch by match_score/mismatch_score, and disallowing additional attributes

* check that non-existing properties raise an AttributeError
  • Loading branch information
mdehoon committed Apr 12, 2019
1 parent efbb646 commit 7b678f8
Show file tree
Hide file tree
Showing 4 changed files with 512 additions and 495 deletions.
13 changes: 10 additions & 3 deletions Bio/Align/__init__.py
Expand Up @@ -1179,7 +1179,7 @@ def aligned(self):
aligned to each other. In particular, this may occur if alignments
differ from each other in terms of their gap placement only:
>>> aligner.mismatch = -10
>>> aligner.mismatch_score = -10
>>> alignments = aligner.align("AAACAAA", "AAAGAAA")
>>> len(alignments)
2
Expand Down Expand Up @@ -1345,8 +1345,8 @@ class PairwiseAligner(_aligners.PairwiseAligner):
1 point is deducted for each non-identical character.
>>> aligner.mode = 'global'
>>> aligner.match = 2
>>> aligner.mismatch = -1
>>> aligner.match_score = 2
>>> aligner.mismatch_score = -1
>>> for alignment in aligner.align("ACCGT", "ACG"):
... print("Score = %.1f:" % alignment.score)
... print(alignment)
Expand Down Expand Up @@ -1405,6 +1405,13 @@ class PairwiseAligner(_aligners.PairwiseAligner):
"""

def __setattr__(self, key, value):
if key not in dir(_aligners.PairwiseAligner):
# To prevent confusion, don't allow users to create new attributes
message = "'PairwiseAligner' object has no attribute '%s'" % key
raise AttributeError(message)
_aligners.PairwiseAligner.__setattr__(self, key, value)

def align(self, seqA, seqB):
"""Return the alignments of two sequences using PairwiseAligner."""
seqA = str(seqA)
Expand Down
74 changes: 41 additions & 33 deletions Bio/Align/_aligners.c
Expand Up @@ -1824,79 +1824,79 @@ Aligner_str(Aligner* self)
n = sprintf(text, "Pairwise sequence aligner with parameters\n");
p += n;
if (self->letters) {
n = sprintf(p, " match/mismatch score: <substitution matrix>\n");
n = sprintf(p, " match/mismatch_score: <substitution matrix>\n");
p += n;
} else {
n = sprintf(p, " match score: %f\n", self->match);
n = sprintf(p, " match_score: %f\n", self->match);
p += n;
n = sprintf(p, " mismatch score: %f\n", self->mismatch);
n = sprintf(p, " mismatch_score: %f\n", self->mismatch);
p += n;
}
if (self->target_gap_function) {
#if PY_MAJOR_VERSION >= 3
n = sprintf(p, " target gap function: %%R\n");
n = sprintf(p, " target_gap_function: %%R\n");
p += n;
#else
char* s;
PyObject* representation = PyObject_Repr(self->target_gap_function);
if (!representation) return PyErr_NoMemory();
s = PyString_AsString(representation);
n = sprintf(p, " target gap function: %s\n", s);
n = sprintf(p, " target_gap_function: %s\n", s);
p += n;
Py_DECREF(representation);
#endif
}
else {
n = sprintf(p, " target open gap score: %f\n",
n = sprintf(p, " target_open_gap_score: %f\n",
self->target_open_gap_score);
p += n;
n = sprintf(p, " target extend gap score: %f\n",
n = sprintf(p, " target_extend_gap_score: %f\n",
self->target_extend_gap_score);
p += n;
n = sprintf(p, " target left open gap score: %f\n",
n = sprintf(p, " target_left_open_gap_score: %f\n",
self->target_left_open_gap_score);
p += n;
n = sprintf(p, " target left extend gap score: %f\n",
n = sprintf(p, " target_left_extend_gap_score: %f\n",
self->target_left_extend_gap_score);
p += n;
n = sprintf(p, " target right open gap score: %f\n",
n = sprintf(p, " target_right_open_gap_score: %f\n",
self->target_right_open_gap_score);
p += n;
n = sprintf(p, " target right extend gap score: %f\n",
n = sprintf(p, " target_right_extend_gap_score: %f\n",
self->target_right_extend_gap_score);
p += n;
}
if (self->query_gap_function) {
#if PY_MAJOR_VERSION >= 3
n = sprintf(p, " query gap function: %%R\n");
n = sprintf(p, " query_gap_function: %%R\n");
p += n;
#else
char* s;
PyObject* representation = PyObject_Repr(self->query_gap_function);
if (!representation) return PyErr_NoMemory();
s = PyString_AsString(representation);
n = sprintf(p, " query gap function: %s\n", s);
n = sprintf(p, " query_gap_function: %s\n", s);
p += n;
Py_DECREF(representation);
#endif
}
else {
n = sprintf(p, " query open gap score: %f\n",
n = sprintf(p, " query_open_gap_score: %f\n",
self->query_open_gap_score);
p += n;
n = sprintf(p, " query extend gap score: %f\n",
n = sprintf(p, " query_extend_gap_score: %f\n",
self->query_extend_gap_score);
p += n;
n = sprintf(p, " query left open gap score: %f\n",
n = sprintf(p, " query_left_open_gap_score: %f\n",
self->query_left_open_gap_score);
p += n;
n = sprintf(p, " query left extend gap score: %f\n",
n = sprintf(p, " query_left_extend_gap_score: %f\n",
self->query_left_extend_gap_score);
p += n;
n = sprintf(p, " query right open gap score: %f\n",
n = sprintf(p, " query_right_open_gap_score: %f\n",
self->query_right_open_gap_score);
p += n;
n = sprintf(p, " query right extend gap score: %f\n",
n = sprintf(p, " query_right_extend_gap_score: %f\n",
self->query_right_extend_gap_score);
p += n;
}
Expand Down Expand Up @@ -1970,10 +1970,10 @@ Aligner_set_mode(Aligner* self, PyObject* value, void* closure)
return -1;
}

static char Aligner_match__doc__[] = "match score";
static char Aligner_match_score__doc__[] = "match score";

static PyObject*
Aligner_get_match(Aligner* self, void* closure)
Aligner_get_match_score(Aligner* self, void* closure)
{ if (self->letters) {
PyErr_SetString(PyExc_ValueError, "using a substitution matrix");
return NULL;
Expand All @@ -1982,7 +1982,7 @@ Aligner_get_match(Aligner* self, void* closure)
}

static int
Aligner_set_match(Aligner* self, PyObject* value, void* closure)
Aligner_set_match_score(Aligner* self, PyObject* value, void* closure)
{ int i;
const int n = 26;
const double match = PyFloat_AsDouble(value);
Expand All @@ -2001,10 +2001,10 @@ Aligner_set_match(Aligner* self, PyObject* value, void* closure)
return 0;
}

static char Aligner_mismatch__doc__[] = "mismatch score";
static char Aligner_mismatch_score__doc__[] = "mismatch score";

static PyObject*
Aligner_get_mismatch(Aligner* self, void* closure)
Aligner_get_mismatch_score(Aligner* self, void* closure)
{ if (self->letters) {
PyErr_SetString(PyExc_ValueError, "using a substitution matrix");
return NULL;
Expand All @@ -2013,7 +2013,7 @@ Aligner_get_mismatch(Aligner* self, void* closure)
}

static int
Aligner_set_mismatch(Aligner* self, PyObject* value, void* closure)
Aligner_set_mismatch_score(Aligner* self, PyObject* value, void* closure)
{ int i, j;
const int n = 26;
const double mismatch = PyFloat_AsDouble(value);
Expand Down Expand Up @@ -3811,14 +3811,22 @@ static PyGetSetDef Aligner_getset[] = {
(getter)Aligner_get_mode,
(setter)Aligner_set_mode,
Aligner_mode__doc__, NULL},
{"match",
(getter)Aligner_get_match,
(setter)Aligner_set_match,
Aligner_match__doc__, NULL},
{"mismatch",
(getter)Aligner_get_mismatch,
(setter)Aligner_set_mismatch,
Aligner_mismatch__doc__, NULL},
{"match_score",
(getter)Aligner_get_match_score,
(setter)Aligner_set_match_score,
Aligner_match_score__doc__, NULL},
{"mismatch_score",
(getter)Aligner_get_mismatch_score,
(setter)Aligner_set_mismatch_score,
Aligner_mismatch_score__doc__, NULL},
{"match", /* synonym for match_score */
(getter)Aligner_get_match_score,
(setter)Aligner_set_match_score,
Aligner_match_score__doc__, NULL},
{"mismatch", /* synonym for mismatch_score */
(getter)Aligner_get_mismatch_score,
(setter)Aligner_set_mismatch_score,
Aligner_mismatch_score__doc__, NULL},
{"substitution_matrix",
(getter)Aligner_get_substitution_matrix,
(setter)Aligner_set_substitution_matrix,
Expand Down
60 changes: 30 additions & 30 deletions Doc/Tutorial/chapter_align.tex
Expand Up @@ -1668,20 +1668,20 @@ \subsubsection{The pairwise aligner object}
\begin{verbatim}
>>> print(aligner)
Pairwise sequence aligner with parameters
match score: 1.000000
mismatch score: 0.000000
target open gap score: 0.000000
target extend gap score: 0.000000
target left open gap score: 0.000000
target left extend gap score: 0.000000
target right open gap score: 0.000000
target right extend gap score: 0.000000
query open gap score: 0.000000
query extend gap score: 0.000000
query left open gap score: 0.000000
query left extend gap score: 0.000000
query right open gap score: 0.000000
query right extend gap score: 0.000000
match_score: 1.000000
mismatch_score: 0.000000
target_open_gap_score: 0.000000
target_extend_gap_score: 0.000000
target_left_open_gap_score: 0.000000
target_left_extend_gap_score: 0.000000
target_right_open_gap_score: 0.000000
target_right_extend_gap_score: 0.000000
query_open_gap_score: 0.000000
query_extend_gap_score: 0.000000
query_left_open_gap_score: 0.000000
query_left_extend_gap_score: 0.000000
query_right_open_gap_score: 0.000000
query_right_extend_gap_score: 0.000000
mode: local
<BLANKLINE>
\end{verbatim}
Expand Down Expand Up @@ -1716,14 +1716,14 @@ \subsubsection{Match and mismatch scores}
\begin{verbatim}
>>> from Bio import Align
>>> aligner = Align.PairwiseAligner()
>>> aligner.match
>>> aligner.match_score
1.0
>>> aligner.mismatch
>>> aligner.mismatch_score
0.0
>>> score = aligner.score("AAA","AAC")
>>> print(score)
2.0
>>> aligner.match = 2.0
>>> aligner.match_score = 2.0
>>> score = aligner.score("AAA","AAC")
>>> print(score)
4.0
Expand All @@ -1736,9 +1736,9 @@ \subsubsection{Match and mismatch scores}
>>> aligner.substitution_matrix = matrix
\end{verbatim}

The attributes \verb+aligner.match+ and \verb+aligner.mismatch+ are ignored
if \verb+aligner.substitution_matrix+ is specified. Likewise, after specifying
\verb+aligner.match+ or \verb+aligner.mismatch+,
The attributes \verb+aligner.match_score+ and \verb+aligner.mismatch_score+ are
ignored if \verb+aligner.substitution_matrix+ is specified. Likewise, after
specifying \verb+aligner.match_score+ or \verb+aligner.mismatch_score+,
\verb+aligner.substitution_matrix+ will be ignored.

Note that \verb+aligner.substitution_matrix+ will return a copy of the
Expand Down Expand Up @@ -1792,22 +1792,22 @@ \subsubsection{Affine gap scores}
A & - & query left open gap score \\
C & - & query left extend gap score \\
C & - & query left extend gap score \\
G & G & match \\
G & T & mismatch \\
G & G & match score \\
G & T & mismatch score \\
G & - & query internal open gap score \\
A & - & query internal extend gap score \\
A & - & query internal extend gap score \\
T & T & match \\
A & A & match \\
T & T & match score \\
A & A & match score \\
G & - & query internal open gap score \\
C & C & match \\
C & C & match score \\
- & C & target internal open gap score \\
- & C & target internal extend gap score \\
C & C & match \\
T & G & mismatch \\
C & C & match \\
C & C & match score \\
T & G & mismatch score \\
C & C & match score \\
- & C & target internal open gap score \\
A & A & match \\
A & A & match score \\
- & T & target right open gap score \\
- & A & target right extend gap score \\
- & A & target right extend gap score
Expand Down Expand Up @@ -2060,7 +2060,7 @@ \subsubsection{Alignment objects}
Note that different alignments may have the same subsequences aligned to each other. In particular, this may occur if alignments differ from each other in terms of their gap placement only:
%cont-doctest
\begin{verbatim}
>>> aligner.mismatch = -10
>>> aligner.mismatch_score = -10
>>> alignments = aligner.align("AAACAAA", "AAAGAAA")
>>> len(alignments)
2
Expand Down

0 comments on commit 7b678f8

Please sign in to comment.