Implementing a customized helpful hints function which includes docstring but much shorter than Python help()












6














When I show others how to use this module I have them make a PROJECT instance and then add other objects.



>>> from Abstracted import PROJECT

>>> project = PROJECT('bob', Y_factor=2, use_MQTT=True)


MQTT status: started
Yay, your new Project named "bob" has been created


They may do this from a terminal which does not show the docstring balloons the way IDLE and many other IDEs can. So instead I've found a way so that when they type the instance, a few lines only of helpful text appears. This is in contrast to help() which can generate dozens of lines and scroll their previous work right up off the top of the screen.



>>> project




PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)




>>> carrot = project.new_CARROT('carrotte')




a CARROT was added!




>>> onion = project.new_ONION('onionne', n_rings=42)




an ONION was added!


Type the object, and a few lines with a few "main methods" and helpful hints shows up. This is the functionality that I am asking about.



>>> carrot




CARROT("carrotte")
instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)

>>> onion




ONION("onionne"), n_rings=42
instantiate with o = ONION(project, name, n_rings=None)
increase onionrings with o.multiply(factor)


Question: Am I re-inventing the wheel? Is this recreating something that Python does naturally? I've only really learned about docstrings in the past 24 hours, so maybe there are better ways to do this?





Here is Abstracted.py, an abstracted version of a large module. All of the functionality I'm asking about is contained here.





import time, datetime, logging, socket, Queue
from threading import Thread, Lock, Event
#import paho.mqtt.client as mqtt

important_lock = Lock() # the rest of threading not shown

class PROJECT(object):
""" intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name, X) or p.new_ONION(name)"""
def __init__(self, name, Y_factor=None, use_MQTT=False):

self.name = name
self.use_MQTT = use_MQTT
self.things =
if Y_factor == None:
Y_factor = -1
self.Y_factor = Y_factor
if self.use_MQTT:
status = self.start_MQTT()
print "MQTT status: {}".format(status)
self.info = ('{self.__class__.__name__}("{self.name}")'
.format(self=self))
print 'Yay, your new Project named "{}" has been created'.format(self.name)

def __repr__(self):
return (self.info + 'n' + self.__doc__)

def doc(self):
print self.__doc__

def start_MQTT(self, start_looping=False):
"""(start_looping=False) creates an MQTT client attribute and connects it"""
# do stuff
status = 'started'
self.client = 'client instance'
return status

def stop_MQTT(self):
"""disconnects from MQTT"""
# do stuff
status = 'stopped'
return status

def new_ONION(self, name, n_rings=None):
"""def new_ONION"""

onion = ONION(project=self, name=name, n_rings=n_rings)

self.things.append(onion)
print "an ONION was added!"
return onion

def new_CARROT(self, name):
"""def new_CARROT"""

carrot = CARROT(project=self, name=name)

self.things.append(carrot)
print "a CARROT was added!"
return carrot

class THING(object):
"""THING!"""

def __init__(self, project, name):
"""THING.__init__!"""

self.project = project
self.name = name
self.Y_factor = self.project.Y_factor

def __repr__(self):
return (self.info + 'n' + self.__doc__)

class ONION(THING):
""" instantiate with o = ONION(project, name, n_rings=None)
increase onionrings with o.multiply(factor)"""
def __init__(self, project, name, n_rings=None):
"""ONION.__init__!"""
if n_rings==None:
n_rings = 7
self.n_rings = n_rings

THING.__init__(self, project, name)

self.info = ('{self.__class__.__name__}("{self.name}"), n_rings={self.n_rings}'
.format(self=self))

def multiply(self, factor):
if type(factor) in (int, float):
self.n_rings *= factor

class CARROT(THING):
""" instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)"""
def __init__(self, project, name):
"""CARROT.__init__!"""

self.thread_value = 1

THING.__init__(self, project, name)

self.info = ('{self.__class__.__name__}("{self.name}")'
.format(self=self))

def change_thread_value(self, new_value):
with important_lock:
self.thread_value = new_value









share|improve this question
























  • Good question! Would print(onion.__doc__) provide the desired behavior? What about inspect.getdoc()?
    – alecxe
    yesterday










  • @alecxe my power went off right after I posted, just got back on line... Those are particularly wordy for two-finger typers, and remembering that is a bit hard for python noobs, and by the time they finish typing it they may forget what it was they were hoping to do. I'm looking for a quick way, as easy as remembering help() but not actually help(). Thanks!
    – uhoh
    yesterday
















6














When I show others how to use this module I have them make a PROJECT instance and then add other objects.



>>> from Abstracted import PROJECT

>>> project = PROJECT('bob', Y_factor=2, use_MQTT=True)


MQTT status: started
Yay, your new Project named "bob" has been created


They may do this from a terminal which does not show the docstring balloons the way IDLE and many other IDEs can. So instead I've found a way so that when they type the instance, a few lines only of helpful text appears. This is in contrast to help() which can generate dozens of lines and scroll their previous work right up off the top of the screen.



>>> project




PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)




>>> carrot = project.new_CARROT('carrotte')




a CARROT was added!




>>> onion = project.new_ONION('onionne', n_rings=42)




an ONION was added!


Type the object, and a few lines with a few "main methods" and helpful hints shows up. This is the functionality that I am asking about.



>>> carrot




CARROT("carrotte")
instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)

>>> onion




ONION("onionne"), n_rings=42
instantiate with o = ONION(project, name, n_rings=None)
increase onionrings with o.multiply(factor)


Question: Am I re-inventing the wheel? Is this recreating something that Python does naturally? I've only really learned about docstrings in the past 24 hours, so maybe there are better ways to do this?





Here is Abstracted.py, an abstracted version of a large module. All of the functionality I'm asking about is contained here.





import time, datetime, logging, socket, Queue
from threading import Thread, Lock, Event
#import paho.mqtt.client as mqtt

important_lock = Lock() # the rest of threading not shown

class PROJECT(object):
""" intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name, X) or p.new_ONION(name)"""
def __init__(self, name, Y_factor=None, use_MQTT=False):

self.name = name
self.use_MQTT = use_MQTT
self.things =
if Y_factor == None:
Y_factor = -1
self.Y_factor = Y_factor
if self.use_MQTT:
status = self.start_MQTT()
print "MQTT status: {}".format(status)
self.info = ('{self.__class__.__name__}("{self.name}")'
.format(self=self))
print 'Yay, your new Project named "{}" has been created'.format(self.name)

def __repr__(self):
return (self.info + 'n' + self.__doc__)

def doc(self):
print self.__doc__

def start_MQTT(self, start_looping=False):
"""(start_looping=False) creates an MQTT client attribute and connects it"""
# do stuff
status = 'started'
self.client = 'client instance'
return status

def stop_MQTT(self):
"""disconnects from MQTT"""
# do stuff
status = 'stopped'
return status

def new_ONION(self, name, n_rings=None):
"""def new_ONION"""

onion = ONION(project=self, name=name, n_rings=n_rings)

self.things.append(onion)
print "an ONION was added!"
return onion

def new_CARROT(self, name):
"""def new_CARROT"""

carrot = CARROT(project=self, name=name)

self.things.append(carrot)
print "a CARROT was added!"
return carrot

class THING(object):
"""THING!"""

def __init__(self, project, name):
"""THING.__init__!"""

self.project = project
self.name = name
self.Y_factor = self.project.Y_factor

def __repr__(self):
return (self.info + 'n' + self.__doc__)

class ONION(THING):
""" instantiate with o = ONION(project, name, n_rings=None)
increase onionrings with o.multiply(factor)"""
def __init__(self, project, name, n_rings=None):
"""ONION.__init__!"""
if n_rings==None:
n_rings = 7
self.n_rings = n_rings

THING.__init__(self, project, name)

self.info = ('{self.__class__.__name__}("{self.name}"), n_rings={self.n_rings}'
.format(self=self))

def multiply(self, factor):
if type(factor) in (int, float):
self.n_rings *= factor

class CARROT(THING):
""" instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)"""
def __init__(self, project, name):
"""CARROT.__init__!"""

self.thread_value = 1

THING.__init__(self, project, name)

self.info = ('{self.__class__.__name__}("{self.name}")'
.format(self=self))

def change_thread_value(self, new_value):
with important_lock:
self.thread_value = new_value









share|improve this question
























  • Good question! Would print(onion.__doc__) provide the desired behavior? What about inspect.getdoc()?
    – alecxe
    yesterday










  • @alecxe my power went off right after I posted, just got back on line... Those are particularly wordy for two-finger typers, and remembering that is a bit hard for python noobs, and by the time they finish typing it they may forget what it was they were hoping to do. I'm looking for a quick way, as easy as remembering help() but not actually help(). Thanks!
    – uhoh
    yesterday














6












6








6







When I show others how to use this module I have them make a PROJECT instance and then add other objects.



>>> from Abstracted import PROJECT

>>> project = PROJECT('bob', Y_factor=2, use_MQTT=True)


MQTT status: started
Yay, your new Project named "bob" has been created


They may do this from a terminal which does not show the docstring balloons the way IDLE and many other IDEs can. So instead I've found a way so that when they type the instance, a few lines only of helpful text appears. This is in contrast to help() which can generate dozens of lines and scroll their previous work right up off the top of the screen.



>>> project




PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)




>>> carrot = project.new_CARROT('carrotte')




a CARROT was added!




>>> onion = project.new_ONION('onionne', n_rings=42)




an ONION was added!


Type the object, and a few lines with a few "main methods" and helpful hints shows up. This is the functionality that I am asking about.



>>> carrot




CARROT("carrotte")
instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)

>>> onion




ONION("onionne"), n_rings=42
instantiate with o = ONION(project, name, n_rings=None)
increase onionrings with o.multiply(factor)


Question: Am I re-inventing the wheel? Is this recreating something that Python does naturally? I've only really learned about docstrings in the past 24 hours, so maybe there are better ways to do this?





Here is Abstracted.py, an abstracted version of a large module. All of the functionality I'm asking about is contained here.





import time, datetime, logging, socket, Queue
from threading import Thread, Lock, Event
#import paho.mqtt.client as mqtt

important_lock = Lock() # the rest of threading not shown

class PROJECT(object):
""" intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name, X) or p.new_ONION(name)"""
def __init__(self, name, Y_factor=None, use_MQTT=False):

