Classes, instances, threads, and other things.

This “thread” :wink: - topic - is to discuss the mechanics of classes, instances, threads, and the things that make them work.

===========================

First question:

Assume I have three classes and they inherit from each other.

  • gopigo3
  • easygopigo3 that uses gopigo3 as its superclass.
  • simplegopigo3 that uses easygopigo3 as its superclass.

Assume three methods:

  • gopigo3:
    • gopigo3_method
  • easygopigo3:
    • easygopigo3_method
  • simplegopigo3:
    • simplegopigo3_method

. . . and I instantiate simplegopigo3 like this:
my_bot = simplegopigo3([whatever])

  1. What happens to the other classes?

  2. If I want to use gopigo3_method or easygopigo3_method:

    • Is that possible?
    • How?

=========================

If I create a class that ultimately becomes a threaded process, and I need to kill it for whatever reason, how do I do that?

  • Several articles I read lead me to believe that join() is sufficient.
  • @cyclicalobsessive leads me to believe that there are other things that must be done first, and then join() simply cleans up the trash afterwards.
  • Some of the thread-classes in the original streaming and web-server code implement a “kill” process that seems to go off somewhere and kill the thread - as in
mythread = threaded_process([args})
[some code]
kill.mythread()
where "kill.mythread" doesn't seem to have a corresponding method in "threaded_process".

What do I need to know, what advice can you give me, and where else can I get (hopefully reliable) information?

1 Like

Not sure I understand the question.

The instantiation of a simplegopigo3 class makes an object that has all the simplegopigo3 “guts”,
plus instantiates any non-overloaded easygopigo3 class “guts” by the call to the super().__init__(),
plus instantiates all the gopigo3 class “guts” because the easygopigo3 class init() call to super().__init__()

To use any non-overloaded variable, constant, or method simply:

my_simplegopigo3=SimpleGoPiGo3()       # I cannot write a Class name in all lower case - can't do it!
my_simplegopigo3.stop_and_power_off()  # calls SimpleGoPiGo3.stop_and_power_off()
my_simplegopigo3.volt()                # calls EasyGoPiGo3.volt()
my_simplegopigo3.battery_voltage()     # calls GoPiGo3.battery_voltage()
# get speed from EasyGoPiGo3 class variable in my_simplegopigo3 instance
print("Current speed limit: {}".format(my_simplegopigo3.speed))  

Update: removed wrong worry.

3 Likes

Some folks write a helper function to handle sending signals or flags for threads to exit, or blasting them out of existence with an operating system kill.

Would need to find the def for kill.mythread() to know what it does.

2 Likes

OK, I’ll bite.  Why doesn’t it fail?

It seems that, (if I read that rightly, which I probably don’t), if you try to sub-class it, it will puke all over itself.

Yet, you say it doesn’t.  And I have every confidence you are correct.

So, what am I missing and why doesn’t it fail as we’re expecting it to?

Or is that because it was originally written for Python2 with the insane instantiation/signature way they did things that would confuse the Old Testament Prophets themselves?

1 Like

Next question:

Given the following classes:

  • EasyGoPiGo3
  • My_Class_Of_Includes  (That has a bunch of stuff I’ve encapsulated)
  • Another_Robot_Helper_Class  (That has stuff for the Remote Camera Robot project)
  • Server_Sent_Event_Stuff  (That includes stuff for sending messages back to the browser)
  • Plus_whatever_other_classes_I may_have_defined in my attempt to reduce a confusing morass of code into “cubelets” I can “stick together with magnets” as and when I need them.

. . . . which I include in my code in the conventional fashion.

Now, let’s get down to brass tacks and instantiate all these little beasties:

  • my_robot = EasyGoPiGo3()
  • my_robot_includes = My_Class_Of_Includes()
  • my_robot_helper_includes = Another_Robot_Helper_Class()
  • my_robot_SSE_stuff = Server_Sent_Event_Stuff()
  • my_robot\'s_endless_list_of_other_class_instances = God_Himself_Only_Knows_What_Else()

(@cyclicalobsessive: Please reformat and change capitalization on your machine however you wish.  :wink:  I do it this way as it is easier and less painful to type without hitting the shift-key every other letter - the underscore is painful enough as it is.)

