/* Python 3 -- Classes and objects class, __init__, self, instances, methods No em dashes anywhere -- use plain hyphens or rewrite the sentence. Line index comments mark each scenario's 0-based line numbers (blank lines are not counted). */ STEPPER_REGISTRY.register("python-3/classes", { label: 'Classes and objects', panelConfig: { stack: true, heap: true, custom: false, legend: [ { color:'#1d5799', border:'#9dc0e8', label:'Currently running frame' }, { color:'#3d3490', border:'#b0a8e0', label:'Object instance (heap)' }, { color:'#a0540a', border:'#e8c070', label:'Just assigned or created' }, { color:'#095e40', border:'#90d4b8', label:'Holding a value' }, ] }, scenarios: [ /* ------------------------------------------------------------------ */ /* 1. What is a class? */ /* */ /* 0 class Cat: */ /* 1 def __init__(self, name, age): */ /* 2 self.name = name */ /* 3 self.age = age */ /* (blank -- not counted) */ /* 4 def meow(self): */ /* 5 print(self.name + " says: Meow!") */ /* ------------------------------------------------------------------ */ { label: 'What is a class?', lines: [ { code: [['tok-kw','class '],['tok-cls','Cat'],['tok-op',':']] }, { code: [['',' '],['tok-kw','def '],['tok-meth','__init__'],['tok-op','(self, name, age):']] }, { code: [['',' '],['tok-field','self.name'],['tok-op',' = name']] }, { code: [['',' '],['tok-field','self.age'],['tok-op',' = age']] }, { blank: true }, { code: [['',' '],['tok-kw','def '],['tok-meth','meow'],['tok-op','(self):']] }, { code: [['',' '],['tok-meth','print'],['tok-op','('],['tok-field','self.name'],['tok-op',' + '],['tok-str','" says: Meow!"'],['tok-op',')']] }, ], steps: [ { line:0, stack:[], heap:[], expl:{label:'A class is a blueprint', text:'class Cat defines a blueprint. It describes what data a Cat holds and what it can do. No actual Cat exists yet.'} }, { line:1, stack:[], heap:[], expl:{label:'__init__: the constructor', text:'def __init__(self, name, age) is the constructor. Python calls it automatically when you create a new Cat. self refers to the object being built.'} }, { line:2, stack:[], heap:[], expl:{label:'Setting an instance attribute', text:'self.name = name stores the passed-in value on the object itself. Every Cat will have its own name.'} }, { line:3, stack:[], heap:[], expl:{label:'Another instance attribute', text:'self.age = age sets a second attribute. The constructor job is to set up all attributes so the object starts in a valid state.'} }, { line:4, stack:[], heap:[], expl:{label:'A method: the behavior', text:'def meow(self) is a method -- something a Cat can do. self is how the method reaches back to the specific object that called it.'} }, { line:5, stack:[], heap:[], expl:{label:'Using an instance attribute', text:'self.name inside meow() refers to the name of whichever Cat object called the method -- not a fixed value.'} }, { line:5, stack:[], heap:[], expl:{label:'The blueprint is complete', text:'The class definition is done. But we still have zero Cat objects. A blueprint by itself does nothing -- we need to build from it.'} }, ] }, /* ------------------------------------------------------------------ */ /* 2. Creating an instance */ /* */ /* 0 class Cat: # ... */ /* 1 mittens = Cat("Mittens", 3) */ /* ------------------------------------------------------------------ */ { label: 'Creating an instance', lines: [ { code: [['tok-kw','class '],['tok-cls','Cat'],['tok-op',': '],['tok-cmt','# ...']] }, { blank: true }, { code: [['tok-op','mittens = '],['tok-cls','Cat'],['tok-op','('],['tok-str','"Mittens"'],['tok-op',', '],['tok-num','3'],['tok-op',')']] }, ], steps: [ { line:0, stack:[], heap:[], expl:{label:'Two components', text:'The class Cat defines the blueprint. Below it we write top-level code to create an actual object.'} }, { line:1, stack:[{name:'',vars:[{n:'mittens',v:'None',fresh:false,cls:false}],active:true}], heap:[], expl:{label:'Constructor call', text:'Cat("Mittens", 3) calls the constructor. Python allocates space for the object in memory.'} }, { line:1, stack:[{name:'__init__()',vars:[{n:'self',v:'@Cat1',fresh:true,cls:true},{n:'name',v:'"Mittens"',fresh:true,cls:false},{n:'age',v:'3',fresh:true,cls:false}],active:true},{name:'',vars:[{n:'mittens',v:'None',fresh:false,cls:false}],active:false}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'None',fresh:false},{n:'age',v:'None',fresh:false}],active:false}], expl:{label:'__init__ runs', text:'A frame is pushed for __init__(). self points to the new Cat object. Its attributes are not set yet.'} }, { line:1, stack:[{name:'__init__()',vars:[{n:'self',v:'@Cat1',fresh:false,cls:true},{n:'name',v:'"Mittens"',fresh:false,cls:false},{n:'age',v:'3',fresh:false,cls:false}],active:true},{name:'',vars:[{n:'mittens',v:'None',fresh:false,cls:false}],active:false}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:true},{n:'age',v:'3',fresh:true}],active:true}], expl:{label:'Attributes set', text:'__init__ sets self.name = "Mittens" and self.age = 3. The object now has its own data.'} }, { line:1, stack:[{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:true,cls:true}],active:true}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:false}], expl:{label:'Variable holds a reference', text:'__init__() finishes, its frame is popped. mittens now holds a reference pointing to the Cat object in memory -- not the object itself.'} }, { line:1, stack:[{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:false,cls:true}],active:true}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:false}], expl:{label:'Script ends', text:'There is nothing more to run. The object in memory goes away when the script finishes.'} }, ] }, /* ------------------------------------------------------------------ */ /* 3. Two instances */ /* */ /* 0 class Cat: # ... */ /* 1 mittens = Cat("Mittens", 3) */ /* 2 mochi = Cat("Mochi", 5) */ /* ------------------------------------------------------------------ */ { label: 'Two instances', lines: [ { code: [['tok-kw','class '],['tok-cls','Cat'],['tok-op',': '],['tok-cmt','# ...']] }, { blank: true }, { code: [['tok-op','mittens = '],['tok-cls','Cat'],['tok-op','('],['tok-str','"Mittens"'],['tok-op',', '],['tok-num','3'],['tok-op',')']] }, { code: [['tok-op','mochi = '],['tok-cls','Cat'],['tok-op','('],['tok-str','"Mochi"'],['tok-op',', '],['tok-num','5'],['tok-op',')']] }, ], steps: [ { line:0, stack:[{name:'',vars:[],active:true}], heap:[], expl:{label:'Script starts', text:'We have our Cat blueprint. We are going to create two separate Cat objects from it.'} }, { line:1, stack:[{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:true,cls:true}],active:true}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:true},{n:'age',v:'3',fresh:true}],active:false}], expl:{label:'First instance created', text:'Cat("Mittens", 3) builds a Cat object in memory. mittens holds a reference to it. This object has its own name and age.'} }, { line:2, stack:[{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:false,cls:true},{n:'mochi',v:'@Cat2',fresh:true,cls:true}],active:true}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:false},{id:'c2',type:'Cat',ref:'@Cat2',fields:[{n:'name',v:'"Mochi"',fresh:true},{n:'age',v:'5',fresh:true}],active:false}], expl:{label:'Second instance created', text:'Cat("Mochi", 5) builds a completely separate object. mochi holds a reference to it. Same blueprint, totally independent data.'} }, { line:2, stack:[{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:false,cls:true},{n:'mochi',v:'@Cat2',fresh:false,cls:true}],active:true}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:false},{id:'c2',type:'Cat',ref:'@Cat2',fields:[{n:'name',v:'"Mochi"',fresh:false},{n:'age',v:'5',fresh:false}],active:false}], expl:{label:'Two objects, one blueprint', text:'Both mittens and mochi are Cats, but they are distinct objects. Changing Mittens name would have zero effect on Mochi. Each instance owns its data.'} }, { line:2, stack:[{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:false,cls:true},{n:'mochi',v:'@Cat2',fresh:false,cls:true}],active:true}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:false},{id:'c2',type:'Cat',ref:'@Cat2',fields:[{n:'name',v:'"Mochi"',fresh:false},{n:'age',v:'5',fresh:false}],active:false}], expl:{label:'Script ends', text:'There is nothing more to run.'} }, ] }, /* ------------------------------------------------------------------ */ /* 4. Calling instance methods */ /* */ /* 0 class Cat: # ... */ /* 1 mittens = Cat("Mittens", 3) */ /* 2 mochi = Cat("Mochi", 5) */ /* 3 mittens.meow() # "Mittens says: Meow!" */ /* 4 mochi.meow() # "Mochi says: Meow!" */ /* ------------------------------------------------------------------ */ { label: 'Calling instance methods', lines: [ { code: [['tok-kw','class '],['tok-cls','Cat'],['tok-op',': '],['tok-cmt','# ...']] }, { blank: true }, { code: [['tok-op','mittens = '],['tok-cls','Cat'],['tok-op','('],['tok-str','"Mittens"'],['tok-op',', '],['tok-num','3'],['tok-op',')']] }, { code: [['tok-op','mochi = '],['tok-cls','Cat'],['tok-op','('],['tok-str','"Mochi"'],['tok-op',', '],['tok-num','5'],['tok-op',')']] }, { blank: true }, { code: [['tok-op','mittens.'],['tok-meth','meow'],['tok-op','()'],['',' '],['tok-cmt','# "Mittens says: Meow!"']] }, { code: [['tok-op','mochi.'],['tok-meth','meow'],['tok-op','()'],['',' '],['tok-cmt','# "Mochi says: Meow!"']] }, ], steps: [ { line:0, stack:[{name:'',vars:[],active:true}], heap:[], expl:{label:'Script starts', text:'We have the Cat blueprint. We will create two instances and call a method on each.'} }, { line:1, stack:[{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:true,cls:true}],active:true}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:false}], expl:{label:'mittens created', text:'mittens now points to a Cat object with name "Mittens" and age 3.'} }, { line:2, stack:[{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:false,cls:true},{n:'mochi',v:'@Cat2',fresh:true,cls:true}],active:true}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:false},{id:'c2',type:'Cat',ref:'@Cat2',fields:[{n:'name',v:'"Mochi"',fresh:false},{n:'age',v:'5',fresh:false}],active:false}], expl:{label:'mochi created', text:'mochi now points to a separate Cat object with name "Mochi" and age 5. Two objects, both from the same class.'} }, { line:3, stack:[{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:false,cls:true},{n:'mochi',v:'@Cat2',fresh:false,cls:true}],active:true}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:false},{id:'c2',type:'Cat',ref:'@Cat2',fields:[{n:'name',v:'"Mochi"',fresh:false},{n:'age',v:'5',fresh:false}],active:false}], expl:{label:'Calling meow() on mittens', text:'mittens.meow() -- the dot means "call this method on that specific object." Python follows the reference in mittens to find the right object.'} }, { line:3, stack:[{name:'meow() on @Cat1',vars:[],active:true},{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:false,cls:true},{n:'mochi',v:'@Cat2',fresh:false,cls:true}],active:false}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:true},{id:'c2',type:'Cat',ref:'@Cat2',fields:[{n:'name',v:'"Mochi"',fresh:false},{n:'age',v:'5',fresh:false}],active:false}], expl:{label:'meow() runs on Mittens', text:'Python enters meow() with the Mittens object as self. Inside the method, self.name is "Mittens". It prints "Mittens says: Meow!"'} }, { line:4, stack:[{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:false,cls:true},{n:'mochi',v:'@Cat2',fresh:false,cls:true}],active:true}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:false},{id:'c2',type:'Cat',ref:'@Cat2',fields:[{n:'name',v:'"Mochi"',fresh:false},{n:'age',v:'5',fresh:false}],active:false}], expl:{label:'Now calling meow() on mochi', text:'mochi.meow() calls the exact same method code, but this time on the Mochi object. Python follows the reference in mochi.'} }, { line:4, stack:[{name:'meow() on @Cat2',vars:[],active:true},{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:false,cls:true},{n:'mochi',v:'@Cat2',fresh:false,cls:true}],active:false}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:false},{id:'c2',type:'Cat',ref:'@Cat2',fields:[{n:'name',v:'"Mochi"',fresh:false},{n:'age',v:'5',fresh:false}],active:true}], expl:{label:'Same method, different object', text:'Now self.name inside meow() refers to "Mochi". It prints "Mochi says: Meow!" One method definition, two different results depending on which object called it.'} }, { line:4, stack:[{name:'',vars:[{n:'mittens',v:'@Cat1',fresh:false,cls:true},{n:'mochi',v:'@Cat2',fresh:false,cls:true}],active:true}], heap:[{id:'c1',type:'Cat',ref:'@Cat1',fields:[{n:'name',v:'"Mittens"',fresh:false},{n:'age',v:'3',fresh:false}],active:false},{id:'c2',type:'Cat',ref:'@Cat2',fields:[{n:'name',v:'"Mochi"',fresh:false},{n:'age',v:'5',fresh:false}],active:false}], expl:{label:'Script ends', text:'This is the core idea: one class, many independent instances, each with its own state.'} }, ] }, ] });