self.name = name
self.use_MQTT = use_MQTT
self.things =
if Y_factor == None:
Y_factor = -1
self.Y_factor = Y_factor
if self.use_MQTT:
status = self.start_MQTT()
print "MQTT status: {}".format(status)
self.info = ('{self.__class__.__name__}("{self.name}")'
.format(self=self))
print 'Yay, your new Project named "{}" has been created'.format(self.name)

def __repr__(self):
return (self.info + 'n' + self.__doc__)

def doc(self):
print self.__doc__

def start_MQTT(self, start_looping=False):
"""(start_looping=False) creates an MQTT client attribute and connects it"""
# do stuff
status = 'started'
self.client = 'client instance'
return status

def stop_MQTT(self):
"""disconnects from MQTT"""
# do stuff
status = 'stopped'
return status

def new_ONION(self, name, n_rings=None):
"""def new_ONION"""

onion = ONION(project=self, name=name, n_rings=n_rings)

self.things.append(onion)
print "an ONION was added!"
return onion

def new_CARROT(self, name):
"""def new_CARROT"""

carrot = CARROT(project=self, name=name)

self.things.append(carrot)
print "a CARROT was added!"
return carrot

class THING(object):
"""THING!"""

def __init__(self, project, name):
"""THING.__init__!"""

self.project = project
self.name = name
self.Y_factor = self.project.Y_factor

def __repr__(self):
return (self.info + 'n' + self.__doc__)

class ONION(THING):
""" instantiate with o = ONION(project, name, n_rings=None)
increase onionrings with o.multiply(factor)"""
def __init__(self, project, name, n_rings=None):
"""ONION.__init__!"""
if n_rings==None:
n_rings = 7
self.n_rings = n_rings

THING.__init__(self, project, name)

self.info = ('{self.__class__.__name__}("{self.name}"), n_rings={self.n_rings}'
.format(self=self))

def multiply(self, factor):
if type(factor) in (int, float):
self.n_rings *= factor

class CARROT(THING):
""" instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)"""
def __init__(self, project, name):
"""CARROT.__init__!"""

self.thread_value = 1

THING.__init__(self, project, name)

self.info = ('{self.__class__.__name__}("{self.name}")'
.format(self=self))

def change_thread_value(self, new_value):
with important_lock:
self.thread_value = new_value









share|improve this question















When I show others how to use this module I have them make a PROJECT instance and then add other objects.



>>> from Abstracted import PROJECT

>>> project = PROJECT('bob', Y_factor=2, use_MQTT=True)


MQTT status: started
Yay, your new Project named "bob" has been created


They may do this from a terminal which does not show the docstring balloons the way IDLE and many other IDEs can. So instead I've found a way so that when they type the instance, a few lines only of helpful text appears. This is in contrast to help() which can generate dozens of lines and scroll their previous work right up off the top of the screen.



>>> project




PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)




>>> carrot = project.new_CARROT('carrotte')




a CARROT was added!




>>> onion = project.new_ONION('onionne', n_rings=42)




an ONION was added!


Type the object, and a few lines with a few "main methods" and helpful hints shows up. This is the functionality that I am asking about.



>>> carrot




CARROT("carrotte")
instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)

>>> onion




ONION("onionne"), n_rings=42
instantiate with o = ONION(project, name, n_rings=None)
increase onionrings with o.multiply(factor)


Question: Am I re-inventing the wheel? Is this recreating something that Python does naturally? I've only really learned about docstrings in the past 24 hours, so maybe there are better ways to do this?





Here is Abstracted.py, an abstracted version of a large module. All of the functionality I'm asking about is contained here.





import time, datetime, logging, socket, Queue
from threading import Thread, Lock, Event
#import paho.mqtt.client as mqtt

important_lock = Lock() # the rest of threading not shown

class PROJECT(object):
""" intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name, X) or p.new_ONION(name)"""
def __init__(self, name, Y_factor=None, use_MQTT=False):

self.name = name
self.use_MQTT = use_MQTT
self.things =
if Y_factor == None:
Y_factor = -1
self.Y_factor = Y_factor
if self.use_MQTT:
status = self.start_MQTT()
print "MQTT status: {}".format(status)
self.info = ('{self.__class__.__name__}("{self.name}")'
.format(self=self))
print 'Yay, your new Project named "{}" has been created'.format(self.name)

def __repr__(self):
return (self.info + 'n' + self.__doc__)

def doc(self):
print self.__doc__

def start_MQTT(self, start_looping=False):
"""(start_looping=False) creates an MQTT client attribute and connects it"""
# do stuff
status = 'started'
self.client = 'client instance'
return status

def stop_MQTT(self):
"""disconnects from MQTT"""
# do stuff
status = 'stopped'
return status

def new_ONION(self, name, n_rings=None):
"""def new_ONION"""

onion = ONION(project=self, name=name, n_rings=n_rings)

self.things.append(onion)
print "an ONION was added!"
return onion

def new_CARROT(self, name):
"""def new_CARROT"""

carrot = CARROT(project=self, name=name)

self.things.append(carrot)
print "a CARROT was added!"
return carrot

class THING(object):
"""THING!"""

def __init__(self, project, name):
"""THING.__init__!"""

self.project = project
self.name = name
self.Y_factor = self.project.Y_factor

def __repr__(self):
return (self.info + 'n' + self.__doc__)

class ONION(THING):
""" instantiate with o = ONION(project, name, n_rings=None)
increase onionrings with o.multiply(factor)"""
def __init__(self, project, name, n_rings=None):
"""ONION.__init__!"""
if n_rings==None:
n_rings = 7
self.n_rings = n_rings

THING.__init__(self, project, name)

self.info = ('{self.__class__.__name__}("{self.name}"), n_rings={self.n_rings}'
.format(self=self))

def multiply(self, factor):
if type(factor) in (int, float):
self.n_rings *= factor

class CARROT(THING):
""" instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)"""
def __init__(self, project, name):
"""CARROT.__init__!"""

self.thread_value = 1

THING.__init__(self, project, name)

self.info = ('{self.__class__.__name__}("{self.name}")'
.format(self=self))

def change_thread_value(self, new_value):
with important_lock:
self.thread_value = new_value






python python-2.x user-interface






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited yesterday









Jamal

30.3k11116226




30.3k11116226










asked yesterday









uhoh

1757




1757












  • Good question! Would print(onion.__doc__) provide the desired behavior? What about inspect.getdoc()?
    – alecxe
    yesterday










  • @alecxe my power went off right after I posted, just got back on line... Those are particularly wordy for two-finger typers, and remembering that is a bit hard for python noobs, and by the time they finish typing it they may forget what it was they were hoping to do. I'm looking for a quick way, as easy as remembering help() but not actually help(). Thanks!
    – uhoh
    yesterday


















  • Good question! Would print(onion.__doc__) provide the desired behavior? What about inspect.getdoc()?
    – alecxe
    yesterday










  • @alecxe my power went off right after I posted, just got back on line... Those are particularly wordy for two-finger typers, and remembering that is a bit hard for python noobs, and by the time they finish typing it they may forget what it was they were hoping to do. I'm looking for a quick way, as easy as remembering help() but not actually help(). Thanks!
    – uhoh
    yesterday
















Good question! Would print(onion.__doc__) provide the desired behavior? What about inspect.getdoc()?
– alecxe
yesterday




Good question! Would print(onion.__doc__) provide the desired behavior? What about inspect.getdoc()?
– alecxe
yesterday












@alecxe my power went off right after I posted, just got back on line... Those are particularly wordy for two-finger typers, and remembering that is a bit hard for python noobs, and by the time they finish typing it they may forget what it was they were hoping to do. I'm looking for a quick way, as easy as remembering help() but not actually help(). Thanks!
– uhoh
yesterday




@alecxe my power went off right after I posted, just got back on line... Those are particularly wordy for two-finger typers, and remembering that is a bit hard for python noobs, and by the time they finish typing it they may forget what it was they were hoping to do. I'm looking for a quick way, as easy as remembering help() but not actually help(). Thanks!
– uhoh
yesterday










3 Answers
3






active

oldest

votes


















6














I'm not sure whether you're reinventing the wheel, but you are using the wrong tools for the job.



I've been through the phase where I think I'm doing something unique enough that I need to do something nonstandard. Every time I've done it, I inevitably regret it. Don't do it. It's not a good idea. Use tools very closely to the way they were intended to be used.



You seem to be trying to do basically two things.



A guidance system to aid developers



The first is a sort of "brief help" that prompts the users with details about the API.



You implement this with repr, but repr is absolutely the wrong tool for this job.
repr is not intended to return help messages. repr is intended to be a quick and dirty introspection tool, showing just enough information for the programmer to identify the object they're looking at. You're reducing their ability to do so by misusing it, which is far and away something I would not advise for beginners.



To be honest, I wouldn't recommend building this into Python itself at all. Doing so is very nonstandard. Instead, I'd be more inclined to generate a set of HTML docs for them to use. pydoc is built in and is probably a good starting point. Sphinx is also well known and used. Python's wiki lists others to consider as well. Some of these will allow you to include documentation not just of the API, but also custom pages including examples and what have you. A set of HTML docs is likely to be infinitely more usable to your users since it doesn't require backspacing the command they're in the middle of typing.



Bottom line: I'd use the existing standard tools to provide your users with this kind of guidance, and the standard is to make it external to the code itself.



You might also want to take note of the dir command. dir(o) lists the names bound to o, which includes attributes, descriptors (similar to "properties" in other languages), and methods. This can be combined with pprint or pformat for easier to read output, and the if clause of list comprehensions can be used to filter out private names (i.e., [a for a in dir(o) if not a.startswith('_')]) or based on other conditions. I use this myself to explore an API when the documentation is poor. vars(o) may also be useful for viewing attributes.



If it absolutely must be accessible by code, I think I'd probably implement it as a new "protocol". Something like this:



class PROJECT(object):
# Your other code
@staticmethod
def _mytips():
return """
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)
"""


Then in another module, you can leverage this _mytips() method:



import os

def tips(o):
try:
raw_tips = o._mytips()
# Use standardized indentation
return (os.linesep + ' ').join([l.strip() for l in raw_tips.strip().splitlines()])
except AttributeError:
raise ValueError('object does not support tips')