It seems that this is exactly what we have here - pure naming confusion - as everything I do has to start with a different [something-dot] prefix if I succeed in creating the “cubelets” I envisioned.

  1. Is this just “the nature of the beast”?
    — or —
  2. Is there some way to reduce this intractable morass of something-dots to a more reasonable naming method such that  my_bot = [all of this stuff], all at once?

================

This has been one of the reasons I have avoided classes, (aside from having to define a hot-key on my keyboard to = “self.”  :wink:), as it seems that you end up with either a GIGANTIC SUPER-super-class that contains everything hard-coded into it, or a can-of-worms morass of individual dot-names.

What am I misunderstanding?   I cannot believe that this is what the (class and OO) founding fathers had envisioned.

1 Like

I just looked at that.

Holy Christmas!  What a piece of work!!

1 Like

That is sort of the problem I was suffering from - confounding myself by confounding four different issues -

  1. Python2 Python3 difference in the subclass initialization calling syntax
  2. EasyIMUSensor cannot be super-classed
  3. EasyIMUSensor only available in Python2.x
  4. My errant thinking that isinstance(obj,class_or_type) would not check more than one class deep

Why does super-classing EasyGoPiGo3 class work? Because it properly calls the sub-class’s init:

try:
            if sys.version_info[0] < 3:
                super(self.__class__, self).__init__(config_file_path=config_file_path)
            else:
                super().__init__(config_file_path=config_file_path)
2 Likes

I cannot know what you are misunderstanding without understanding what you are misunderstanding AND everything you are understanding, so no way I can answer that.

There is a design I have used when I want to tie stuff together. Let’s tie a distance sensor object and an imu object and a tiltpan object to a robot object:

mybot = My_EasyGoPiGo3(use_mutex=True)
mybot.ds = mybot.init_distance_sensor()   # default h/w I2C is good and use_mutex gets passed down
mybot.imu = mybot.init_imu_sensor()   # default s/w I2C on AD1 is good and use_mutex gets passed down

So now everywhere mybot is passed, the distance sensor and imu are available - say I have a function

def drive_a_square_with_obstacle_detection(bot,size_cm=10):
   ...
   if (bot.ds.read() > size_sm): 
       ...

   current_heading = bot.imu.safe_read_euler()[0]  # method returns [fHeading, fRoll, fPitch]

So everything that lives on my robot is accessable to any function that has the mybot passed to it.

In your case the stuff is not available under a mybot initialization method, so your design looks like:

import thingx
import thingy
import tiltpan
import myeasygopigo3

mybot = myeasygopigo3.My_EasyGoPiGo3(use_mutex=True)  # create my GoPiGo3 robot object
mybot.thingx = thingx.ThingX()  # attach a ThingX object to my robot object
mybot.thingy = thingy.ThingY()  # attach a ThingY object to my robot object
mybot.tiltpan = tiltpan.TiltPan()  # attach the tiltpan as object to the robot object

OR even tighter do the thingey object inits in the init for My_EasyGoPiGo3 class:
    def __init__():
        ...
        self.thingx = thingx.ThingX()
        self.thingy = thingy.ThingY()
        self.tiltpan = tiltpan.TiltPan()

Anywhere (outside of your robot object) that you need to do some thingx thing you only need to:

mybot.thingx.do_thingx_thing()
mybot.tiltpan.center()


and inside your mybot it looks like:

