Skip to content

Commit

Permalink
Merge pull request #120 from sciris/prepr-update
Browse files Browse the repository at this point in the history
Fix prepr() for __slots__
  • Loading branch information
cliffckerr committed Aug 12, 2020
2 parents cd9969f + b7741a1 commit bf09f18
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 27 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.

By import convention, components of the Sciris library are listed beginning with `sc.`, e.g. `sc.odict()`.


## Version 0.17.4 (2020-08-11)
1. `sc.profile()` and `sc.mprofile()` now return the line profiler instance for later use (e.g., to extract additional statistics).
1. `sc.prepr()` (also used in `sc.prettyobj()`) can now support objects with slots instead of dicts.

## Version 0.17.3 (2020-07-21)
1. `sc.parallelize()` now explicitly deep-copies objects, since on some platforms this copying does not take place as part of the parallelization process.

Expand Down
50 changes: 30 additions & 20 deletions sciris/sc_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,18 @@ def prepr(obj, maxlen=None, maxitems=None, skip=None, dividerchar='—', divider
# Initialize things to print out
labels = []
values = []
if hasattr(obj, '__dict__'):
if len(obj.__dict__):
labels = sorted(set(obj.__dict__.keys()) - set(skip)) # Get the attribute keys

if not (hasattr(obj, '__dict__') or hasattr(obj, '__slots__')):
# It's a plain object
labels = ['%s' % type(obj)]
values = [flexstr(obj)]
else:
if hasattr(obj, '__dict__'):
labels = sorted(set(obj.__dict__.keys()) - set(skip)) # Get the dict attribute keys
else:
labels = sorted(set(obj.__slots__) - set(skip)) # Get the slots attribute keys

if len(labels):
extraitems = len(labels) - maxitems
if extraitems>0:
labels = labels[:maxitems]
Expand All @@ -464,9 +473,6 @@ def prepr(obj, maxlen=None, maxitems=None, skip=None, dividerchar='—', divider
if extraitems > 0:
labels.append('etc.')
values.append(f'{extraitems} entries not shown')
else: # If it's not an object, just get its representation
labels = ['%s' % type(obj)]
values = [flexstr(obj)]

# Decide how to print them
maxkeylen = 0
Expand Down Expand Up @@ -1828,17 +1834,18 @@ def suggest(user_input, valid_inputs, n=1, threshold=4, fulloutput=False, die=Fa
return suggestions[:n]


def profile(run, follow=None, *args, **kwargs):
def profile(run, follow=None, print_stats=True, *args, **kwargs):
'''
Profile the line-by-line time required by a function.
Args:
run (function): The function to be run
follow (function): The function or list of functions to be followed in the profiler; if None, defaults to the run function
print_stats (bool): whether to print the statistics of the profile to stdout
args, kwargs: Passed to the function to be run
Returns:
None (the profile output is printed to stdout)
LineProfiler (by default, the profile output is also printed to stdout)
Example
-------
Expand Down Expand Up @@ -1891,27 +1898,29 @@ def inner(self):
lp.enable_by_count()
wrapper = lp(run)

print('Profiling...')
if print_stats:
print('Profiling...')
wrapper(*args, **kwargs)
lp.print_stats()
run = orig_func
print('Done.')

return
if print_stats:
lp.print_stats()
print('Done.')
return lp


def mprofile(run, follow=None, *args, **kwargs):
def mprofile(run, follow=None, show_results=True, *args, **kwargs):
'''
Profile the line-by-line memory required by a function. See profile() for a
usage example.
Args:
run (function): The function to be run
follow (function): The function or list of functions to be followed in the profiler; if None, defaults to the run function
show_results (bool): whether to print the statistics of the profile to stdout
args, kwargs: Passed to the function to be run
Returns:
None (the profile output is printed to stdout)
LineProfiler (by default, the profile output is also printed to stdout)
'''

try:
Expand All @@ -1932,12 +1941,13 @@ def mprofile(run, follow=None, *args, **kwargs):
except TypeError as e:
raise TypeError('Function wrapping failed; are you profiling an already-profiled function?') from e

print('Profiling...')
if show_results:
print('Profiling...')
wrapper(*args, **kwargs)
mp.show_results(lp)
print('Done.')

return
if show_results:
mp.show_results(lp)
print('Done.')
return lp



Expand Down
4 changes: 2 additions & 2 deletions sciris/sc_version.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__all__ = ['__version__', '__versiondate__', '__license__']

__version__ = '0.17.3'
__versiondate__ = '2020-07-21'
__version__ = '0.17.4'
__versiondate__ = '2020-08-11'
__license__ = 'Sciris %s (%s) -- (c) Sciris.org' % (__version__, __versiondate__)
24 changes: 19 additions & 5 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ def inner(self):
foo = Foo()
try:
sc.mprofile(big_fn) # NB, cannot re-profile the same function at the same time
except TypeError: # This happens when re-running this script
pass
except TypeError as E: # This happens when re-running this script
print(f'Unable to re-profile memory function; this is usually not cause for concern ({E})')
sc.profile(run=foo.outer, follow=[foo.outer, foo.inner])
sc.profile(slow_fn)
lp = sc.profile(slow_fn)

return foo
return lp


def test_prepr():
Expand All @@ -94,6 +94,19 @@ def test_prepr():
return myobj


def test_prepr_slots():
sc.heading('Test pretty representation of an object using slots')

class Foo():
__slots__ = ['bar']
def __init__(self):
self.bar = 1

x = Foo()
print(sc.prepr(x))
return x


def test_uuid():
sc.heading('Test UID generation')
import uuid
Expand Down Expand Up @@ -302,8 +315,9 @@ def test_progress_bar():

bluearray = test_colorize()
string = test_printing()
foo = test_profile()
lp = test_profile()
myobj = test_prepr()
myobj2 = test_prepr_slots()
uid = test_uuid()
plist = test_promotetolist()
dists = test_suggest()
Expand Down

0 comments on commit bf09f18

Please sign in to comment.