Then it can be used like this:



>>> tips(PROJECT)
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)
>>> p = PROJECT()
>>> tips(p)
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)


Calling the method _mytips gives the tips call a standard method to look for, and prefixing it with an underscore indicates it's not intended to be called directly. Note that you should not use something like __mytips__, as the magic method syntax is reserved for the language or the standard library. Making it static means you can use it on other the class directly or an instance. You still have to write the documentation yourself or generate it somehow. This is not particularly common, but at least this is in line with the standard "protocol pattern" Python uses for things that need to be implemented by lots of different classes/objects. Other devs will recognize what you're doing when they look at the tips code.



I'd still prefer an actual help page open in the browser, though, and don't really recommend it. You might consider loading the tips from an actual external document, giving you both with the same content.



Informational messages



The other thing you seem to want to implement is informational messages. The standard solution for this is logging. That is definitely what you're doing when you print the "added" messages. Using the logging module in those cases would look like this:



def new_carrot(self, name):
"""def new_carrot"""

carrot = Carrot(project=self, name=name)

self.things.append(carrot)
logging.debug("a Carrot was added!")
return carrot


But requires a tiny bit of set up:



import logging

logging.basicConfig(level=logging.DEBUG)


You can get fancier if you want more control over the format, but I'm not going to get into all those details here.



This also gives your users the option of disabling these messages by cranking up the message level:



logging.getLogger().setLevel(logging.INFO)


Other concerns



Resource management



I notice your PROJECT class starts up something in its initializer:



status = self.start_MQTT()


and then has a stop method as well.



The Python standard for managing resources is context managers. Context managers are a fantastic little tool that will make your code much simpler. If you convert PROJECT to one, then callers will have much less boilerplate managing it:



with PROJECT("myproj", use_MQTT=True) as p:
p.new_CARROT("carrot 1")


Yes, that block of code is correct. There is no need to call stop_MQTT, and that's because the with block invokes the method to release it automatically:



class PROJECT(object):
def __init__(self, name, Y_factor=None, use_MQTT=False):
# Everything EXCEPT starting the server

def __enter__(self):
if self.use_MQTT:
# do stuff
status = 'started'
self.client = 'client instance'
logging.debug("MQTT status: {}".format(status))

def __exit__(self, type, value, traceback):
if self.status == 'started':
# do stuff to stop server
status = 'stopped'
return status

# Other methods


Some details may not exactly line up with what you need to do, but you have pseudocode here anyway. Much simpler for your callers.



Use super



You have this call:



THING.__init__(self, project, name)


Don't do that. Use super:



super(ONION, self).__init__(project, name)


Naming and formatting standards



Please read PEP8 for naming conventions. Right off the bat, I notice that PROJECT would be Project and Y_factor should be yfactor. (One might argue that y_factor is also acceptable, but PEP8 notes that underscores should be used "to improve readability". Putting it after just a y doesn't really add any readability.)



Conclusion



The bottom line here is that Python has a lot of norms and standards, and your code doesn't conform to them. This isn't just a matter of being pedantic. Python's norms are built around practical concerns and avoiding real world problems. When you use Python the way it was intended to be used, it will make solving your problems easier.



If you want a sort of intro to the idea of writing "Pythonic" code and why it's important and a practical example, check out Raymond Hettinger's Beyond PEP8.






share|improve this answer



















  • 1




    +n! This is an excellent answer and what I needed, thank you! You are right about logger, elsewhere I've echoed the logged events to the screen with a second handler using logging.getLogger('').addHandler(console) and that may be what I need here. I've used context manager for the threading lock (and files) but never realized it could be used this way, nice! I have the manual stop method for other reasons (example: if you turn off WiFi to save batteries, then MQTT's looping/pinging starts complaining) but for final shut down, what you've shown is great.
    – uhoh
    yesterday












  • Super looks super, I will dig in and read about that now. Again, thank you for taking the time for a thorough review!
    – uhoh
    yesterday






  • 1




    @uhoh I added some notes about dir and vars you may find useful.
    – jpmc26
    7 hours ago










  • yep, thanks. Maybe what I'm really looking for is something that could look like and work like a Built-in function, and be called pretty_help(class), concise_help(class) or even local_hints(class). Something that returns a few concise lines of main methods, hints, or other things that are what users are most likely to need, and because of compactness still leave your current train of thought on the screen, even if the screen is small. I'm a heavy python user but will never be, or think like you steel-trap-minded developers, and there are a lot of us who could use some simple hints.
    – uhoh
    4 hours ago






  • 1




    @uhoh I'm not really opposed to the idea; I can see the usefulness.Jupyter/IPython do something similar, after all. It's just that such a thing does not exist out of the box, and hijacking functionality that's supposed to do something else will hurt you more that it will help in the long run. I read documentation all day long, and I find myself using help pages way more than any kind of command line help. That said, I've made one more addition: designing your own protocol method. It's not my preference and I don't really encourage it, but at least it doesn't conflict with existing things.
    – jpmc26
    2 hours ago





















8














Disclaimer



Now that the OP has clarified that they intend to use this as a teaching tool, my answer has lost relevance for their use-case. It can still be taken as general advice for most Python modules.



I do agree with @jpmc26 to a certain extent, specifically that this code itself as currently written should not be used as an example for new learners of Python. I am certainly not advocating teaching students bad Python style. But I think that this could be a useful learning aid within the context the OP provides.



Original answer



I realize this is one of the central premises of your program, but printing help docstrings during normal use of the program is highly unusual behavior and likely to be annoying to users of your program. For example, a user might already know how to use your API, and doesn't need to be told how every time they invoke it; it would just clutter up the command line. Besides, you're solving a problem that doesn't really need to be solved: a class docstring may scroll the screen as described, but function and method docstrings do not, and the Python REPL interpreter already allows you to short circuit the scrolling.



Instead, I would recommend using more descriptive and helpful docstrings. For example, informing the user of the class calling conventions in isolation is not useful; explaining what the class's intended use is, and what the various parameters mean would be more useful. PEP 257 provides guidance on how to write good docstrings.






share|improve this answer























  • The first sentence of my question makes it clear that this is an exercise that I am leading them through, rather than a module for general release. In this case Python is a tool, but not the topic. Getting short, helpful docstrings will not be annoying to them, but instead helpful. I would have mentioned this right away, but my power went out right after posting (I usually babysit my questions). In any event, I'll take your answer to be that you don't feel there is a better way to do this (type an object and receive hints how to use it).
    – uhoh
    yesterday












  • @uhoh My answer was premised on the assumption that this was code intended to be a general Python module for distribution, not a teaching tool. I read the first sentence, but I was genuinely confused as to who the intended audience was. Perhaps adding a sentence or two that provides the additional context, and editing your question title would help you receive answers that are closer to what you're looking for; my answer is by no means a comprehensive analysis of your code.
    – Graham
    yesterday










  • It's unfortunate the power went out and I couldn't stay with my post. I've just gotten home now after spending the night in a McDonalds writing with a pen in a notebook (it's been a while, surprised I remembered how!). I'll update the question in a few hours. Until then let's assume that the behavior indicated really is the behavior needed, and I'm checking if there is a simpler way to do this, rather than asking "is this a good idea". Thanks!
    – uhoh
    yesterday










  • "Now that the OP has clarified that they intend to use this as a teaching tool" NO! It is vital that education conforms to the norms of the language and teaches students to adhere to them. The students will be far better served by the teacher using the correct, existing technology for the task they are trying to perform.
    – jpmc26
    yesterday












  • @jpmc26 I think it depends on what level of education the students are at, and the general context. If the OP wants to show their students how to instantiate objects and how objects work in Python, then this might be a useful tool. But, there are probably better tools. And I do agree that if this code is used, the underlying code should not be shown to students as an example.
    – Graham
    yesterday



















3














It is difficult to add anything to the existing great and detailed answers, but, to follow up your comment that .__doc__ might be to difficult to remember for newcomers, you could then wrap it around your custom function:



def doc(obj):
"""Prints a docstring of a given object."""
print(obj.__doc__)


Usage:



In [2]: class CARROT:
...: """ instantiate with c = CARROT(project, name)
...: change thread with c.change_thread_value(new_value)"""
...:

In [3]: carrot = CARROT()

In [4]: doc(carrot)
instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)




Jupyter Notebooks?



As a side note, you may switch to using Jupyter notebooks that may actually be a good learning environment for newcomers and would let them use the standard built-in help() in the Jupyter cells.



There are also built-in shortcuts, like shift+TAB to access a help popup:



enter image description here






share|improve this answer





















  • I'd forgotten about Jupyter, thanks! You might want to add a "me too" answer there (or not). I won't have control over what people use, and this question is for those who live 100% inside a terminal. "They may do this from a terminal which does not show the docstring balloons the way IDLE and many other IDEs can. So instead I've found a way..."
    – uhoh
    yesterday











Your Answer





StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");

StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210746%2fimplementing-a-customized-helpful-hints-function-which-includes-docstring-but-mu%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























3 Answers
3






active

oldest

votes








3 Answers
3






active

oldest

votes









active

oldest

votes






active

oldest

votes









6














I'm not sure whether you're reinventing the wheel, but you are using the wrong tools for the job.



I've been through the phase where I think I'm doing something unique enough that I need to do something nonstandard. Every time I've done it, I inevitably regret it. Don't do it. It's not a good idea. Use tools very closely to the way they were intended to be used.



You seem to be trying to do basically two things.



A guidance system to aid developers



The first is a sort of "brief help" that prompts the users with details about the API.



You implement this with repr, but repr is absolutely the wrong tool for this job.
repr is not intended to return help messages. repr is intended to be a quick and dirty introspection tool, showing just enough information for the programmer to identify the object they're looking at. You're reducing their ability to do so by misusing it, which is far and away something I would not advise for beginners.



To be honest, I wouldn't recommend building this into Python itself at all. Doing so is very nonstandard. Instead, I'd be more inclined to generate a set of HTML docs for them to use. pydoc is built in and is probably a good starting point. Sphinx is also well known and used. Python's wiki lists others to consider as well. Some of these will allow you to include documentation not just of the API, but also custom pages including examples and what have you. A set of HTML docs is likely to be infinitely more usable to your users since it doesn't require backspacing the command they're in the middle of typing.



