#!/usr/bin/env python
#
# This script will run on Python 2.7.x and later
#
# By default, iter_modules looks in sys.path which is initialized from
# the environment variable PYTHONPATH, plus an installation-dependent default.
from pkgutil import iter_modules
import sys
def parse_command_line():
import argparse # added to Python 2.7 to replace optparse
Its typically considered best to import all modules at the beginning. Avoid importing inside functions. Also, the python history lesson was pointless.
python_version = sys.version
Why are you doing this? Why not just use sys.version when you need this?
parser = argparse.ArgumentParser(description='Use this script to view the installed packages and/or modules for Python version: '+python_version)
parser.add_argument('--type',dest='list_type',type=str,default='packages',help='Can be \'packages\' (default), \'modules\', or \'both\'')
parser.add_argument('--ncols',type=int,default=2,help='Number of columns in output (default: 2)')
args = parser.parse_args()
return args
I'd combine these last two lines.
def select_packages():
return [ module[1:] for module in iter_modules() if module[2] ],'Packages'
def select_modules():
return [ module[1:] for module in iter_modules() if not module[2] ],'Modules'
def select_both():
return [ module[1:] for module in iter_modules() ],'Packages and Modules'
These three functions are awkward. For one, when looping over tuples, its generally best to unpack the tuples. This will make the code easier to follow. They are also largely duplicate of each other.
options = { 'packages' : select_packages,
'modules' : select_modules,
'both' : select_both,
}
The python standard is to use ALL_CAPS for global constants.
def get_printed_names(pack_tuple):
pack_tuple is a bad name, I don't know what its in it.
p_name = []
avoid abbreviations like p, it just makes it harder to see what's going on.
for code in pack_tuple:
if ( code[1] ):
You don't need the parens. It'd also be easier to follow if you used for name, ispkg in pack_tuple:
p_name.append(code[0]+' (Package)')
else:
p_name.append(code[0]+' (Module)')
return p_name
Why does this function operate on a list of packages?
def fmt_cols(my_list, cols):
my_list
is a bad name. I have no idea what's in the list.
import math
max_width = max(map(lambda x: len(x), my_list))
lamba x: len(x)
is just len(x)
. So you can do max(map(len, my_list))
col_length = int(math.ceil(len(my_list)/cols+1))
justify_list = list(map(lambda x: x.ljust(max_width), my_list))
In python 2.x, map
produces a list already. (Perhaps you're aiming to work on Python 3 as well?) It's also probably better to use a list comprehension.
lines = [ ' '.join(justify_list[i::col_length]) for i in range(col_length) ]
print("\n".join(lines))
There's not much point in putting everything in a line just to print it. I'd just print it directly in a for loop.
def sort_func(a_str):
tmp_str = a_str.lower() # fine with ASCII subset of characters
for index,letter in enumerate(a_str):
if letter == '_':
continue
else:
return tmp_str[index:]
It seems to me that you are just stripping off the _
so instead, you can use return a_str.lower().lstrip('_')
# main()
Normal style is to put this in a main function.
arguments = parse_command_line()
detail_level = arguments.list_type
try:
installed_code,tag = options[detail_level]()
except KeyError:
print('Error: detail_level argument must be \'packages\', \'modules\', or \'both\'')
sys.exit()
Why don't you have argument parser check this?
ncols = arguments.ncols
Why are you copying this in a local?
print('\nFor output options type \'python_pkginfo.py -h\'')
print('\nInstalled '+tag+' for Python version '+sys.version+':\n')
print('To see package versions try: pip freeze | sort')
print('after issuing the appropriate \"setpkgs -a python\" command\n')
print_list = get_printed_names(installed_code)
print_list.sort(key=sort_func)
fmt_cols(print_list,ncols)
My rewrite of your script:
#!/usr/bin/env python
#
# This script will run on Python 2.7.x and later
#
# By default, iter_modules looks in sys.path which is initialized from
# the environment variable PYTHONPATH, plus an installation-dependent default.
from pkgutil import iter_modules
import sys
import argparse
import math
def parse_command_line():
parser = argparse.ArgumentParser(description='Use this script to view the installed packages and/or modules for Python version: ' + sys.version)
parser.add_argument('--type',dest='list_type',type=str,default='packages',help='What to list', choices = ['packages', 'modules', 'both'])
parser.add_argument('--ncols',type=int,default=2,help='Number of columns in output (default: 2)')
args = parser.parse_args()
return args
LIST_TYPE = {
'packages': (True, False, 'Packages'),
'modules': (False, True, 'Modules'),
'both': (True, True, 'Packages and Modules')
}
def find_modules(include_packages, include_modules):
for module_loader, module_name, is_package in iter_modules():
if is_package and include_packages:
yield module_name, is_package
elif not is_package and include_modules:
yield module_name, is_package
def formatted_name(module):
name, is_package = module
if is_package:
return name + ' (Package)'
else:
return name + ' (Module)'
def print_cols(cells, cols):
max_width = max(map(len, cells))
rows = int(math.ceil(len(cells)/cols+1))
for row in range(rows):
print(' '.join(x.ljust(max_width) for x in cells[row::rows]))
def main():
arguments = parse_command_line()
include_packages, include_modules, tag = LIST_TYPE[arguments.list_type]
installed_code = sorted(
find_modules(include_packages, include_modules),
key = lambda module: module[0].lower().lstrip('_'))
print('\nFor output options type \'python_pkginfo.py -h\'')
print('\nInstalled '+tag+' for Python version '+sys.version+':\n')
print('To see package versions try: pip freeze | sort')
print('after issuing the appropriate \"setpkgs -a python\" command\n')
print_cols(list(map(formatted_name, installed_code)), arguments.ncols)
if __name__ == '__main__':
main()