class My_EasyGoPiGo3(…

def someMethodThatUsesThingX():
    ...
    self.thingx.do_thingx_thing()
    self.tiltpan.tilt(-20)

and in the usage in that case:

mybot.someMethodThatUsesThingX()   # 

You can attach things to an object at any time. Sometimes you can’t do it when initializing the object, so it can be done outside the object, or by creating an object method init_thingx():

class My_EasyGoPiGo3(...:
   ...
   def __init__(...:     # can't init thingx at this time
       ...

   def init_thingx():
      self.thingx = thingx.ThingX()    # init a ThingX object and attach it to the My_EasyGoPiGo3 object

I’ve left out the good programming practice to fail gracefully everywhere. Methods that expect an object exists should not blow up the whole kit-n-kaboodle if the object isn’t available at the moment or was not successfully initialized.

2 Likes

If I’m reading this correctly, (??!!!), I think you understand the basic issue of a clogged-up naming convention.

However, I am still confused.

I am beginning to suspect that I am asking questions that are just a tad beyond the “Dick-and-Jane” reading level for classes, where my experience is still at the toddler’s “picture-book” stage.

For example - given what you say are two separate cases - how are they different?  Except for the class names you used, they look virtually identical to me.

Likewise, I don’t understand the thingx.ThingX() construction you’re using.

I feel like I’m being abysmally stupid, and that I should be understanding this more clearly and easily - but everything I read seems to contradict everything else I read. (:pounding head against wall:)

2 Likes

I forgot to mention this method works well to eliminate globals. If you attach all the applicable variables and constants to the class object, you always have them available self.xyz_variable in every method inside the class and mybot.xyz_variable everywhere else.

2 Likes

No different - sometimes I slap stuff onto an existing class which is not very good - it creates a kind of “virtual superclass” that is not obvious. Sometimes I draw pretty pictures and include the attaching in the init of a true super class, so it is obvious and documented and cohesive.

2 Likes

I’m not sure I’m following correctly.

Let me re-ask the question in different terms so that you might understand the issue more correctly:

Given the classes noted above:

  • EasyGoPiGo3
  • My_Class_Of_Includes (That has a bunch of stuff I’ve encapsulated)
  • Another_Robot_Helper_Class (That has stuff for the Remote Camera Robot project)
  • Server_Sent_Event_Stuff (That includes stuff for sending messages back to the browser)

Based on my limited experience, what I do is like this:

  • my_easygopigo3 = EasyGoPiGo3(. . .)
  • my_includes = My_Class_Of_Includes(. . .)
  • my_helper_class = Another_Robot_Helper_Class(. . .)
    — and —
  • my_SSE_Stuff = Server_Sent_Event_Stuff(. . .)

When I want to use a particular method. I have to prefix it with my instance name for that class, such as:

  • my_easygopigo3.set_servo(. . .)
  • my_includes.Shake_Head(. . .)
  • my_helper_class.Where_Am_I_Going(. . .)
    — and —
  • my_SSE_Stuff.send_battery_voltage(. . .)

What I want to do is to, somehow or other, aggregate all the methods in such a way that I can say

  • my_bot.set_servo(. . .)
  • my_bot.Shake_Head(. . .)
  • my_bot.Where_Am_I_Going(. . .)
  • my_bot.Send_Battery_Voltage(. . .)

So that everything looks unified, consistent, and makes sense.

Am I loosing my grip?

P.S.
I guess “pretty” is in the eye of the beholder.
Are you sure you got an engineering degree instead of a medical degree?  That looks an awful lot like a prescription to me!  :wink:

Unfortunately, though obvious to you, it makes no sense to me. . .

P.P.S.

I discovered this reading some of the RTFM! docs for the GoPiGo:

They do this in JavaScript where they take an object, even if it’s an instance of another object, or an object’s method, and “rename it” to make it easier to use.

Is this normal?  Is this something any class can do or is this a peculiarity of Python and/or JavaScript?

import everything from file thingx.py (into the “thingx” namespace):

import thingx

mybot.thingx = thingx.ThingX()   # instantiates ThingX class from inside the thingx.py module
                                # put the resultant ThingX object in a variable thingx attached to the mybot object.

or (import a specific thing from thingx into the current namespace):

from thingx import ThingX

mybot.thingx = ThingX()  #  instantiate a ThingX class object, put it in a variable attached to mybot
            

The dot on the left is used to say:
In the mybot namespace, create and attach a thingx variable (Everything in Python is an object so it is a “variable object”)

The dot on the right side of thingx.ThingX() is used to say:
From the thingx module (“namespace”), create a ThingX class object

The = sign puts the result of the right into the variable on left

2 Likes

That is python code - don’t confuse it with JavaScript …oh I forgot JavaScript is so confused it warps the mind of anyone that tries to code with it.

Theoretically, yes you can do that kind of stuff where a variable is actually a function, e.g.:

def myfunc():
print(“This is myfunc()”)

reallymyfunc = myfunc # note the lack of parens
reallymyfunc() # executes myfunc()

BUT I don’t think that works with class methods because you don’t have that class object, only the method, but you could give it a try and see if it works. I don’t think it is a good organization because a casual python reader won’t expect it.

class MyObj():
   def myfunc():
      print("MyObj.myfunc()")

myobj = MyObj()

mof = myobj.myfunc   # I don't know if that is ok

mof()    # I don't know if that will work.

If you want to put everything together, build a superclass called MySuperRobot() that inherits from all the classes and pass the Superbot to helper functions:

import * from my_easygopigo3
import * from my_SSE_Stuff
import * from my_includes
import * my_helper_class

class MySuperRobot(MyEasyGoPiGo3, My_Helper_Class):
    def __init__:
            init super MyEasyGoPiGo3
            init super My_Helper_Class


mysuperbot = MySuperBot()        # classes are initial capped in my tiny world

mysuperbot.where_am_i_going()           # methods are lowercase in my world

and for helper functions, pass mysuperbot to them:

def send_battery_voltage(bot):
      serversend(bot.volt())

so in the code:

send_battery_voltage(mysuperbot)

myobj.not_my_obj_func() would just confuse me to no end, but
not_my_obj_func(myobj) tells me it is a function that operates on a myobj or uses something in myobj to accomplish its task, and
myobj.myobj_method() tells me the details are in the MyObj() class

Now finding where non-class helper functions are defined is a mystery

import * from nonclasshelperfunctions

but that is what find . "nonclasshelperfunc" is for.

2 Likes

(head banging. . .)

I think that maintaining separate names would be easier and I just have to deal with it.

I suspect that this is a lot like my power supply project where I mentioned that two ground points were “connected but not identical” and Keith said “say What?!

I’ve been doing that kind of stuff for decades and decades and, (to me), it’s trivially easy to understand.  If you haven’t been doing that for a while, it may seem like trying to read Chinese with a fun-house mirror!

So it is with me.

I’m trying to “wrap my arms around” all this class stuff before I start writing code, so that I can make intelligent decisions instead of painting myself into a corner.

What with signatures and everything else you mentioned - especially when overloading - I’m not even sure how I’m going to write the init part yet.

Part of the problem I faced with the initial implementation of the New Remote Camera Robot project was a lack of knowledge, (Hosea 4:6, “My people are destroyed [because of] a lack of knowledge”), and I end up making sub-optimal choices and/or I paint myself into a corner.

I really want to avoid that as much as possible with this re-write.  This is why I am asking the questions now instead of writing code to try things out.  (i.e.  Throwing stuff at the wall to see if it sticks.)

IMHO, it’s like looking at something on MapQuest/Google Maps instead of listening to five different people giving you directions - you get the correct answer, all at once, once only, and can implement it directly.

Maybe if you can explain the way to “repackage” all the classes, (or at least some of the classes), in a simpler way, I will understand better.

I am really sorry I am being such an idiot, and I am sure it frustrates you to try to push electricity through a solid rubber rod, but I am really reading this stuff, looking on line, and trying to make sense of it all.

1 Like

Hold on a 'sec - let me see if I have this right. . .

Does import * [. . .] mean “import all the classes from [. . .]” or “import all the methods from [. . .]”?

If it means “methods”, then I could do something like that for each of my classes and then instantiate the super-class and refer to the methods by dot-name such as:

class Superclass(. . .)
[init stuff]

import * from my_easygopigo3
import * from my_SSE_Stuff
import * from my_includes
import * from my_helper_class

[other stuff]

my_bot = Super_Class(. . .)  as noted above and therefore I could then write

    my_bot.set_servo(. . .)
    my_bot.Shake_Head(. . .)
    my_bot.Where_Am_I_Going(. . .)
    my_bot.Send_Battery_Voltage(. . .)

Am I reading that correctly?

P.S.

Supposedly there are a lot of PEP’s that say that “import * from. . .” is a Bad Idea, and Guido Himself will put an ancient Chinese Curse on you if you do."

However, I find it difficult to take seriously suggestions from anyone who would decide to name a programming language after a bunch of British buffoons.

===============

Update:

Check me on this.

If I am understanding this, (Python Modules - GeeksforGeeks), correctly, then the answer to

. . . is a resounding  “it depends”.

  1. If [. . . .] is a module containing methods, (and I think a bunch of def() functions is sufficient), then import * imports all the named, (non private), methods/functions

  2. If [. . . .] is a module containing classes, it imports all the classes along with their associated methods.  (And the methods are still contained within their classes as class.method?)

  3. If [. . . .] is a package, it imports all the package’s modules.  (I think)

  4. If [. . . .] is a module containing both functions and classes, I’m not sure what happens.

  5. Just to add insult to injury, apparently if the module/package/whatever has a __all__ variable or whatever set, it imports the methods, classes, functions, etc., named within __all__, be they public, private, green, blue, purple, or polka-dotted.

I think I am going to have to write some exploratory code to investigate this, but I think that:

  1. import * from easygopigo will import EasyGoPiGo3 as a class.

  2. import * from easygopigo.EasyGoPiGo3 will import all of EasyGoPiGo3’s methods into the current namespace.

  3. import * from easygopigo.EasyGoPiGo3 is potentially dangerous as method/function names within a class are not guaranteed to be unique outside the scope of the class.  (i.e.  Class foo and class bar may both have a method “do_this” that may do entirely different things.)

    • IOW, EasyGoPiGo3() and gopigo3() may have methods that have the same name but do different things.  (i.e.  “do_this” in EasyGoPiGo3 might return your favorite ice-cream flavor, whereas “do_this” in gopigo3 might erase the NOVRAM on your GoPiGo3 robot controller board, rendering it a piece of junk.) - And I don’t know which method wins when you import two methods with the same name.

Who hid my bottle of Excedrin. Prozac?  :wink:

1 Like

No, it is not putting the stuff there, it is making them available without needing the module/namespace qualifier.

2 Likes

Firstly 2 and 3 are syntax errors - no can do, no meaning.

Next, EasyGoPiGo3 is a class, and the import makes the class “available” to be instantiated. That’s all.

I thought you were biting off too much when you launched into your project, but you managed to be successful.

I think refactoring a Python and JavaScript project is again biting off too much, but perhaps if you keep at it it will all click at some point. Python is a crazy programming language that allows unbelievable complexity to be expressed, but the flexibility makes understanding its nuances important to success.

You learn a lot by modifying and extending existing code, but you do not have to understand every line and its purpose.

Refactoring rips apart the structures and requires understanding every line and the myriad of possible constructs that can be used to express the data, objects, and methods.

I don’t feel I can bring all these things into focus. I’m sorry.

2 Likes

Why do they need “repackaging”?

Classes are usually not repackaged. If there is some natural hierarchy then ok, but many systems are quite flat.

Actually not the right thing, should pas bot.volt(), so helper func does not need to understand / depend on bot implementation

2 Likes

. . . and it was named after a troupe of, (IMHO), idiots who didn’t know the limits of decency and respect (/rant), so it’s not surprising that the language shares some of those traits.

Perhaps.

Success, (IMHO), is a question of going outside your comfort zone to find that your “comfort zone” is more elastic than you thought it was.  Again, IMHO, if I leaned something - even if the overall effort pooches - it’s still successful.

You have already accomplished much explaining nuances of working with classes that I had not read online.  You have taught me much.  I would not expect that learning about substrate leakage and the phenomenon of parasitic diodes and transistors in a semiconductor design would be an easy topic to grasp at first glance either.  I am convinced that if I, (as I said), “byte” the bullet and just get busy doing it, I will also learn much.

My understanding of this is that “including” it makes it “visible”.  Instantiating the class makes it “available”.  I’m still not exactly sure what happens programmatically when you instantiate a class, but so long as it works, I’m a happy camper!

I am sure I am using the wrong words to describe what I would like to achieve.

I want to “collapse them into a single naming convention” and I think I know how to do it, but I am not sure.  I will try a few things and see what happens.

You have been a huge help and I appreciate it.

2 Likes