Bottom line: I'd use the existing standard tools to provide your users with this kind of guidance, and the standard is to make it external to the code itself.



You might also want to take note of the dir command. dir(o) lists the names bound to o, which includes attributes, descriptors (similar to "properties" in other languages), and methods. This can be combined with pprint or pformat for easier to read output, and the if clause of list comprehensions can be used to filter out private names (i.e., [a for a in dir(o) if not a.startswith('_')]) or based on other conditions. I use this myself to explore an API when the documentation is poor. vars(o) may also be useful for viewing attributes.



If it absolutely must be accessible by code, I think I'd probably implement it as a new "protocol". Something like this:



class PROJECT(object):
# Your other code
@staticmethod
def _mytips():
return """
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)
"""


Then in another module, you can leverage this _mytips() method:



import os

def tips(o):
try:
raw_tips = o._mytips()
# Use standardized indentation
return (os.linesep + ' ').join([l.strip() for l in raw_tips.strip().splitlines()])
except AttributeError:
raise ValueError('object does not support tips')


Then it can be used like this:



>>> tips(PROJECT)
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)
>>> p = PROJECT()
>>> tips(p)
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)


Calling the method _mytips gives the tips call a standard method to look for, and prefixing it with an underscore indicates it's not intended to be called directly. Note that you should not use something like __mytips__, as the magic method syntax is reserved for the language or the standard library. Making it static means you can use it on other the class directly or an instance. You still have to write the documentation yourself or generate it somehow. This is not particularly common, but at least this is in line with the standard "protocol pattern" Python uses for things that need to be implemented by lots of different classes/objects. Other devs will recognize what you're doing when they look at the tips code.



I'd still prefer an actual help page open in the browser, though, and don't really recommend it. You might consider loading the tips from an actual external document, giving you both with the same content.



Informational messages



The other thing you seem to want to implement is informational messages. The standard solution for this is logging. That is definitely what you're doing when you print the "added" messages. Using the logging module in those cases would look like this:



def new_carrot(self, name):
"""def new_carrot"""

carrot = Carrot(project=self, name=name)

self.things.append(carrot)
logging.debug("a Carrot was added!")
return carrot


But requires a tiny bit of set up:



import logging

logging.basicConfig(level=logging.DEBUG)


You can get fancier if you want more control over the format, but I'm not going to get into all those details here.



This also gives your users the option of disabling these messages by cranking up the message level:



logging.getLogger().setLevel(logging.INFO)


Other concerns



Resource management



I notice your PROJECT class starts up something in its initializer:



status = self.start_MQTT()


and then has a stop method as well.



The Python standard for managing resources is context managers. Context managers are a fantastic little tool that will make your code much simpler. If you convert PROJECT to one, then callers will have much less boilerplate managing it:



with PROJECT("myproj", use_MQTT=True) as p:
p.new_CARROT("carrot 1")


Yes, that block of code is correct. There is no need to call stop_MQTT, and that's because the with block invokes the method to release it automatically:



class PROJECT(object):
def __init__(self, name, Y_factor=None, use_MQTT=False):
# Everything EXCEPT starting the server

def __enter__(self):
if self.use_MQTT:
# do stuff
status = 'started'
self.client = 'client instance'
logging.debug("MQTT status: {}".format(status))

def __exit__(self, type, value, traceback):
if self.status == 'started':
# do stuff to stop server
status = 'stopped'
return status

# Other methods


Some details may not exactly line up with what you need to do, but you have pseudocode here anyway. Much simpler for your callers.



Use super



You have this call:



THING.__init__(self, project, name)


Don't do that. Use super:



super(ONION, self).__init__(project, name)


Naming and formatting standards



Please read PEP8 for naming conventions. Right off the bat, I notice that PROJECT would be Project and Y_factor should be yfactor. (One might argue that y_factor is also acceptable, but PEP8 notes that underscores should be used "to improve readability". Putting it after just a y doesn't really add any readability.)



Conclusion



The bottom line here is that Python has a lot of norms and standards, and your code doesn't conform to them. This isn't just a matter of being pedantic. Python's norms are built around practical concerns and avoiding real world problems. When you use Python the way it was intended to be used, it will make solving your problems easier.



If you want a sort of intro to the idea of writing "Pythonic" code and why it's important and a practical example, check out Raymond Hettinger's Beyond PEP8.






share|improve this answer



















  • 1




    +n! This is an excellent answer and what I needed, thank you! You are right about logger, elsewhere I've echoed the logged events to the screen with a second handler using logging.getLogger('').addHandler(console) and that may be what I need here. I've used context manager for the threading lock (and files) but never realized it could be used this way, nice! I have the manual stop method for other reasons (example: if you turn off WiFi to save batteries, then MQTT's looping/pinging starts complaining) but for final shut down, what you've shown is great.
    – uhoh
    yesterday












  • Super looks super, I will dig in and read about that now. Again, thank you for taking the time for a thorough review!
    – uhoh
    yesterday






  • 1




    @uhoh I added some notes about dir and vars you may find useful.
    – jpmc26
    7 hours ago










  • yep, thanks. Maybe what I'm really looking for is something that could look like and work like a Built-in function, and be called pretty_help(class), concise_help(class) or even local_hints(class). Something that returns a few concise lines of main methods, hints, or other things that are what users are most likely to need, and because of compactness still leave your current train of thought on the screen, even if the screen is small. I'm a heavy python user but will never be, or think like you steel-trap-minded developers, and there are a lot of us who could use some simple hints.
    – uhoh
    4 hours ago






  • 1




    @uhoh I'm not really opposed to the idea; I can see the usefulness.Jupyter/IPython do something similar, after all. It's just that such a thing does not exist out of the box, and hijacking functionality that's supposed to do something else will hurt you more that it will help in the long run. I read documentation all day long, and I find myself using help pages way more than any kind of command line help. That said, I've made one more addition: designing your own protocol method. It's not my preference and I don't really encourage it, but at least it doesn't conflict with existing things.
    – jpmc26
    2 hours ago


















6














I'm not sure whether you're reinventing the wheel, but you are using the wrong tools for the job.



I've been through the phase where I think I'm doing something unique enough that I need to do something nonstandard. Every time I've done it, I inevitably regret it. Don't do it. It's not a good idea. Use tools very closely to the way they were intended to be used.



You seem to be trying to do basically two things.



A guidance system to aid developers



The first is a sort of "brief help" that prompts the users with details about the API.



You implement this with repr, but repr is absolutely the wrong tool for this job.
repr is not intended to return help messages. repr is intended to be a quick and dirty introspection tool, showing just enough information for the programmer to identify the object they're looking at. You're reducing their ability to do so by misusing it, which is far and away something I would not advise for beginners.



To be honest, I wouldn't recommend building this into Python itself at all. Doing so is very nonstandard. Instead, I'd be more inclined to generate a set of HTML docs for them to use. pydoc is built in and is probably a good starting point. Sphinx is also well known and used. Python's wiki lists others to consider as well. Some of these will allow you to include documentation not just of the API, but also custom pages including examples and what have you. A set of HTML docs is likely to be infinitely more usable to your users since it doesn't require backspacing the command they're in the middle of typing.



Bottom line: I'd use the existing standard tools to provide your users with this kind of guidance, and the standard is to make it external to the code itself.



You might also want to take note of the dir command. dir(o) lists the names bound to o, which includes attributes, descriptors (similar to "properties" in other languages), and methods. This can be combined with pprint or pformat for easier to read output, and the if clause of list comprehensions can be used to filter out private names (i.e., [a for a in dir(o) if not a.startswith('_')]) or based on other conditions. I use this myself to explore an API when the documentation is poor. vars(o) may also be useful for viewing attributes.



If it absolutely must be accessible by code, I think I'd probably implement it as a new "protocol". Something like this:



class PROJECT(object):
# Your other code
@staticmethod
def _mytips():
return """
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)
"""


Then in another module, you can leverage this _mytips() method:



import os

def tips(o):
try:
raw_tips = o._mytips()
# Use standardized indentation
return (os.linesep + ' ').join([l.strip() for l in raw_tips.strip().splitlines()])
except AttributeError:
raise ValueError('object does not support tips')


Then it can be used like this:



>>> tips(PROJECT)
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)
>>> p = PROJECT()
>>> tips(p)
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)


Calling the method _mytips gives the tips call a standard method to look for, and prefixing it with an underscore indicates it's not intended to be called directly. Note that you should not use something like __mytips__, as the magic method syntax is reserved for the language or the standard library. Making it static means you can use it on other the class directly or an instance. You still have to write the documentation yourself or generate it somehow. This is not particularly common, but at least this is in line with the standard "protocol pattern" Python uses for things that need to be implemented by lots of different classes/objects. Other devs will recognize what you're doing when they look at the tips code.



I'd still prefer an actual help page open in the browser, though, and don't really recommend it. You might consider loading the tips from an actual external document, giving you both with the same content.



Informational messages



The other thing you seem to want to implement is informational messages. The standard solution for this is logging. That is definitely what you're doing when you print the "added" messages. Using the logging module in those cases would look like this:



def new_carrot(self, name):
"""def new_carrot"""

carrot = Carrot(project=self, name=name)

self.things.append(carrot)
logging.debug("a Carrot was added!")
return carrot


But requires a tiny bit of set up:



import logging

logging.basicConfig(level=logging.DEBUG)


You can get fancier if you want more control over the format, but I'm not going to get into all those details here.



This also gives your users the option of disabling these messages by cranking up the message level:



logging.getLogger().setLevel(logging.INFO)


Other concerns



Resource management



I notice your PROJECT class starts up something in its initializer:



status = self.start_MQTT()


and then has a stop method as well.



The Python standard for managing resources is context managers. Context managers are a fantastic little tool that will make your code much simpler. If you convert PROJECT to one, then callers will have much less boilerplate managing it:



with PROJECT("myproj", use_MQTT=True) as p:
p.new_CARROT("carrot 1")


Yes, that block of code is correct. There is no need to call stop_MQTT, and that's because the with block invokes the method to release it automatically:



class PROJECT(object):
def __init__(self, name, Y_factor=None, use_MQTT=False):
# Everything EXCEPT starting the server

