10.1 -- Style
Python linters¶
Learning objectives:¶
By the end of this tutorial you should: - Understand why using a style guide is a good idea - Be familiar with the 'black' style guide for Python. - Be able to implement 'black' on your code from a shell.
Install some linters¶
Let's start with installing the pycodestyle
, pylint
and black
linters.
conda install pycodestyle pylint black -c conda-forge
Reading¶
Please read the following short article about why using a style guide is a good idea. - Why use a style guide (JS)
Then read the following articles which introduce black, and why you should use it as your preferred style. (I do not necessary promote this as the best style guide, personally I use pylint, but I think black is good to know about.) - Black code style
Implementing black¶
Unlike other linters that we will learn about, black is intended not to be modified,
in other words, if you use black you are expected to accept all of its recommendations.
For this reason, it doesn't really function as a recommender, but moreso as an
implementer. It can make changes directly to your Python files to fix the style
errors that it finds. Personally, I find this a little obtrusive, so I always first
run it with the argument --diff
, which will show you the changes it intends to make
without actually making them. It's sort of a dry run.
Let's try this on a few example files from a public repository. Use the commands below
to clone two repos into your ~/hacks
directory. We will not bother forking the repos
this time, since we do not intend to push the changes we will make to these files back
to the origin repos.
cd ~/hacks/
git clone https://github.com/rajanil/fastStructure
git clone https://github.com/eaton-lab/toytree/
Let's start by running black on one of the main scripts in the fastStructure package.
This is a tool that was published about 6 years ago, so its a bit outdated now, but
still in common use. It is written in Python2.7, and impressively black is able to
recognize this and propose changes to the code style. Here I run it using the
--diff
option.
The format of this output is called a diff
, it is showing the differences
between the original file and the file that black
would have made if it had been allowed to overwrite the original file (i.e., if we
hadn't used the --diff arg). On the left side of each line it shows a minus or plus
sign. The minus lines are from the old file, and the plus lines are showing what
would replace the old lines. In some cases it may look like no change was made,
this usually means that the difference has to do with the whitespace, for example,
by removing extra spaces at the ends of lines.
black --diff fastStructure/structure.py
--- fastStructure/structure.py 2021-02-03 17:02:15.106875 +0000
+++ fastStructure/structure.py 2021-02-03 21:20:31.833481 +0000
@@ -1,137 +1,157 @@
-
import numpy as np
-import fastStructure
+import fastStructure
import parse_bed
import parse_str
import random
import getopt
import sys
import pdb
import warnings
# ignore warnings with these expressions
-warnings.filterwarnings('ignore', '.*divide by zero.*',)
-warnings.filterwarnings('ignore', '.*invalid value.*',)
+warnings.filterwarnings(
+ "ignore",
+ ".*divide by zero.*",
+)
+warnings.filterwarnings(
+ "ignore",
+ ".*invalid value.*",
+)
+
def parseopts(opts):
"""
parses the command-line flags and options passed to the script
"""
- params = {'mintol': 1e-6,
- 'prior': "simple",
- 'cv': 0,
- 'full': False,
- 'format': 'bed'
- }
+ params = {
+ "mintol": 1e-6,
+ "prior": "simple",
+ "cv": 0,
+ "full": False,
+ "format": "bed",
+ }
for opt, arg in opts:
if opt in ["-K"]:
- params['K'] = int(arg)
+ params["K"] = int(arg)
elif opt in ["--input"]:
- params['inputfile'] = arg
+ params["inputfile"] = arg
elif opt in ["--output"]:
- params['outputfile'] = arg
+ params["outputfile"] = arg
elif opt in ["--prior"]:
- params['prior'] = arg
-
- if params['prior'] not in ['simple','logistic']:
+ params["prior"] = arg
+
+ if params["prior"] not in ["simple", "logistic"]:
print "%s prior is not currently implemented, defaulting to the simple prior"
- params['prior'] = 'simple'
+ params["prior"] = "simple"
elif opt in ["--format"]:
- params['format'] = arg
+ params["format"] = arg
elif opt in ["--cv"]:
- params['cv'] = int(arg)
-
+ params["cv"] = int(arg)
+
elif opt in ["--tol"]:
- params['mintol'] = float(arg)
+ params["mintol"] = float(arg)
elif opt in ["--full"]:
- params['full'] = True
+ params["full"] = True
elif opt in ["--seed"]:
np.random.seed(int(arg))
random.seed(int(arg))
return params
+
def checkopts(params):
"""
checks if some of the command-line options passed are valid.
In the case of invalid options, an exception is always thrown.
"""
- if params['mintol']<=0:
+ if params["mintol"] <= 0:
print "a non-positive value was provided as convergence criterion"
raise ValueError
-
- if params['cv']<0:
+
+ if params["cv"] < 0:
print "a negative value was provided for the number of cross-validations folds"
raise ValueError
- if not params.has_key('K'):
+ if not params.has_key("K"):
print "a positive integer should be provided for number of populations"
raise KeyError
- if params['format'] not in ['bed','str']:
+ if params["format"] not in ["bed", "str"]:
print "%s data format is not currently implemented"
raise ValueError
- if params['K']<=0:
+ if params["K"] <= 0:
print "a negative value was provided for the number of populations"
raise ValueError
-
- if not params.has_key('inputfile'):
+
+ if not params.has_key("inputfile"):
print "an input file needs to be provided"
- raise KeyError
-
- if not params.has_key('outputfile'):
+ raise KeyError
+
+ if not params.has_key("outputfile"):
print "an output file needs to be provided"
raise KeyError
-
+
+
def write_output(Q, P, other, params):
"""
write the posterior means and variational parameters
to separate output files.
"""
- handle = open('%s.%d.meanQ'%(params['outputfile'],params['K']),'w')
- handle.write('\n'.join([' '.join(['%.6f'%i for i in q]) for q in Q])+'\n')
+ handle = open("%s.%d.meanQ" % (params["outputfile"], params["K"]), "w")
+ handle.write("\n".join([" ".join(["%.6f" % i for i in q]) for q in Q]) + "\n")
handle.close()
- handle = open('%s.%d.meanP'%(params['outputfile'],params['K']),'w')
- handle.write('\n'.join([' '.join(['%.6f'%i for i in p]) for p in P])+'\n')
+ handle = open("%s.%d.meanP" % (params["outputfile"], params["K"]), "w")
+ handle.write("\n".join([" ".join(["%.6f" % i for i in p]) for p in P]) + "\n")
handle.close()
- if params['full']:
- handle = open('%s.%d.varQ'%(params['outputfile'],params['K']),'w')
- handle.write('\n'.join([' '.join(['%.6f'%i for i in q]) for q in other['varQ']])+'\n')
+ if params["full"]:
+ handle = open("%s.%d.varQ" % (params["outputfile"], params["K"]), "w")
+ handle.write(
+ "\n".join([" ".join(["%.6f" % i for i in q]) for q in other["varQ"]])
+ + "\n"
+ )
handle.close()
- handle = open('%s.%d.varP'%(params['outputfile'],params['K']),'w')
- handle.write('\n'.join([' '.join(['%.6f'%i for i in np.hstack((pb,pg))]) \
- for pb,pg in zip(other['varPb'],other['varPg'])])+'\n')
+ handle = open("%s.%d.varP" % (params["outputfile"], params["K"]), "w")
+ handle.write(
+ "\n".join(
+ [
+ " ".join(["%.6f" % i for i in np.hstack((pb, pg))])
+ for pb, pg in zip(other["varPb"], other["varPg"])
+ ]
+ )
+ + "\n"
+ )
handle.close()
+
def usage():
-
+
"""
brief description of various flags and options for this script
"""
print "\nHere is how you can use this script\n"
- print "Usage: python %s"%sys.argv[0]
+ print "Usage: python %s" % sys.argv[0]
print "\t -K <int> (number of populations)"
print "\t --input=<file> (/path/to/input/file)"
print "\t --output=<file> (/path/to/output/file)"
print "\t --tol=<float> (convergence criterion; default: 10e-6)"
print "\t --prior={simple,logistic} (choice of prior; default: simple)"
@@ -139,16 +159,25 @@
print "\t --format={bed,str} (format of input file; default: bed)"
print "\t --full (to output all variational parameters; optional)"
print "\t --seed=<int> (manually specify seed for random number generator; optional)"
-if __name__=="__main__":
+if __name__ == "__main__":
# parse command-line options
argv = sys.argv[1:]
smallflags = "K:"
- bigflags = ["prior=", "tol=", "input=", "output=", "cv=", "seed=", "format=", "full"]
+ bigflags = [
+ "prior=",
+ "tol=",
+ "input=",
+ "output=",
+ "cv=",
+ "seed=",
+ "format=",
+ "full",
+ ]
try:
opts, args = getopt.getopt(argv, smallflags, bigflags)
if not opts:
usage()
sys.exit(2)
@@ -160,22 +189,27 @@
params = parseopts(opts)
# check if command-line options are valid
try:
checkopts(params)
- except (ValueError,KeyError):
+ except (ValueError, KeyError):
sys.exit(2)
# load data
- if params['format']=='bed':
- G = parse_bed.load(params['inputfile'])
- elif params['format']=='str':
- G = parse_str.load(params['inputfile'])
- G = np.require(G, dtype=np.uint8, requirements='C')
+ if params["format"] == "bed":
+ G = parse_bed.load(params["inputfile"])
+ elif params["format"] == "str":
+ G = parse_str.load(params["inputfile"])
+ G = np.require(G, dtype=np.uint8, requirements="C")
# run the variational algorithm
- Q, P, other = fastStructure.infer_variational_parameters(G, params['K'], \
- params['outputfile'], params['mintol'], \
- params['prior'], params['cv'])
+ Q, P, other = fastStructure.infer_variational_parameters(
+ G,
+ params["K"],
+ params["outputfile"],
+ params["mintol"],
+ params["prior"],
+ params["cv"],
+ )
# write out inferred parameters
write_output(Q, P, other, params)
<b>would reformat fastStructure/structure.py</b>
<b>All done! ✨ 🍰 ✨</b>
<b>1 file would be reformatted</b>.
Pylint¶
Running this first example below will encounter an error and STOP when run on the structure.py script, since the script cannot successfully execute in its current mode. The terminal error in this case is caused by the fact that pylint uses your current Python version to test for errors, and this script is written in Python2, whereas we are testing in Python3. It raises an error saying that the print function is being called incorrectly. This is a common error-producing difference between py2/3. This is fine, good to know, we'll continue to the next example.
pylint fastStructure/structure.py
************* Module structure
fastStructure/structure.py:44:23: E0001: Missing parentheses in call to 'print'. Did you mean print("%s prior is not currently implemented, defaulting to the simple prior")? (<unknown>, line 44) (syntax-error)
Now let's run pylint on some code that is written to work in either Python 2 or Python 3. Writing code in this way is kind of a pain, but is worth it for big general use packages. The Python package Toytree is an example. You can see in this example that pylint finishes running and reports a score of 7.26/10. Not bad. It has several recommendations having to do with mundane style (extra whitespace), but also some useful suggestions for cleaner code, like "unnecessary else after break". If we were to edit the files at the suggested lines to implement these suggestions, and rerun pylint, it should return improved scores until it reaches 10/10.
pylint toytree/toytree/Rooter.py
************* Module toytree.Rooter
toytree/toytree/Rooter.py:52:26: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:90:42: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:106:27: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:114:65: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:138:31: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:139:33: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:158:60: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:182:73: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:183:66: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:196:69: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:197:70: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:216:88: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:244:70: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:247:23: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:248:32: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:250:9: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:262:73: C0303: Trailing whitespace (trailing-whitespace)
toytree/toytree/Rooter.py:1:0: C0103: Module name "Rooter" doesn't conform to snake_case naming style (invalid-name)
toytree/toytree/Rooter.py:11:0: C0115: Missing class docstring (missing-class-docstring)
toytree/toytree/Rooter.py:11:0: R0902: Too many instance attributes (11/7) (too-many-instance-attributes)
toytree/toytree/Rooter.py:50:8: C0103: Variable name "x0" doesn't conform to snake_case naming style (invalid-name)
toytree/toytree/Rooter.py:51:8: C0103: Variable name "x1" doesn't conform to snake_case naming style (invalid-name)
toytree/toytree/Rooter.py:70:4: C0116: Missing function or method docstring (missing-function-docstring)
toytree/toytree/Rooter.py:79:4: C0116: Missing function or method docstring (missing-function-docstring)
toytree/toytree/Rooter.py:83:8: W0212: Access to a protected member _coords of a client class (protected-access)
toytree/toytree/Rooter.py:131:12: R1723: Unnecessary "else" after "break" (no-else-break)
toytree/toytree/Rooter.py:104:4: R0912: Too many branches (13/12) (too-many-branches)
toytree/toytree/Rooter.py:228:4: C0112: Empty method docstring (empty-docstring)
toytree/toytree/Rooter.py:261:4: C0116: Missing function or method docstring (missing-function-docstring)
toytree/toytree/Rooter.py:281:8: C0103: Variable name "x0" doesn't conform to snake_case naming style (invalid-name)
toytree/toytree/Rooter.py:282:8: C0103: Variable name "x1" doesn't conform to snake_case naming style (invalid-name)
------------------------------------------------------------------
Your code has been rated at 7.26/10 (previous run: 7.26/10, +0.00)
You can see that pycodestyle makes similar recommendations as pylint, but fewer, mostly having to do with things of little consequence (extra whitespace or variable names). It does run quite a bit faster though, which is convenient for when the linter is running automatically in the background of your code editor (as we'll see later).
pycodestyle toytree/toytree/Rooter.py
toytree/toytree/Rooter.py:30:80: E501 line too long (82 > 79 characters)
toytree/toytree/Rooter.py:52:27: W291 trailing whitespace
toytree/toytree/Rooter.py:70:5: E303 too many blank lines (3)
toytree/toytree/Rooter.py:79:5: E303 too many blank lines (2)
toytree/toytree/Rooter.py:87:5: E303 too many blank lines (3)
toytree/toytree/Rooter.py:90:43: W291 trailing whitespace
toytree/toytree/Rooter.py:104:5: E303 too many blank lines (3)
toytree/toytree/Rooter.py:106:28: W291 trailing whitespace
toytree/toytree/Rooter.py:114:66: W291 trailing whitespace
toytree/toytree/Rooter.py:138:32: W291 trailing whitespace
toytree/toytree/Rooter.py:139:34: W291 trailing whitespace
toytree/toytree/Rooter.py:145:80: E501 line too long (80 > 79 characters)
toytree/toytree/Rooter.py:158:61: W291 trailing whitespace
toytree/toytree/Rooter.py:182:74: W291 trailing whitespace
toytree/toytree/Rooter.py:183:67: W291 trailing whitespace
toytree/toytree/Rooter.py:194:5: E303 too many blank lines (3)
toytree/toytree/Rooter.py:196:70: W291 trailing whitespace
toytree/toytree/Rooter.py:197:71: W291 trailing whitespace
toytree/toytree/Rooter.py:216:80: E501 line too long (88 > 79 characters)
toytree/toytree/Rooter.py:216:89: W291 trailing whitespace
toytree/toytree/Rooter.py:220:17: E128 continuation line under-indented for visual indent
toytree/toytree/Rooter.py:220:80: E501 line too long (81 > 79 characters)
toytree/toytree/Rooter.py:221:17: E128 continuation line under-indented for visual indent
toytree/toytree/Rooter.py:221:80: E501 line too long (81 > 79 characters)
toytree/toytree/Rooter.py:222:17: E128 continuation line under-indented for visual indent
toytree/toytree/Rooter.py:223:17: E128 continuation line under-indented for visual indent
toytree/toytree/Rooter.py:228:5: E303 too many blank lines (3)
toytree/toytree/Rooter.py:244:71: W291 trailing whitespace
toytree/toytree/Rooter.py:247:24: W291 trailing whitespace
toytree/toytree/Rooter.py:248:33: W291 trailing whitespace
toytree/toytree/Rooter.py:250:10: W291 trailing whitespace
toytree/toytree/Rooter.py:261:5: E303 too many blank lines (3)
toytree/toytree/Rooter.py:262:74: W291 trailing whitespace
toytree/toytree/Rooter.py:270:5: E303 too many blank lines (3)
Finally, let's run black. Studying the output of the --diff command to black can be really insightful about what the actual solutions are to the many recommendations that were provided by pylint above.
black --diff toytree/toytree/Rooter.py
--- toytree/toytree/Rooter.py 2021-02-03 17:22:53.767184 +0000
+++ toytree/toytree/Rooter.py 2021-02-03 21:30:12.332704 +0000
@@ -26,11 +26,11 @@
# the new node that will be inserted
self.nnode = None
# make a copy and ensure supports are either all int or float
self.maxsup = max([int(i.support) for i in self.tree.treenode.traverse()])
- self.maxsup = (1.0 if self.maxsup <= 1.0 else 100)
+ self.maxsup = 1.0 if self.maxsup <= 1.0 else 100
self.get_features()
# parse node selecting arguments (nastuple) with NodeAssist
self.get_match(*nastuple)
@@ -45,13 +45,13 @@
if len(self.node2.children) == 1:
self.node2 = self.node2.up
self.node1 = self.node1.up
# if rooting where root already exists then return current tree
- x0 = (self.node1.is_root())
- x1 = (self.node2.is_root() and self.tree.is_rooted())
- if not (x0 or x1):
+ x0 = self.node1.is_root()
+ x1 = self.node2.is_root() and self.tree.is_rooted()
+ if not (x0 or x1):
# create new root node on an existing edge to split it.
self.insert_new_node()
# update edge lengths given new node insertion
@@ -62,58 +62,51 @@
self.restructure_tree()
# update coodrds on tree
self.update_tree_from_tdict()
self.update()
-
-
def update_tree_from_tdict(self):
# update tree structure and node labels
for node in self.tdict:
node.up = self.tdict[node][0]
node.children = self.tdict[node][1]
for key, val in self.tdict[node][2].items():
setattr(node, key, val)
-
def update(self):
# update coordinates which updates idx and adds it to any new nodes.
self.tree.treenode = self.nnode
self.tree.treenode.ladderize()
self.tree._coords.update()
-
-
def redirect_edge_features(self):
"""
Set support values to maximum for new node since the user forced
- rooting, i.e, it is not uncertain.
+ rooting, i.e, it is not uncertain.
"""
# mark new split with zero...
for feature in set(self.edge_features) - set(["support", "dist"]):
self.tdict[self.node2][2][feature] = 0.0
# unless support value, then mark with full.
if "support" in self.edge_features:
- self.tdict[self.node2][2]['support'] = self.maxsup
+ self.tdict[self.node2][2]["support"] = self.maxsup
else:
- self.tdict[self.node2][2]['support'] = self.node2.support
-
-
+ self.tdict[self.node2][2]["support"] = self.node2.support
def restructure_tree(self):
"""
- At this point tdict
+ At this point tdict
(node): (parent) (children), features
{
nnode: [None, [node1, node2], {}]
node1: [nnode, node1.children, {'dist'}]
node2: [nnode, node2.children, {'dist': 0.0}]
}
"""
- # start with the node leading from new root child2 to the
+ # start with the node leading from new root child2 to the
# rest of the tree structure and move up until old root.
tnode = self.node2.up
# label all remaining nodes by moving up from tnode to old root.
while 1:
@@ -133,12 +126,12 @@
# need a root add feature here if unrooted...
if len(children) > 1:
# update dist from new parent
self.tdict[tnode] = [
- parent,
- children,
+ parent,
+ children,
{"dist": parent.dist},
]
# update edge features from new parent
for feature in self.edge_features:
@@ -153,11 +146,11 @@
# get children that are not in tdict yet
else:
for child in children:
- # record whose children they are now
+ # record whose children they are now
# (node2 already did this)
if parent is self.node2:
self.tdict[self.node2][1].append(child)
else:
self.tdict[parent][1].append(child)
@@ -170,33 +163,31 @@
break
# normal nodes
else:
# update tnode.features [dist will be inherited from child]
- features = {'dist': tnode.dist, 'support': tnode.support}
+ features = {"dist": tnode.dist, "support": tnode.support}
# keep connecting swap parent-child up to root
if not tnode.up.is_root():
children += [tnode.up]
# pass support values down (up in new tree struct)
- child = [i for i in tnode.children if i in self.tdict][0]
- for feature in {'dist'}.union(self.edge_features):
+ child = [i for i in tnode.children if i in self.tdict][0]
+ for feature in {"dist"}.union(self.edge_features):
features[feature] = getattr(child, feature)
# store node update vals
self.tdict[tnode] = [parent, children, features]
# move towards root
tnode = tnode.up
-
-
def config_root_dist(self):
"""
- Now that the new root node is inserted .dist features must be
- set for the two descendant nodes. Midpoint rooting is a common
+ Now that the new root node is inserted .dist features must be
+ set for the two descendant nodes. Midpoint rooting is a common
option, but users can toggle 'resolve_root_dist' to change this.
"""
# if not already at root polytomy, then connect node2 to parent
if self.node2.up:
if not self.node2.up.is_root():
@@ -206,31 +197,29 @@
if self.resolve_root_dist is False:
self.resolve_root_dist = 0.0
# if True then use midpoint rooting
elif self.resolve_root_dist is True:
- self.tdict[self.node1][2]["dist"] = self.node1.dist / 2.
- self.tdict[self.node2][2]["dist"] = self.node1.dist / 2.
+ self.tdict[self.node1][2]["dist"] = self.node1.dist / 2.0
+ self.tdict[self.node2][2]["dist"] = self.node1.dist / 2.0
# split the edge on 0 or a float
if isinstance(self.resolve_root_dist, float):
- self.tdict[self.node1][2]["dist"] = self.node1.dist - self.resolve_root_dist
+ self.tdict[self.node1][2]["dist"] = self.node1.dist - self.resolve_root_dist
self.tdict[self.node2][2]["dist"] = self.resolve_root_dist
if self.resolve_root_dist > self.node1.dist:
- raise ToytreeError("\n"
- "To preserve existing edge lengths the 'resolve_root_dist' arg\n"
- "must be smaller than the edge being split (it is selecting a \n"
- "a point along the edge.) The edge above node idx {} is {}."
- .format(self.node1.idx, self.node1.dist)
+ raise ToytreeError(
+ "\n"
+ "To preserve existing edge lengths the 'resolve_root_dist' arg\n"
+ "must be smaller than the edge being split (it is selecting a \n"
+ "a point along the edge.) The edge above node idx {} is {}.".format(
+ self.node1.idx, self.node1.dist
+ )
)
-
-
def insert_new_node(self):
- """
-
- """
+ """"""
# the new root node to be placed on the split
self.nnode = self.tree.treenode.__class__()
self.nnode.name = "root"
self.nnode.add_feature("idx", self.tree.treenode.idx)
self.nnode.support = self.maxsup
@@ -239,36 +228,32 @@
self.node2.children.remove(self.node1)
# new node has no parent and 1,2 as children and default features
self.tdict[self.nnode] = [None, [self.node1, self.node2], {}]
- # node1 has new root parent, same children, and dist preserved
+ # node1 has new root parent, same children, and dist preserved
# (or split?), or should: node1.dist / 2.
self.tdict[self.node1] = [
- self.nnode,
- self.node1.children,
- {"dist": self.node1.dist}
- ]
+ self.nnode,
+ self.node1.children,
+ {"dist": self.node1.dist},
+ ]
# node2 has new root parent, same children + mods, and dist/supp mods
self.tdict[self.node2] = [
self.nnode,
self.node2.children,
{"dist": 0.0},
]
-
-
def get_features(self):
- # define which features to use/keep on nodes and which are "edge"
+ # define which features to use/keep on nodes and which are "edge"
# features which must be redirected on rooting.
testnode = self.tree.treenode.get_leaves()[0]
extrafeat = {i for i in testnode.features if i not in self.features}
self.features.update(extrafeat)
-
-
def get_match(self, names, wildcard, regex):
"""
tries to get monophyletic clade from selection, then tests
the reciprocal set, then reports error.
"""
@@ -276,20 +261,18 @@
self.nas = NodeAssist(self.tree, names, wildcard, regex)
# self.nas.match_query()
self.tipnames = self.nas.get_tipnames()
# check for reciprocal match
- x0 = (not self.nas.is_query_monophyletic())
- x1 = (self.nas.get_mrca().is_root())
+ x0 = not self.nas.is_query_monophyletic()
+ x1 = self.nas.get_mrca().is_root()
if x0 or x1:
clade1 = self.nas.tipnames
self.nas.match_reciprocal()
# check reciprocal match
if not self.nas.is_query_monophyletic():
# clade2 = self.nas.tipnames
# reports the smaller sized clade
- raise ToytreeError(
- "Matched query is paraphyletic: {}".format(clade1)
- )
+ raise ToytreeError("Matched query is paraphyletic: {}".format(clade1))
# .format(sorted([clade1, clade2], key=len)[0]))
<b>would reformat toytree/toytree/Rooter.py</b>
<b>All done! ✨ 🍰 ✨</b>
<b>1 file would be reformatted</b>.
Assessment¶
None. Continue to the next tutorials where we will continue to learn about style.
Cleanup¶
Now that you've finished the exercise you can cleanup by removing the repos
that we had cloned only for this example. Remember, to remove a git repo you
will need to use the arguments -rf
; this is because the .git/ subfolder
in the repo wants you to be sure you really want to remove it before you do
so. Here we are quite sure, so go ahead and run the commands below to remove
both folders.
# move into your hacks directory
cd ~/hacks
# rm the two repos
rm -rf ./toytree
rm -rf ./fastStructure