def __enter__(self):
if self.use_MQTT:
# do stuff
status = 'started'
self.client = 'client instance'
logging.debug("MQTT status: {}".format(status))

def __exit__(self, type, value, traceback):
if self.status == 'started':
# do stuff to stop server
status = 'stopped'
return status

# Other methods


Some details may not exactly line up with what you need to do, but you have pseudocode here anyway. Much simpler for your callers.



Use super



You have this call:



THING.__init__(self, project, name)


Don't do that. Use super:



super(ONION, self).__init__(project, name)


Naming and formatting standards



Please read PEP8 for naming conventions. Right off the bat, I notice that PROJECT would be Project and Y_factor should be yfactor. (One might argue that y_factor is also acceptable, but PEP8 notes that underscores should be used "to improve readability". Putting it after just a y doesn't really add any readability.)



Conclusion



The bottom line here is that Python has a lot of norms and standards, and your code doesn't conform to them. This isn't just a matter of being pedantic. Python's norms are built around practical concerns and avoiding real world problems. When you use Python the way it was intended to be used, it will make solving your problems easier.



If you want a sort of intro to the idea of writing "Pythonic" code and why it's important and a practical example, check out Raymond Hettinger's Beyond PEP8.






share|improve this answer



















  • 1




    +n! This is an excellent answer and what I needed, thank you! You are right about logger, elsewhere I've echoed the logged events to the screen with a second handler using logging.getLogger('').addHandler(console) and that may be what I need here. I've used context manager for the threading lock (and files) but never realized it could be used this way, nice! I have the manual stop method for other reasons (example: if you turn off WiFi to save batteries, then MQTT's looping/pinging starts complaining) but for final shut down, what you've shown is great.
    – uhoh
    yesterday












  • Super looks super, I will dig in and read about that now. Again, thank you for taking the time for a thorough review!
    – uhoh
    yesterday






  • 1




    @uhoh I added some notes about dir and vars you may find useful.
    – jpmc26
    7 hours ago










  • yep, thanks. Maybe what I'm really looking for is something that could look like and work like a Built-in function, and be called pretty_help(class), concise_help(class) or even local_hints(class). Something that returns a few concise lines of main methods, hints, or other things that are what users are most likely to need, and because of compactness still leave your current train of thought on the screen, even if the screen is small. I'm a heavy python user but will never be, or think like you steel-trap-minded developers, and there are a lot of us who could use some simple hints.
    – uhoh
    4 hours ago






  • 1




    @uhoh I'm not really opposed to the idea; I can see the usefulness.Jupyter/IPython do something similar, after all. It's just that such a thing does not exist out of the box, and hijacking functionality that's supposed to do something else will hurt you more that it will help in the long run. I read documentation all day long, and I find myself using help pages way more than any kind of command line help. That said, I've made one more addition: designing your own protocol method. It's not my preference and I don't really encourage it, but at least it doesn't conflict with existing things.
    – jpmc26
    2 hours ago
















6












6








6






I'm not sure whether you're reinventing the wheel, but you are using the wrong tools for the job.



I've been through the phase where I think I'm doing something unique enough that I need to do something nonstandard. Every time I've done it, I inevitably regret it. Don't do it. It's not a good idea. Use tools very closely to the way they were intended to be used.



You seem to be trying to do basically two things.



A guidance system to aid developers



The first is a sort of "brief help" that prompts the users with details about the API.



You implement this with repr, but repr is absolutely the wrong tool for this job.
repr is not intended to return help messages. repr is intended to be a quick and dirty introspection tool, showing just enough information for the programmer to identify the object they're looking at. You're reducing their ability to do so by misusing it, which is far and away something I would not advise for beginners.



To be honest, I wouldn't recommend building this into Python itself at all. Doing so is very nonstandard. Instead, I'd be more inclined to generate a set of HTML docs for them to use. pydoc is built in and is probably a good starting point. Sphinx is also well known and used. Python's wiki lists others to consider as well. Some of these will allow you to include documentation not just of the API, but also custom pages including examples and what have you. A set of HTML docs is likely to be infinitely more usable to your users since it doesn't require backspacing the command they're in the middle of typing.



Bottom line: I'd use the existing standard tools to provide your users with this kind of guidance, and the standard is to make it external to the code itself.



You might also want to take note of the dir command. dir(o) lists the names bound to o, which includes attributes, descriptors (similar to "properties" in other languages), and methods. This can be combined with pprint or pformat for easier to read output, and the if clause of list comprehensions can be used to filter out private names (i.e., [a for a in dir(o) if not a.startswith('_')]) or based on other conditions. I use this myself to explore an API when the documentation is poor. vars(o) may also be useful for viewing attributes.



If it absolutely must be accessible by code, I think I'd probably implement it as a new "protocol". Something like this:



class PROJECT(object):
# Your other code
@staticmethod
def _mytips():
return """
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)
"""


Then in another module, you can leverage this _mytips() method:



import os

def tips(o):
try:
raw_tips = o._mytips()
# Use standardized indentation
return (os.linesep + ' ').join([l.strip() for l in raw_tips.strip().splitlines()])
except AttributeError:
raise ValueError('object does not support tips')


Then it can be used like this:



>>> tips(PROJECT)
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)
>>> p = PROJECT()
>>> tips(p)
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)


Calling the method _mytips gives the tips call a standard method to look for, and prefixing it with an underscore indicates it's not intended to be called directly. Note that you should not use something like __mytips__, as the magic method syntax is reserved for the language or the standard library. Making it static means you can use it on other the class directly or an instance. You still have to write the documentation yourself or generate it somehow. This is not particularly common, but at least this is in line with the standard "protocol pattern" Python uses for things that need to be implemented by lots of different classes/objects. Other devs will recognize what you're doing when they look at the tips code.



I'd still prefer an actual help page open in the browser, though, and don't really recommend it. You might consider loading the tips from an actual external document, giving you both with the same content.



Informational messages



The other thing you seem to want to implement is informational messages. The standard solution for this is logging. That is definitely what you're doing when you print the "added" messages. Using the logging module in those cases would look like this:



def new_carrot(self, name):
"""def new_carrot"""

carrot = Carrot(project=self, name=name)

self.things.append(carrot)
logging.debug("a Carrot was added!")
return carrot


But requires a tiny bit of set up:



import logging

logging.basicConfig(level=logging.DEBUG)


You can get fancier if you want more control over the format, but I'm not going to get into all those details here.



This also gives your users the option of disabling these messages by cranking up the message level:



logging.getLogger().setLevel(logging.INFO)


Other concerns



Resource management



I notice your PROJECT class starts up something in its initializer:



status = self.start_MQTT()


and then has a stop method as well.



The Python standard for managing resources is context managers. Context managers are a fantastic little tool that will make your code much simpler. If you convert PROJECT to one, then callers will have much less boilerplate managing it:



with PROJECT("myproj", use_MQTT=True) as p:
p.new_CARROT("carrot 1")


Yes, that block of code is correct. There is no need to call stop_MQTT, and that's because the with block invokes the method to release it automatically:



class PROJECT(object):
def __init__(self, name, Y_factor=None, use_MQTT=False):
# Everything EXCEPT starting the server

def __enter__(self):
if self.use_MQTT:
# do stuff
status = 'started'
self.client = 'client instance'
logging.debug("MQTT status: {}".format(status))

def __exit__(self, type, value, traceback):
if self.status == 'started':
# do stuff to stop server
status = 'stopped'
return status

# Other methods


Some details may not exactly line up with what you need to do, but you have pseudocode here anyway. Much simpler for your callers.



Use super



You have this call:



THING.__init__(self, project, name)


Don't do that. Use super:



super(ONION, self).__init__(project, name)


Naming and formatting standards



Please read PEP8 for naming conventions. Right off the bat, I notice that PROJECT would be Project and Y_factor should be yfactor. (One might argue that y_factor is also acceptable, but PEP8 notes that underscores should be used "to improve readability". Putting it after just a y doesn't really add any readability.)



Conclusion



The bottom line here is that Python has a lot of norms and standards, and your code doesn't conform to them. This isn't just a matter of being pedantic. Python's norms are built around practical concerns and avoiding real world problems. When you use Python the way it was intended to be used, it will make solving your problems easier.



If you want a sort of intro to the idea of writing "Pythonic" code and why it's important and a practical example, check out Raymond Hettinger's Beyond PEP8.






share|improve this answer














I'm not sure whether you're reinventing the wheel, but you are using the wrong tools for the job.



I've been through the phase where I think I'm doing something unique enough that I need to do something nonstandard. Every time I've done it, I inevitably regret it. Don't do it. It's not a good idea. Use tools very closely to the way they were intended to be used.



You seem to be trying to do basically two things.



A guidance system to aid developers



The first is a sort of "brief help" that prompts the users with details about the API.



You implement this with repr, but repr is absolutely the wrong tool for this job.
repr is not intended to return help messages. repr is intended to be a quick and dirty introspection tool, showing just enough information for the programmer to identify the object they're looking at. You're reducing their ability to do so by misusing it, which is far and away something I would not advise for beginners.



To be honest, I wouldn't recommend building this into Python itself at all. Doing so is very nonstandard. Instead, I'd be more inclined to generate a set of HTML docs for them to use. pydoc is built in and is probably a good starting point. Sphinx is also well known and used. Python's wiki lists others to consider as well. Some of these will allow you to include documentation not just of the API, but also custom pages including examples and what have you. A set of HTML docs is likely to be infinitely more usable to your users since it doesn't require backspacing the command they're in the middle of typing.



Bottom line: I'd use the existing standard tools to provide your users with this kind of guidance, and the standard is to make it external to the code itself.



You might also want to take note of the dir command. dir(o) lists the names bound to o, which includes attributes, descriptors (similar to "properties" in other languages), and methods. This can be combined with pprint or pformat for easier to read output, and the if clause of list comprehensions can be used to filter out private names (i.e., [a for a in dir(o) if not a.startswith('_')]) or based on other conditions. I use this myself to explore an API when the documentation is poor. vars(o) may also be useful for viewing attributes.



If it absolutely must be accessible by code, I think I'd probably implement it as a new "protocol". Something like this:



class PROJECT(object):
# Your other code
@staticmethod
def _mytips():
return """
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)
"""


Then in another module, you can leverage this _mytips() method:



import os

def tips(o):
try:
raw_tips = o._mytips()
# Use standardized indentation
return (os.linesep + ' ').join([l.strip() for l in raw_tips.strip().splitlines()])
except AttributeError:
raise ValueError('object does not support tips')


Then it can be used like this:



>>> tips(PROJECT)
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)
>>> p = PROJECT()
>>> tips(p)
PROJECT("bob")
intantiate with p = PROJECT(name, Y_factor=None, use_MQTT=False)
later use start_MQTT(), stop_MQTT(), and
add THINGs with p.new_CARROT(name) or p.new_ONION(name, n_rings=None)


Calling the method _mytips gives the tips call a standard method to look for, and prefixing it with an underscore indicates it's not intended to be called directly. Note that you should not use something like __mytips__, as the magic method syntax is reserved for the language or the standard library. Making it static means you can use it on other the class directly or an instance. You still have to write the documentation yourself or generate it somehow. This is not particularly common, but at least this is in line with the standard "protocol pattern" Python uses for things that need to be implemented by lots of different classes/objects. Other devs will recognize what you're doing when they look at the tips code.



I'd still prefer an actual help page open in the browser, though, and don't really recommend it. You might consider loading the tips from an actual external document, giving you both with the same content.



Informational messages



The other thing you seem to want to implement is informational messages. The standard solution for this is logging. That is definitely what you're doing when you print the "added" messages. Using the logging module in those cases would look like this:



def new_carrot(self, name):
"""def new_carrot"""

carrot = Carrot(project=self, name=name)

self.things.append(carrot)
logging.debug("a Carrot was added!")
return carrot


But requires a tiny bit of set up:



import logging

logging.basicConfig(level=logging.DEBUG)


You can get fancier if you want more control over the format, but I'm not going to get into all those details here.



This also gives your users the option of disabling these messages by cranking up the message level:



logging.getLogger().setLevel(logging.INFO)


Other concerns



Resource management



I notice your PROJECT class starts up something in its initializer:



status = self.start_MQTT()


and then has a stop method as well.



The Python standard for managing resources is context managers. Context managers are a fantastic little tool that will make your code much simpler. If you convert PROJECT to one, then callers will have much less boilerplate managing it:



with PROJECT("myproj", use_MQTT=True) as p:
p.new_CARROT("carrot 1")


Yes, that block of code is correct. There is no need to call stop_MQTT, and that's because the with block invokes the method to release it automatically:



class PROJECT(object):
def __init__(self, name, Y_factor=None, use_MQTT=False):
# Everything EXCEPT starting the server

def __enter__(self):
if self.use_MQTT:
# do stuff
status = 'started'
self.client = 'client instance'
logging.debug("MQTT status: {}".format(status))

def __exit__(self, type, value, traceback):
if self.status == 'started':
# do stuff to stop server
status = 'stopped'
return status

# Other methods


Some details may not exactly line up with what you need to do, but you have pseudocode here anyway. Much simpler for your callers.



Use super



You have this call:



THING.__init__(self, project, name)


Don't do that. Use super:



super(ONION, self).__init__(project, name)


Naming and formatting standards



Please read PEP8 for naming conventions. Right off the bat, I notice that PROJECT would be Project and Y_factor should be yfactor. (One might argue that y_factor is also acceptable, but PEP8 notes that underscores should be used "to improve readability". Putting it after just a y doesn't really add any readability.)



Conclusion



The bottom line here is that Python has a lot of norms and standards, and your code doesn't conform to them. This isn't just a matter of being pedantic. Python's norms are built around practical concerns and avoiding real world problems. When you use Python the way it was intended to be used, it will make solving your problems easier.



If you want a sort of intro to the idea of writing "Pythonic" code and why it's important and a practical example, check out Raymond Hettinger's Beyond PEP8.







share|improve this answer














share|improve this answer



share|improve this answer








edited 2 hours ago

























answered yesterday









jpmc26

69338




69338








  • 1




    +n! This is an excellent answer and what I needed, thank you! You are right about logger, elsewhere I've echoed the logged events to the screen with a second handler using logging.getLogger('').addHandler(console) and that may be what I need here. I've used context manager for the threading lock (and files) but never realized it could be used this way, nice! I have the manual stop method for other reasons (example: if you turn off WiFi to save batteries, then MQTT's looping/pinging starts complaining) but for final shut down, what you've shown is great.
    – uhoh
    yesterday












  • Super looks super, I will dig in and read about that now. Again, thank you for taking the time for a thorough review!
    – uhoh
    yesterday






  • 1




    @uhoh I added some notes about dir and vars you may find useful.
    – jpmc26
    7 hours ago










  • yep, thanks. Maybe what I'm really looking for is something that could look like and work like a Built-in function, and be called pretty_help(class), concise_help(class) or even local_hints(class). Something that returns a few concise lines of main methods, hints, or other things that are what users are most likely to need, and because of compactness still leave your current train of thought on the screen, even if the screen is small. I'm a heavy python user but will never be, or think like you steel-trap-minded developers, and there are a lot of us who could use some simple hints.
    – uhoh
    4 hours ago






  • 1




    @uhoh I'm not really opposed to the idea; I can see the usefulness.Jupyter/IPython do something similar, after all. It's just that such a thing does not exist out of the box, and hijacking functionality that's supposed to do something else will hurt you more that it will help in the long run. I read documentation all day long, and I find myself using help pages way more than any kind of command line help. That said, I've made one more addition: designing your own protocol method. It's not my preference and I don't really encourage it, but at least it doesn't conflict with existing things.
    – jpmc26
    2 hours ago
















  • 1




    +n! This is an excellent answer and what I needed, thank you! You are right about logger, elsewhere I've echoed the logged events to the screen with a second handler using logging.getLogger('').addHandler(console) and that may be what I need here. I've used context manager for the threading lock (and files) but never realized it could be used this way, nice! I have the manual stop method for other reasons (example: if you turn off WiFi to save batteries, then MQTT's looping/pinging starts complaining) but for final shut down, what you've shown is great.
    – uhoh
    yesterday












  • Super looks super, I will dig in and read about that now. Again, thank you for taking the time for a thorough review!
    – uhoh
    yesterday






  • 1




    @uhoh I added some notes about dir and vars you may find useful.
    – jpmc26
    7 hours ago










  • yep, thanks. Maybe what I'm really looking for is something that could look like and work like a Built-in function, and be called pretty_help(class), concise_help(class) or even local_hints(class). Something that returns a few concise lines of main methods, hints, or other things that are what users are most likely to need, and because of compactness still leave your current train of thought on the screen, even if the screen is small. I'm a heavy python user but will never be, or think like you steel-trap-minded developers, and there are a lot of us who could use some simple hints.
    – uhoh
    4 hours ago






  • 1




    @uhoh I'm not really opposed to the idea; I can see the usefulness.Jupyter/IPython do something similar, after all. It's just that such a thing does not exist out of the box, and hijacking functionality that's supposed to do something else will hurt you more that it will help in the long run. I read documentation all day long, and I find myself using help pages way more than any kind of command line help. That said, I've made one more addition: designing your own protocol method. It's not my preference and I don't really encourage it, but at least it doesn't conflict with existing things.
    – jpmc26
    2 hours ago










1




1




+n! This is an excellent answer and what I needed, thank you! You are right about logger, elsewhere I've echoed the logged events to the screen with a second handler using logging.getLogger('').addHandler(console) and that may be what I need here. I've used context manager for the threading lock (and files) but never realized it could be used this way, nice! I have the manual stop method for other reasons (example: if you turn off WiFi to save batteries, then MQTT's looping/pinging starts complaining) but for final shut down, what you've shown is great.
– uhoh
yesterday






+n! This is an excellent answer and what I needed, thank you! You are right about logger, elsewhere I've echoed the logged events to the screen with a second handler using logging.getLogger('').addHandler(console) and that may be what I need here. I've used context manager for the threading lock (and files) but never realized it could be used this way, nice! I have the manual stop method for other reasons (example: if you turn off WiFi to save batteries, then MQTT's looping/pinging starts complaining) but for final shut down, what you've shown is great.
– uhoh
yesterday














Super looks super, I will dig in and read about that now. Again, thank you for taking the time for a thorough review!
– uhoh
yesterday




Super looks super, I will dig in and read about that now. Again, thank you for taking the time for a thorough review!
– uhoh
yesterday




1




1




@uhoh I added some notes about dir and vars you may find useful.
– jpmc26
7 hours ago




@uhoh I added some notes about dir and vars you may find useful.
– jpmc26
7 hours ago












yep, thanks. Maybe what I'm really looking for is something that could look like and work like a Built-in function, and be called pretty_help(class), concise_help(class) or even local_hints(class). Something that returns a few concise lines of main methods, hints, or other things that are what users are most likely to need, and because of compactness still leave your current train of thought on the screen, even if the screen is small. I'm a heavy python user but will never be, or think like you steel-trap-minded developers, and there are a lot of us who could use some simple hints.
– uhoh
4 hours ago




yep, thanks. Maybe what I'm really looking for is something that could look like and work like a Built-in function, and be called pretty_help(class), concise_help(class) or even local_hints(class). Something that returns a few concise lines of main methods, hints, or other things that are what users are most likely to need, and because of compactness still leave your current train of thought on the screen, even if the screen is small. I'm a heavy python user but will never be, or think like you steel-trap-minded developers, and there are a lot of us who could use some simple hints.
– uhoh
4 hours ago




1




1




@uhoh I'm not really opposed to the idea; I can see the usefulness.Jupyter/IPython do something similar, after all. It's just that such a thing does not exist out of the box, and hijacking functionality that's supposed to do something else will hurt you more that it will help in the long run. I read documentation all day long, and I find myself using help pages way more than any kind of command line help. That said, I've made one more addition: designing your own protocol method. It's not my preference and I don't really encourage it, but at least it doesn't conflict with existing things.
– jpmc26
2 hours ago






@uhoh I'm not really opposed to the idea; I can see the usefulness.Jupyter/IPython do something similar, after all. It's just that such a thing does not exist out of the box, and hijacking functionality that's supposed to do something else will hurt you more that it will help in the long run. I read documentation all day long, and I find myself using help pages way more than any kind of command line help. That said, I've made one more addition: designing your own protocol method. It's not my preference and I don't really encourage it, but at least it doesn't conflict with existing things.
– jpmc26
2 hours ago















8














Disclaimer



Now that the OP has clarified that they intend to use this as a teaching tool, my answer has lost relevance for their use-case. It can still be taken as general advice for most Python modules.



I do agree with @jpmc26 to a certain extent, specifically that this code itself as currently written should not be used as an example for new learners of Python. I am certainly not advocating teaching students bad Python style. But I think that this could be a useful learning aid within the context the OP provides.



Original answer



I realize this is one of the central premises of your program, but printing help docstrings during normal use of the program is highly unusual behavior and likely to be annoying to users of your program. For example, a user might already know how to use your API, and doesn't need to be told how every time they invoke it; it would just clutter up the command line. Besides, you're solving a problem that doesn't really need to be solved: a class docstring may scroll the screen as described, but function and method docstrings do not, and the Python REPL interpreter already allows you to short circuit the scrolling.



Instead, I would recommend using more descriptive and helpful docstrings. For example, informing the user of the class calling conventions in isolation is not useful; explaining what the class's intended use is, and what the various parameters mean would be more useful. PEP 257 provides guidance on how to write good docstrings.






share|improve this answer























  • The first sentence of my question makes it clear that this is an exercise that I am leading them through, rather than a module for general release. In this case Python is a tool, but not the topic. Getting short, helpful docstrings will not be annoying to them, but instead helpful. I would have mentioned this right away, but my power went out right after posting (I usually babysit my questions). In any event, I'll take your answer to be that you don't feel there is a better way to do this (type an object and receive hints how to use it).
    – uhoh
    yesterday












  • @uhoh My answer was premised on the assumption that this was code intended to be a general Python module for distribution, not a teaching tool. I read the first sentence, but I was genuinely confused as to who the intended audience was. Perhaps adding a sentence or two that provides the additional context, and editing your question title would help you receive answers that are closer to what you're looking for; my answer is by no means a comprehensive analysis of your code.
    – Graham
    yesterday










  • It's unfortunate the power went out and I couldn't stay with my post. I've just gotten home now after spending the night in a McDonalds writing with a pen in a notebook (it's been a while, surprised I remembered how!). I'll update the question in a few hours. Until then let's assume that the behavior indicated really is the behavior needed, and I'm checking if there is a simpler way to do this, rather than asking "is this a good idea". Thanks!
    – uhoh
    yesterday










  • "Now that the OP has clarified that they intend to use this as a teaching tool" NO! It is vital that education conforms to the norms of the language and teaches students to adhere to them. The students will be far better served by the teacher using the correct, existing technology for the task they are trying to perform.
    – jpmc26
    yesterday












  • @jpmc26 I think it depends on what level of education the students are at, and the general context. If the OP wants to show their students how to instantiate objects and how objects work in Python, then this might be a useful tool. But, there are probably better tools. And I do agree that if this code is used, the underlying code should not be shown to students as an example.
    – Graham
    yesterday
















8














Disclaimer



Now that the OP has clarified that they intend to use this as a teaching tool, my answer has lost relevance for their use-case. It can still be taken as general advice for most Python modules.



I do agree with @jpmc26 to a certain extent, specifically that this code itself as currently written should not be used as an example for new learners of Python. I am certainly not advocating teaching students bad Python style. But I think that this could be a useful learning aid within the context the OP provides.



Original answer



I realize this is one of the central premises of your program, but printing help docstrings during normal use of the program is highly unusual behavior and likely to be annoying to users of your program. For example, a user might already know how to use your API, and doesn't need to be told how every time they invoke it; it would just clutter up the command line. Besides, you're solving a problem that doesn't really need to be solved: a class docstring may scroll the screen as described, but function and method docstrings do not, and the Python REPL interpreter already allows you to short circuit the scrolling.



Instead, I would recommend using more descriptive and helpful docstrings. For example, informing the user of the class calling conventions in isolation is not useful; explaining what the class's intended use is, and what the various parameters mean would be more useful. PEP 257 provides guidance on how to write good docstrings.






share|improve this answer























  • The first sentence of my question makes it clear that this is an exercise that I am leading them through, rather than a module for general release. In this case Python is a tool, but not the topic. Getting short, helpful docstrings will not be annoying to them, but instead helpful. I would have mentioned this right away, but my power went out right after posting (I usually babysit my questions). In any event, I'll take your answer to be that you don't feel there is a better way to do this (type an object and receive hints how to use it).
    – uhoh
    yesterday












  • @uhoh My answer was premised on the assumption that this was code intended to be a general Python module for distribution, not a teaching tool. I read the first sentence, but I was genuinely confused as to who the intended audience was. Perhaps adding a sentence or two that provides the additional context, and editing your question title would help you receive answers that are closer to what you're looking for; my answer is by no means a comprehensive analysis of your code.
    – Graham
    yesterday










  • It's unfortunate the power went out and I couldn't stay with my post. I've just gotten home now after spending the night in a McDonalds writing with a pen in a notebook (it's been a while, surprised I remembered how!). I'll update the question in a few hours. Until then let's assume that the behavior indicated really is the behavior needed, and I'm checking if there is a simpler way to do this, rather than asking "is this a good idea". Thanks!
    – uhoh
    yesterday










  • "Now that the OP has clarified that they intend to use this as a teaching tool" NO! It is vital that education conforms to the norms of the language and teaches students to adhere to them. The students will be far better served by the teacher using the correct, existing technology for the task they are trying to perform.
    – jpmc26
    yesterday












  • @jpmc26 I think it depends on what level of education the students are at, and the general context. If the OP wants to show their students how to instantiate objects and how objects work in Python, then this might be a useful tool. But, there are probably better tools. And I do agree that if this code is used, the underlying code should not be shown to students as an example.
    – Graham
    yesterday














8












8








8






Disclaimer



Now that the OP has clarified that they intend to use this as a teaching tool, my answer has lost relevance for their use-case. It can still be taken as general advice for most Python modules.



I do agree with @jpmc26 to a certain extent, specifically that this code itself as currently written should not be used as an example for new learners of Python. I am certainly not advocating teaching students bad Python style. But I think that this could be a useful learning aid within the context the OP provides.



Original answer



I realize this is one of the central premises of your program, but printing help docstrings during normal use of the program is highly unusual behavior and likely to be annoying to users of your program. For example, a user might already know how to use your API, and doesn't need to be told how every time they invoke it; it would just clutter up the command line. Besides, you're solving a problem that doesn't really need to be solved: a class docstring may scroll the screen as described, but function and method docstrings do not, and the Python REPL interpreter already allows you to short circuit the scrolling.



Instead, I would recommend using more descriptive and helpful docstrings. For example, informing the user of the class calling conventions in isolation is not useful; explaining what the class's intended use is, and what the various parameters mean would be more useful. PEP 257 provides guidance on how to write good docstrings.






share|improve this answer














Disclaimer



Now that the OP has clarified that they intend to use this as a teaching tool, my answer has lost relevance for their use-case. It can still be taken as general advice for most Python modules.



I do agree with @jpmc26 to a certain extent, specifically that this code itself as currently written should not be used as an example for new learners of Python. I am certainly not advocating teaching students bad Python style. But I think that this could be a useful learning aid within the context the OP provides.



Original answer



I realize this is one of the central premises of your program, but printing help docstrings during normal use of the program is highly unusual behavior and likely to be annoying to users of your program. For example, a user might already know how to use your API, and doesn't need to be told how every time they invoke it; it would just clutter up the command line. Besides, you're solving a problem that doesn't really need to be solved: a class docstring may scroll the screen as described, but function and method docstrings do not, and the Python REPL interpreter already allows you to short circuit the scrolling.



Instead, I would recommend using more descriptive and helpful docstrings. For example, informing the user of the class calling conventions in isolation is not useful; explaining what the class's intended use is, and what the various parameters mean would be more useful. PEP 257 provides guidance on how to write good docstrings.







share|improve this answer














share|improve this answer



share|improve this answer








edited yesterday

























answered yesterday









Graham

968113




968113












  • The first sentence of my question makes it clear that this is an exercise that I am leading them through, rather than a module for general release. In this case Python is a tool, but not the topic. Getting short, helpful docstrings will not be annoying to them, but instead helpful. I would have mentioned this right away, but my power went out right after posting (I usually babysit my questions). In any event, I'll take your answer to be that you don't feel there is a better way to do this (type an object and receive hints how to use it).
    – uhoh
    yesterday












  • @uhoh My answer was premised on the assumption that this was code intended to be a general Python module for distribution, not a teaching tool. I read the first sentence, but I was genuinely confused as to who the intended audience was. Perhaps adding a sentence or two that provides the additional context, and editing your question title would help you receive answers that are closer to what you're looking for; my answer is by no means a comprehensive analysis of your code.
    – Graham
    yesterday










  • It's unfortunate the power went out and I couldn't stay with my post. I've just gotten home now after spending the night in a McDonalds writing with a pen in a notebook (it's been a while, surprised I remembered how!). I'll update the question in a few hours. Until then let's assume that the behavior indicated really is the behavior needed, and I'm checking if there is a simpler way to do this, rather than asking "is this a good idea". Thanks!
    – uhoh
    yesterday










  • "Now that the OP has clarified that they intend to use this as a teaching tool" NO! It is vital that education conforms to the norms of the language and teaches students to adhere to them. The students will be far better served by the teacher using the correct, existing technology for the task they are trying to perform.
    – jpmc26
    yesterday












  • @jpmc26 I think it depends on what level of education the students are at, and the general context. If the OP wants to show their students how to instantiate objects and how objects work in Python, then this might be a useful tool. But, there are probably better tools. And I do agree that if this code is used, the underlying code should not be shown to students as an example.
    – Graham
    yesterday


















  • The first sentence of my question makes it clear that this is an exercise that I am leading them through, rather than a module for general release. In this case Python is a tool, but not the topic. Getting short, helpful docstrings will not be annoying to them, but instead helpful. I would have mentioned this right away, but my power went out right after posting (I usually babysit my questions). In any event, I'll take your answer to be that you don't feel there is a better way to do this (type an object and receive hints how to use it).
    – uhoh
    yesterday












  • @uhoh My answer was premised on the assumption that this was code intended to be a general Python module for distribution, not a teaching tool. I read the first sentence, but I was genuinely confused as to who the intended audience was. Perhaps adding a sentence or two that provides the additional context, and editing your question title would help you receive answers that are closer to what you're looking for; my answer is by no means a comprehensive analysis of your code.
    – Graham
    yesterday










  • It's unfortunate the power went out and I couldn't stay with my post. I've just gotten home now after spending the night in a McDonalds writing with a pen in a notebook (it's been a while, surprised I remembered how!). I'll update the question in a few hours. Until then let's assume that the behavior indicated really is the behavior needed, and I'm checking if there is a simpler way to do this, rather than asking "is this a good idea". Thanks!
    – uhoh
    yesterday










  • "Now that the OP has clarified that they intend to use this as a teaching tool" NO! It is vital that education conforms to the norms of the language and teaches students to adhere to them. The students will be far better served by the teacher using the correct, existing technology for the task they are trying to perform.
    – jpmc26
    yesterday












  • @jpmc26 I think it depends on what level of education the students are at, and the general context. If the OP wants to show their students how to instantiate objects and how objects work in Python, then this might be a useful tool. But, there are probably better tools. And I do agree that if this code is used, the underlying code should not be shown to students as an example.
    – Graham
    yesterday
















The first sentence of my question makes it clear that this is an exercise that I am leading them through, rather than a module for general release. In this case Python is a tool, but not the topic. Getting short, helpful docstrings will not be annoying to them, but instead helpful. I would have mentioned this right away, but my power went out right after posting (I usually babysit my questions). In any event, I'll take your answer to be that you don't feel there is a better way to do this (type an object and receive hints how to use it).
– uhoh
yesterday






The first sentence of my question makes it clear that this is an exercise that I am leading them through, rather than a module for general release. In this case Python is a tool, but not the topic. Getting short, helpful docstrings will not be annoying to them, but instead helpful. I would have mentioned this right away, but my power went out right after posting (I usually babysit my questions). In any event, I'll take your answer to be that you don't feel there is a better way to do this (type an object and receive hints how to use it).
– uhoh
yesterday














@uhoh My answer was premised on the assumption that this was code intended to be a general Python module for distribution, not a teaching tool. I read the first sentence, but I was genuinely confused as to who the intended audience was. Perhaps adding a sentence or two that provides the additional context, and editing your question title would help you receive answers that are closer to what you're looking for; my answer is by no means a comprehensive analysis of your code.
– Graham
yesterday




@uhoh My answer was premised on the assumption that this was code intended to be a general Python module for distribution, not a teaching tool. I read the first sentence, but I was genuinely confused as to who the intended audience was. Perhaps adding a sentence or two that provides the additional context, and editing your question title would help you receive answers that are closer to what you're looking for; my answer is by no means a comprehensive analysis of your code.
– Graham
yesterday












It's unfortunate the power went out and I couldn't stay with my post. I've just gotten home now after spending the night in a McDonalds writing with a pen in a notebook (it's been a while, surprised I remembered how!). I'll update the question in a few hours. Until then let's assume that the behavior indicated really is the behavior needed, and I'm checking if there is a simpler way to do this, rather than asking "is this a good idea". Thanks!
– uhoh
yesterday




It's unfortunate the power went out and I couldn't stay with my post. I've just gotten home now after spending the night in a McDonalds writing with a pen in a notebook (it's been a while, surprised I remembered how!). I'll update the question in a few hours. Until then let's assume that the behavior indicated really is the behavior needed, and I'm checking if there is a simpler way to do this, rather than asking "is this a good idea". Thanks!
– uhoh
yesterday












"Now that the OP has clarified that they intend to use this as a teaching tool" NO! It is vital that education conforms to the norms of the language and teaches students to adhere to them. The students will be far better served by the teacher using the correct, existing technology for the task they are trying to perform.
– jpmc26
yesterday






"Now that the OP has clarified that they intend to use this as a teaching tool" NO! It is vital that education conforms to the norms of the language and teaches students to adhere to them. The students will be far better served by the teacher using the correct, existing technology for the task they are trying to perform.
– jpmc26
yesterday














@jpmc26 I think it depends on what level of education the students are at, and the general context. If the OP wants to show their students how to instantiate objects and how objects work in Python, then this might be a useful tool. But, there are probably better tools. And I do agree that if this code is used, the underlying code should not be shown to students as an example.
– Graham
yesterday




@jpmc26 I think it depends on what level of education the students are at, and the general context. If the OP wants to show their students how to instantiate objects and how objects work in Python, then this might be a useful tool. But, there are probably better tools. And I do agree that if this code is used, the underlying code should not be shown to students as an example.
– Graham
yesterday











3














It is difficult to add anything to the existing great and detailed answers, but, to follow up your comment that .__doc__ might be to difficult to remember for newcomers, you could then wrap it around your custom function:



def doc(obj):
"""Prints a docstring of a given object."""
print(obj.__doc__)


Usage:



In [2]: class CARROT:
...: """ instantiate with c = CARROT(project, name)
...: change thread with c.change_thread_value(new_value)"""
...:

In [3]: carrot = CARROT()

In [4]: doc(carrot)
instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)




Jupyter Notebooks?



As a side note, you may switch to using Jupyter notebooks that may actually be a good learning environment for newcomers and would let them use the standard built-in help() in the Jupyter cells.



There are also built-in shortcuts, like shift+TAB to access a help popup:



enter image description here






share|improve this answer





















  • I'd forgotten about Jupyter, thanks! You might want to add a "me too" answer there (or not). I won't have control over what people use, and this question is for those who live 100% inside a terminal. "They may do this from a terminal which does not show the docstring balloons the way IDLE and many other IDEs can. So instead I've found a way..."
    – uhoh
    yesterday
















3














It is difficult to add anything to the existing great and detailed answers, but, to follow up your comment that .__doc__ might be to difficult to remember for newcomers, you could then wrap it around your custom function:



def doc(obj):
"""Prints a docstring of a given object."""
print(obj.__doc__)


Usage:



In [2]: class CARROT:
...: """ instantiate with c = CARROT(project, name)
...: change thread with c.change_thread_value(new_value)"""
...:

In [3]: carrot = CARROT()

In [4]: doc(carrot)
instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)




Jupyter Notebooks?



As a side note, you may switch to using Jupyter notebooks that may actually be a good learning environment for newcomers and would let them use the standard built-in help() in the Jupyter cells.



There are also built-in shortcuts, like shift+TAB to access a help popup:



enter image description here






share|improve this answer





















  • I'd forgotten about Jupyter, thanks! You might want to add a "me too" answer there (or not). I won't have control over what people use, and this question is for those who live 100% inside a terminal. "They may do this from a terminal which does not show the docstring balloons the way IDLE and many other IDEs can. So instead I've found a way..."
    – uhoh
    yesterday














3












3








3






It is difficult to add anything to the existing great and detailed answers, but, to follow up your comment that .__doc__ might be to difficult to remember for newcomers, you could then wrap it around your custom function:



def doc(obj):
"""Prints a docstring of a given object."""
print(obj.__doc__)


Usage:



In [2]: class CARROT:
...: """ instantiate with c = CARROT(project, name)
...: change thread with c.change_thread_value(new_value)"""
...:

In [3]: carrot = CARROT()

In [4]: doc(carrot)
instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)




Jupyter Notebooks?



As a side note, you may switch to using Jupyter notebooks that may actually be a good learning environment for newcomers and would let them use the standard built-in help() in the Jupyter cells.



There are also built-in shortcuts, like shift+TAB to access a help popup:



enter image description here






share|improve this answer












It is difficult to add anything to the existing great and detailed answers, but, to follow up your comment that .__doc__ might be to difficult to remember for newcomers, you could then wrap it around your custom function:



def doc(obj):
"""Prints a docstring of a given object."""
print(obj.__doc__)


Usage:



In [2]: class CARROT:
...: """ instantiate with c = CARROT(project, name)
...: change thread with c.change_thread_value(new_value)"""
...:

In [3]: carrot = CARROT()

In [4]: doc(carrot)
instantiate with c = CARROT(project, name)
change thread with c.change_thread_value(new_value)




Jupyter Notebooks?



As a side note, you may switch to using Jupyter notebooks that may actually be a good learning environment for newcomers and would let them use the standard built-in help() in the Jupyter cells.



There are also built-in shortcuts, like shift+TAB to access a help popup:



enter image description here







share|improve this answer












share|improve this answer



share|improve this answer










answered yesterday









alecxe

15k53478




15k53478












  • I'd forgotten about Jupyter, thanks! You might want to add a "me too" answer there (or not). I won't have control over what people use, and this question is for those who live 100% inside a terminal. "They may do this from a terminal which does not show the docstring balloons the way IDLE and many other IDEs can. So instead I've found a way..."
    – uhoh
    yesterday


















  • I'd forgotten about Jupyter, thanks! You might want to add a "me too" answer there (or not). I won't have control over what people use, and this question is for those who live 100% inside a terminal. "They may do this from a terminal which does not show the docstring balloons the way IDLE and many other IDEs can. So instead I've found a way..."
    – uhoh
    yesterday
















I'd forgotten about Jupyter, thanks! You might want to add a "me too" answer there (or not). I won't have control over what people use, and this question is for those who live 100% inside a terminal. "They may do this from a terminal which does not show the docstring balloons the way IDLE and many other IDEs can. So instead I've found a way..."
– uhoh
yesterday




I'd forgotten about Jupyter, thanks! You might want to add a "me too" answer there (or not). I won't have control over what people use, and this question is for those who live 100% inside a terminal. "They may do this from a terminal which does not show the docstring balloons the way IDLE and many other IDEs can. So instead I've found a way..."
– uhoh
yesterday


















draft saved

draft discarded




















































Thanks for contributing an answer to Code Review Stack Exchange!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


Use MathJax to format equations. MathJax reference.


To learn more, see our tips on writing great answers.





Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


Please pay close attention to the following guidance:


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210746%2fimplementing-a-customized-helpful-hints-function-which-includes-docstring-but-mu%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

An IMO inspired problem

Management

Has there ever been an instance of an active nuclear power plant within or near a war zone?