The Testing Academy · Class Notes Saturday, 7 November (IST)
Live class · study guide

Python constructors, instance variables and encapsulation

Python OOP continued: the constructor and self, default and parameterized constructors, input inside a constructor, global, instance and local variables, and encapsulation with public, protected and private members, a login check that reads its password from a .env file, and a bank account. Files 123 to 138.

By Pramod Dutta, The Testing Academy. Study notes from the AI Tester Blueprint 4x class, built from the session recording, the class's files 123 to 138 and the 7 November section of the batch's Eraser notes deck, which holds the encapsulation notes and the certification sheet. The files were written in class but not pushed: the batch repository still ends at the 1 November commit, so they were copied, with a hash for each, from the instructor's local copy of the repository, where they are staged together with README notes. Every file was run with Python 3.12 and the output shown is what it prints; the two input programs ran in a terminal with the keys typed one at a time. The two login-check files, 133 and 134, were not run: their result is the one the class saw, and the email and password in file 133 are masked.

Hides every output until you tap Reveal, so you can predict it first.
01

What this class covered

  • Constructors: __init__, self, default and parameterized, files 123 to 126
  • One object per call, input inside a constructor, and a calculator, files 127 to 130
  • Global, instance and local variables, file 131
  • Encapsulation and a plain class, files 132 and 133
  • The password moves into a .env file, file 134
  • Private data behind a method: the bank, files 135 and 136
  • Public, protected and private, and pass, files 137 and 138
  • Tasks and announcements: a certification, no class on Sunday, what is left in Python
  • __init__ is the constructor: Python calls it by itself when an object is created. self is that object, and it is always a method's first parameter.
  • A method written without self fails as soon as it is called on an object, because Python passes the object in as the first argument.
  • A default constructor takes only self; a parameterized one takes values and stores them through self, so every object keeps its own. With only a parameterized constructor, Dog() with no arguments stops with a TypeError.
  • A method that prints and returns nothing gives None when you print its result: call it, do not print it.
  • A global is seen everywhere in the file, a name in the class body is read through self, and a local only inside its own method.
  • One underscore marks a member protected, two make it private: reading a private member from outside raises AttributeError, and the way in is a method of the class.
  • Keep passwords in a .env file that .gitignore keeps out of git, read them with load_dotenv() and os.getenv(), and push only a .env-sample with sample values.
02

Constructors: files 123 to 126

WHAT HAPPENS WHEN AN OBJECT IS CREATED: FILE 126 LINE 22: THE CALL chow = Dog("chow", "mastiff") PYTHON builds a new Dog object and hands it to the constructor THE CONSTRUCTOR RUNS BY ITSELF __init__(self, nameGiven, breedGiven) self = the new object nameGiven = "chow" breedGiven = "mastiff" prints Param C, sets self.name and self.breed THE REFERENCE chow points to the object: name 'chow', breed 'mastiff' LINE 25: A SECOND CALL desi = Dog("rancho", "desi") the same steps build a second object, and self.name is 'rancho' for this one LINE 29: NO ARGUMENTS straydog = Dog() there is no default constructor, so Python stops here: TypeError: Dog.__init__() missing 2 required positional arguments Each call runs the constructor once, with self set to the object that call is building.
The steps behind line 22 of file 126, and what lines 25 and 29 do. Every value and the error are from the file's real run.

Last class started object-oriented programming with classes and objects, files 121 and 122, and this class went on to the constructor. First a recap: a class is like a blueprint, a blueprint to create something, and an object is an instance of the class.

A class without a constructor: file 123. The class keyword, the name of the class, and a colon; this is Python, so no curly braces. model = None is an attribute, because in Python there is nothing called null, as the instructor said: it only has None. And there is no new keyword: you just call the class.

123_Create_Class.pyPython
class MobilePhone:
    model = None

    def talk():
        print("Hello")


ipone = MobilePhone()
ipone.model = "iphone 18 Pro Max"
ipone.talk()
Output
Traceback (most recent call last):
  File "chapter_15_Python_PyTest/ex_18_OOPs_Python/02_Construtor/123_Create_Class.py", line 10, in <module>
    ipone.talk()
TypeError: MobilePhone.talk() takes 0 positional arguments but 1 was given

Is model a function? Not a function, the instructor told a learner: it is a property, an attribute you can access directly, while talk is the function, the behaviour. Line 9 sets the attribute from outside the class.

The missing self. Line 4 defines talk() with no parameters, so line 10 stops with the TypeError above. When you call a method on an object, Python passes the object itself, ipone, as the first argument, and this talk has nowhere to put it. Line 8 is fine: building the object does not call talk. The next file writes its methods with self, and so does the fix below.

With self added on line 4, file 123 runs:

Python
class MobilePhone:
    model = None

    def talk(self):
        print("Hello")


ipone = MobilePhone()
ipone.model = "iphone 18 Pro Max"
ipone.talk()
Output
Hello

The constructor: file 124.

124_Cons.PYPython
class MobilePhone:
    model = None

    def __init__(self):
        print("I will be called, when obj is created!")

    def talk(self):
        print("Normal F(n)")

iphone = MobilePhone()
Output
I will be called, when obj is created!

Line 10 only builds an object, yet the file prints. How did this output come, the instructor asked the class:

Question, file 124. How did this output come, when line 10 only builds an object? And what is self?

Worth practising too: file 123 builds its object on line 8 and fails on line 10. Why there, and not on line 8?

Show the answer

Answer. Because of __init__, the constructor. Init means whenever the object is created: when line 10 creates the object, Python calls __init__ automatically, and that runs line 5. self is the current instance, the object itself. It is always available as the first argument, and through it a method reaches the attributes as well as the other methods, the same as the this keyword in Java. File 123 has no constructor of its own, and building the object does not call talk, so line 8 is fine; line 10 calls talk on the object, Python passes the object in, and a talk with no parameters cannot take it: takes 0 positional arguments but 1 was given.

Default and parameterized: files 125 and 126. A default constructor has no parameters, no arguments, as the instructor put it: only self.

125_Default_Const.pyPython
class Dog:
     # Attributes - Instance variables | Data variables
    name = None
    breed = None
    height = None
    weight = None
    race = None

    def __init__(self):  # Default C - No Parameter
        print("Hi")

It prints nothing: the file defines the class and never builds a Dog, so the constructor never runs. Build two dogs and print their names, and the constructor runs twice, once per object, while both names stay None. A default constructor does not set the value, as the instructor said:

Python
class Dog:
     # Attributes - Instance variables | Data variables
    name = None
    breed = None
    height = None
    weight = None
    race = None

    def __init__(self):  # Default C - No Parameter
        print("Hi")


dog1 = Dog()
dog2 = Dog()
print(dog1.name, dog2.name)
Output
Hi
Hi
None None

A parameterized constructor takes values as well. The first argument is always self; in Python every method's first argument is self, as the instructor said, and self.name lets the constructor set the attribute to whatever value is given:

126_Param_Const.pyPython
class Dog:
     # Attributes - Instance variables | Data variables
    name = None
    breed = None
    height = None
    weight = None
    race = None


    def __init__(self,nameGiven, breedGiven):
        print("Param C")
        self.name = nameGiven
        self.breed = breedGiven

    def bark(self):
        print("Barking! -> ",self.name)


    def talk(self):
        print("talking")

chow = Dog("chow","mastiff")
chow.bark()

desi = Dog("rancho","desi")
desi.bark()

# This will not call the Default PARAM, we don't have defalt c
straydog = Dog()
Output
Param C
Barking! ->  chow
Param C
Barking! ->  rancho
Traceback (most recent call last):
  File "chapter_15_Python_PyTest/ex_18_OOPs_Python/02_Construtor/126_Param_Const.py", line 29, in <module>
    straydog = Dog()
               ^^^^^
TypeError: Dog.__init__() missing 2 required positional arguments: 'nameGiven' and 'breedGiven'
  • Line 22 passes "chow" and "mastiff", so for this dog self.name is chow and self.breed mastiff. The figure at the top of this section follows that call step by step.
  • Line 25 builds a second dog, rancho, of the desi breed, an Indian breed. Each dog barks its own name, because bark() prints self.name.
  • Line 29 calls Dog() with nothing. The comment on line 28 says it will not call a default constructor, because the class does not have one, and the instructor told the class it could ignore that line. Run, the line does more than skip a constructor: Python stops the file there with a TypeError, because __init__ needs both values. Put line 29 behind a # and the file ends cleanly after the same four lines.

It is very similar to Java, the instructor said. A learner asked whether a default constructor is mandatory:

Question from a learner. Is a default constructor mandatory?

Worth practising too: can one class have both a default and a parameterized constructor?

Show the answer

Answer. No. In Python a default constructor is not mandatory, the same as in Java, the instructor answered, but if you have a parameterized constructor, you do not have a default one: Python has restrictions. That is why line 29 of file 126 fails. A class keeps only one __init__: write a second one and it replaces the first, as the README notes staged with the code say, so Dog() with no arguments fails again. The same notes show the way round it, default values: a constructor written as def __init__(self, nameGiven="stray", breedGiven="desi") accepts both Dog() and Dog("chow", "mastiff").

03

One object per call: files 127 to 130

TWO OBJECTS, EACH WITH ITS OWN NAME: FILE 127 THE CLASS class Dog name = None breed = None __init__(self, nameGiven) printName(self) In memory: one object per call OBJECT 1 name = 'bobby' with its own printName() OBJECT 2 name = 'ruks' with its own printName() Dog("bobby") Dog("ruks") chow_ref mow_ref the references The call self is It prints chow_ref.printName() object 1 bobby mow_ref.printName() object 2 ruks The same method, two answers: self is whichever object the method was called on.
Memory, as the class drew it on the iPad: each Dog() call makes its own object. The printed names are the file's real output.

Two objects, two names: file 127. IQ in the file name means interview question, as the instructor told a learner who asked. Work out the output before you open the answer; it was the question to the class:

Interview question, file 127. If you print the name of both dogs, what happens?

Show the answer

Answer. Each object prints its own name: bobby, then ruks. Line 9 builds an object whose self.name is bobby, line 10 another whose self.name is ruks, and printName() prints the name of whichever object it is called on. Wrap the calls in print(), and each name is followed by None, as the instructor's own reading of the output had it, because printName() prints the name and returns nothing.

127_IQ.pyPython
class Dog:
    name = None
    breed = None
    def __init__(self,nameGiven):
        self.name = nameGiven
    def printName(self):
        print(self.name)

chow_ref = Dog("bobby")
mow_ref = Dog("ruks")

chow_ref.printName()
mow_ref.printName()
Output
bobby
ruks

On the iPad, the instructor drew it as memory: two objects are created, each with its own attributes and behaviour, and each with a different reference. When line 9 passes "bobby", it goes to nameGiven and from there to self.name, so for that object self.name is bobby; line 10 does the same with "ruks". Self becomes whichever object you are asking from, as the instructor put it.

The first run in class printed the calls, and None came out as well:

Python
class Dog:
    name = None
    breed = None
    def __init__(self,nameGiven):
        self.name = nameGiven
    def printName(self):
        print(self.name)

chow_ref = Dog("bobby")
mow_ref = Dog("ruks")

print(chow_ref.printName())
print(mow_ref.printName())
Output
bobby
None
ruks
None

That None is the return value. printName() prints by itself and does not return anything, so print() prints the nothing it got back. You do not have to print those, the instructor said: you just have to call them.

Input inside a constructor: file 128. Can we take a user input too? Absolutely we can, the instructor said: in the constructor itself, with input(). Run it via the command, the instructor said, so this one ran in a terminal for these notes, with the keys typed one at a time:

128_IQ.user_input.pyPython
class Person:
    name = None
    age = None
    phone = None
    occupation = None

    def __init__(self):
        print("Let's take the user input, Please share the name,age,phone,occ")
        self.name = input("Enter the name\n")
        self.age = input("Enter the age\n")
        self.phone = input("Enter the Phone\n")
        self.occupation = input("Enter the occupation\n")

    def display_values(self):
        print("Name is ", self.name, "Age is ", self.age, "Phone is", self.phone, "occupation", self.occupation)

amit = Person()
amit.display_values()
Output
Let's take the user input, Please share the name,age,phone,occ
Enter the name
Amit
Enter the age
30
Enter the Phone
999
Enter the occupation
QA
Name is  Amit Age is  30 Phone is 999 occupation QA

Each prompt ends in \n, so every answer goes on the line below it. The four answers are the example ones from the README notes; in class, the instructor typed other answers.

A calculator: the other file 128. A real example: a Calc class with a and b set to None, a default constructor that prints DC, and four methods that take self, a and b and return the result.

128_REAL_Example.pyPython
class Calc:
    a = None
    b = None

    def __init__(self):
        print("DC")

    def sum(self, a, b):
        return a+b

    def sub(self, a, b):
        return a - b

    def mul(self, a, b):
        return a * b

    def div(self, a, b):
        return a / b

a = float(input("Enter the value of a"))
b = float(input("Enter the value of b"))

object_ref = Calc()

output_sum = object_ref.sum(a, b)
output_sub = object_ref.sub(a, b)
output_mul = object_ref.mul(a, b)
output_div = object_ref.div(a, b)
print(output_sum, output_sub, output_mul, output_div)

It cannot be run directly, the instructor said, as this does not support it: type python, drag the file into the terminal, and run it there. With 10 and 10, the values typed in class:

Text
Enter the value of a10
Enter the value of b10
DC
20.0 0.0 100.0 1.0
  • 10 plus 10 is 20, the instructor read out, then pointed to the subtraction, the multiplication and the division.
  • DC comes after both inputs, because the object is built only on line 23.
  • float() turns each answer into a float, which is why every result ends in .0.

There is no file 129: both input programs are numbered 128. Three questions from learners came up here:

  • Why initialize an attribute with None? Without a value it is not a statement in Python, the instructor said: you have to initialize it at least with None.
  • Can it start at zero instead? Zero is a number, so it becomes a number, the instructor said. If you do not want any data type, None is a good option.
  • Why do methods that are not constructors take self? So that they can access the attributes, the instructor said: every method has self as its first parameter.

Same or different: file 130. One more real example, a Baby class:

130_REAL.pyPython
class Baby:
    name : None

    def __init__(self, nameGiven):
        self.name = nameGiven

    def printName(self):
        print(self.name)


b = Baby("gugu")
b2 = Baby("sema")
b.printName()
b2.printName()
Output
gugu
sema

Will the two print the same or different, the instructor asked? Different: each object has its own name.

A colon, not an equals sign. Line 2 is name : None, not name = None. With a colon it is a type annotation: it says what name should hold, and it creates no attribute. The file works anyway, because __init__ sets self.name on every object, but the class itself has no name, as the README notes point out. File 127's name = None does create one: there, Dog.name is None. Two lines added at the end of file 130 show the difference:

Python
class Baby:
    name : None

    def __init__(self, nameGiven):
        self.name = nameGiven

    def printName(self):
        print(self.name)


b = Baby("gugu")
b2 = Baby("sema")
b.printName()
b2.printName()
print(Baby.__annotations__)
print(Baby.name)
Output
gugu
sema
{'name': None}
Traceback (most recent call last):
  File "chapter_15_Python_PyTest/ex_18_OOPs_Python/02_Construtor/130_REAL.py", line 16, in <module>
    print(Baby.name)
          ^^^^^^^^^
AttributeError: type object 'Baby' has no attribute 'name'

Can we define a data type in Python, as in TypeScript, a learner asked? We can, the instructor said. Line 2 of file 130 is one: an annotation is how Python writes a type.

04

Global, instance and local variables: file 131

WHERE EACH NAME CAN BE SEEN: FILE 131 The file g = 10 global: seen everywhere in the file class Person i = 11 written in the class body: a method reads it as self.i METHOD talk(self) METHOD WITH A LOCAL print_infor(self) print(self.i) prints 11 print(g) prints 10 l = 10 a local: this method only print(self.l) AttributeError print(l) prints 10: the fix The file defines the class and builds no object, so it prints nothing. The results above are from a run with an object built and both methods called.
G for global, I for instance, L for local, as the class labelled them. Each result is from a real run of the file with the calls added.

File 131 puts three kinds of variable side by side. Outside the class a variable is global, within the class it is an instance variable, and within a function it is local, as the instructor put it: G for global, I for instance and L for local.

131_IV.pyPython
g = 10 # Global

class Person:
    i = 11 # Instance, Class , Attribute...property
    def print_infor(self):
        l = 10 # local l varaible
        print(self.l)

    def talk(self):
        print(self.i)
        print(g)

It prints nothing: the file defines the class, builds no object and calls nothing. What is the advantage of each, the instructor asked:

Question, file 131. What is the advantage of each kind of variable: global, instance and local?

Show the answer

Answer. Where each one is available. A global variable is available everywhere, an instance variable within the class, and a local variable only within its function, as the instructor answered. The run below shows all three: talk() reaches i through self.i and the global g by its name, while l exists only inside print_infor(), and self.l does not find it.

With an object built and both methods called, talk() works and print_infor() stops:

Python
g = 10 # Global

class Person:
    i = 11 # Instance, Class , Attribute...property
    def print_infor(self):
        l = 10 # local l varaible
        print(self.l)

    def talk(self):
        print(self.i)
        print(g)


p = Person()
p.talk()
p.print_infor()
Output
11
10
Traceback (most recent call last):
  File "chapter_15_Python_PyTest/ex_18_OOPs_Python/03_Instance_Variables/131_IV.py", line 16, in <module>
    p.print_infor()
  File "chapter_15_Python_PyTest/ex_18_OOPs_Python/03_Instance_Variables/131_IV.py", line 7, in print_infor
    print(self.l)
          ^^^^^^
AttributeError: 'Person' object has no attribute 'l'

self.l is not l. Line 6 makes a local variable, l, but line 7 asks the object for an attribute called l, and the object has none: AttributeError. The file as written never calls print_infor(), so it runs clean, and a method's mistake only shows when the method runs. Read the local by its bare name, print(l), and the method prints 10.

Python
g = 10 # Global

class Person:
    i = 11 # Instance, Class , Attribute...property
    def print_infor(self):
        l = 10 # local l varaible
        print(l)

    def talk(self):
        print(self.i)
        print(g)


p = Person()
p.talk()
p.print_infor()
Output
11
10
10
Name Where it is written Read as Result
g line 1, outside the class: global g, in talk() 10
i line 4, in the class body self.i, in talk() 11
l line 6, inside print_infor(): local self.l, as written AttributeError
l line 6, inside print_infor(): local l, the fix 10

Instance or class? The comment on line 4 gives i several names: instance, class, attribute, property. The README notes call it a class attribute: one value, kept on the class and shared by every object, which self.i finds because Python looks on the object first, then on its class. A value set through self, such as self.name in file 126, belongs to that one object.

05

Encapsulation and a plain class: files 132 and 133

Next, encapsulation, and a difference the instructor asked the class not to confuse: encapsulation versus abstraction. Both of them are hiding, but they hide different things, the instructor said. Encapsulation hides the instance variables, the data members, from the outside: they are not allowed outside, only in the presence of a behaviour, a method. Encapsulation happens within the class, and abstraction happens between classes.

Then a 10-minute break, with a task for it:

Task, set before the break. Research encapsulation and abstraction.

Show the answer

Answer. After the break, the instructor went through the deck's four points: encapsulation binds the data variables to the methods, so that the data variables can be accessed only through the methods, which are defined within the class. Abstraction, as the instructor said before the break, happens between classes, while encapsulation happens within one.

The deck's four points open and close with these two:

Bind the data variables with the methods.

Wrapping or binding the instance variables / data variables with the methods - Encapsulation.

In between, it says that the instance variables, the data variables, can be accessed only by the methods, and that a method is a def function within the class. How do you hide them? A normal variable is public by default, the instructor said; with one underscore it becomes protected, and with two underscores, private.

A normal class: file 132. This is not an encapsulation example, just a normal class, as the instructor said. Nothing is marked, so everything is public, and everyone can access it. The o_ at the start of the parameter names marks a value given by the user.

132.Normal_Class.pyPython
class Car:
    name: None
    make: None
    model: None

    def __init__(self, o_name, o_make, o_model):
        self.name = o_name
        self.make = o_make
        self.model = o_model

    def start_engine(self):
        print("Starting a car with the name " + self.name)
        print("Starting a car with the make " + self.make)
        print("Starting a car with the model " + self.model)



lambo = Car("Lambo", "V6", "2023")
lambo.start_engine()

mg_hector = Car("Hector","1.5+ Turbo","2024")
mg_hector.start_engine()
Output
Starting a car with the name Lambo
Starting a car with the make V6
Starting a car with the model 2023
Starting a car with the name Hector
Starting a car with the make 1.5+ Turbo
Starting a car with the model 2024

Lines 2 to 4 use the same colon as file 130: annotations, not attributes. Here, too, the constructor sets every value through self.

Without encapsulation: file 133. A class for the VWO login page, as the instructor put it. The constructor keeps the email and the password as instance variables, and login_confirm() compares them with the values written on line 7, which are masked here.

133_WEcap.pyPython
class VWOLoginPage:
    def __init__(self,email_arg,password_arg):
        self.email = email_arg
        self.password = password_arg

    def login_confirm(self):
        if self.email == "<email>" and self.password == "<password>":
            print("Allowed to Login")
        else:
            print("Login Failed")


email = input("Enter the vwo login email ")
password = input("Enter the vwo login password ")

vwo_object_ref = VWOLoginPage(email,password)
vwo_object_ref.login_confirm()

This file was not run for these notes. In class, the email and password on line 7 printed Allowed to Login, and a wrong email printed Login Failed. But the problem, the instructor said, is that the values are hardcoded.

06

The password moves into .env: file 134

THE PASSWORD MOVES OUT OF THE CODE: FILE 134 PUSHED TO GITHUB .env-sample USERNAME=sample sample values only NEVER PUSHED .env listed in .gitignore a copy, your values LINE 11 load_dotenv() reads .env into the environment LINE 12 os.getenv(...) "USERNAME", "PASSWORD" returns each value THE CHECK login_confirm() compares the typed email and password with them, then prints "Allowed, Login Sucess" or "Login Failed" BUT LINE 23 STILL WORKS print(vwo_object_ref.password) self.password is public, so code outside the class can print it: the next problem No password in the code any more, but the object still hands it to anyone: that is where encapsulation comes in.
The two lines of .env-sample are what gets pushed; the real values stay on your machine. File 134 was not run for these notes.

Without encapsulation, but secure, as the instructor put it: everything stays the same, except that the values the check expects move out of the code into a .env file. Reading that file needs a utility you install from the terminal; the README notes give the command as pip install python-dotenv. Then keep a .env file with the username and the password in it. This is much more secure, the instructor said, because now there is no hardcoded value. Call load_dotenv(), and os.getenv(), from the operating system module, fetches each value.

134_WEcap_Secure.pyPython
from dotenv import load_dotenv
import os
class VWOLoginPage:

    def __init__(self, email_arg, password_arg):

        self.email = email_arg
        self.password = password_arg

    def login_confirm(self):
        load_dotenv()
        if self.email == os.getenv("USERNAME") and self.password == os.getenv("PASSWORD"):
            print("Allowed, Login Sucess")
        else:
            print("Login Failed")


email = input("Enter the vwo login email ")
password = input("Enter the vwo login password ")

vwo_object_ref = VWOLoginPage(email,password)
vwo_object_ref.login_confirm()
print(vwo_object_ref.password)

print(os.name)

You will not commit the .env file. The class added it to .gitignore: password-related things are not going to be pushed, and the file is not going to be uploaded on GitHub, the instructor said. What you get instead is .env-sample, with sample values, and you just have to remove the sample, as the instructor put it. The README notes' way: copy .env-sample to .env, and put your own values in the copy.

.env-sampleText
USERNAME=sample
PASSWORD=sample_password

File 134 was not run for these notes either. In class, a wrong email and password failed the login, and yet the password could still be accessed, the instructor said: it reads vwo_object_ref.password from outside the class, and it can, because the attribute is public. This is where encapsulation comes in, the instructor said.

On Windows, rename the keys. The README notes flag a catch. Windows already sets a USERNAME variable, your Windows user name, and load_dotenv() does not overwrite a variable that is already set. So on Windows, line 12 compares the email with your user name, and the right email still prints Login Failed. The notes' fix is load_dotenv(override=True), or a key name the system does not use, such as VWO_USERNAME. A run of that catch with the class's sample values as .env:

Python
import os
from dotenv import load_dotenv

os.environ["USERNAME"] = "windows-user"   # already set, as Windows sets it
load_dotenv()                             # .env holds USERNAME=sample
print(os.getenv("USERNAME"))
load_dotenv(override=True)
print(os.getenv("USERNAME"))
Output
windows-user
sample
07

Private data behind a method: files 135 and 136

Two panels on cream paper showing the same bank counter. Left, titled Balance, with the sublabel Public: no glass, and an open passbook with a stack of coins lies on the counter in full view, with dashed sight lines reaching it. Right, titled Account number, with the sublabel Private: through a method: a glass partition, a card in a small locked box behind it, a dashed sight line that stops at the glass, and a terracotta arrow that carries the card out only through the small tray under the glass.
The bank from file 136: anyone can see the public balance, and the private account number comes out only through the class's own method, as through the cashier's window.

Can you access a public member from outside the class? Absolutely yes, the instructor said. And a private one? Absolutely no. File 135 shows both: it reads a public attribute from outside, then tries a private one on line 19 and stops with an AttributeError. If you want to access a private member, there is only one way, the instructor said: a method of the class, called through the object reference. Two methods of file 135, on lines 11 to 15, print the private value from inside the class.

What error does it throw, a learner asked:

Question from a learner. What error does Python throw when you try to access a private variable from outside the class?

Worth practising too: is a double-underscore attribute really hidden?

Show the answer

Answer. Something like not found, the instructor answered, and then showed it: AttributeError, no attribute. Line 23 of file 136, switched on below, gives AttributeError: 'Bank' object has no attribute '__account_number'. Run on Python 3.12, the message ends with a hint, Did you mean: '_Bank__account_number'?, which gives the game away: Python renames a double-underscore attribute to _Bank__account_number, which the README notes call name mangling. It prevents accidents, not determined access, as the same notes put it.

The bank: file 136. The best example, the instructor said, one that gives 100% clarity about encapsulation:

136_Ecap.pyPython
class Bank:

    def __init__(self, account_number, balance):
        self.balance = balance  # public
        self.__account_number = account_number  # Private

    def check_balance(self):
        print(self.balance)

    def deposit(self, amount):
        self.balance = self.balance + amount

    def show_me_account_number(self, is_auth):
        if is_auth == True:
            print(self.__account_number)
        else:
            print("Not Allowed!")


icici = Bank(9876543210, 100)
icici.deposit(100)
icici.check_balance()
# print(icici.__account_number)
# if you are cashier you can see the a/c becoz of the ecapsulation.
icici.show_me_account_number(True)
Output
200
9876543210
  • The balance is public, so everyone can see it, as the instructor put it. deposit() adds 100 to the starting 100, and line 22 prints 200.
  • The account number is private: two underscores. Only you can see it, or probably the cashier, the instructor said, and the comment on line 24 says the same of the code.
  • show_me_account_number() is a public method, and it can read the private variable because it is inside the class. That is encapsulation: within the class, this method can access a data variable that is private.

Line 23, switched on, tries the account number from outside the class, and stops:

Python
class Bank:

    def __init__(self, account_number, balance):
        self.balance = balance  # public
        self.__account_number = account_number  # Private

    def check_balance(self):
        print(self.balance)

    def deposit(self, amount):
        self.balance = self.balance + amount

    def show_me_account_number(self, is_auth):
        if is_auth == True:
            print(self.__account_number)
        else:
            print("Not Allowed!")


icici = Bank(9876543210, 100)
icici.deposit(100)
icici.check_balance()
print(icici.__account_number)
# if you are cashier you can see the a/c becoz of the ecapsulation.
icici.show_me_account_number(True)
Output
200
Traceback (most recent call last):
  File "chapter_15_Python_PyTest/ex_18_OOPs_Python/04_Encapsulation/136_Ecap.py", line 23, in <module>
    print(icici.__account_number)
          ^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'Bank' object has no attribute '__account_number'. Did you mean: '_Bank__account_number'?

Interview question, file 136. Can you access the balance from outside the class? Can you access the account number?

Worth practising too: what does icici.show_me_account_number(False) print, and who decides whether is_auth is true?

Show the answer

Answer. The balance, yes: it is public. The account number, no: line 23 switched on stops with AttributeError, and the way in is the public method, show_me_account_number(). This is the number one interview question, the instructor said, one that will definitely come up in your interviews. With False, the method prints Not Allowed!. And the caller decides: it passes is_auth itself, so True always gets in, as the README notes point out.

08

Public, protected and private: files 137 and 138

THREE ACCESS LEVELS, FROM OUTSIDE AND FROM INSIDE: FILE 138 From outside the class obj = TestExample() PUBLIC, NO UNDERSCORE self.driver "Chrome" obj.driver prints Chrome PROTECTED, ONE UNDERSCORE self._config "STAG" obj._config prints STAG, though discouraged PRIVATE, TWO UNDERSCORES self.__api__key "ABC12345" obj.__api__key AttributeError INSIDE THE CLASS show() reads all three: Driver: Chrome Config: STAG API Key: ABC12345 work() calls the two private methods, which only pass Private members are reached through the class's own methods, such as show() and work().
Every result is a real run: show() as the file calls it, and the three commented lines at the end of the file switched on.

The deck's three lines on the access levels:

Public: Variable - Don't Mention anything - anyone can access.

Protected, one underscore: Members are intended for internal use within the class and its subclasses.

Private, two underscores: Members are only accessible within the class they are defined in.

File 137 puts one member at each level into a Home class, with one method that reads the private member and a private method of its own. In a nutshell, the instructor said, remember this: private variables will be accessible only through a public function.

File 137's methods sit inside its constructor. Its two methods are indented one level too far, inside __init__, so they are local functions of the constructor, not methods of the class, as the README notes point out. The file builds an object and calls nothing, so it runs clean, but calling either method on the object fails with AttributeError. Line them up with def __init__ and they work.

A test example: file 138. A test case running on Chrome: the driver, Chrome, is public in nature, the configuration, a stage, is protected, and the API key is private, as the instructor described it.

138_PPP.pyPython
class TestExample:
    def __init__(self):
        self.driver = "Chrome"
        self._config = "STAG"
        self.__api__key = "ABC12345"

    def show(self):
        print(f"Driver: {self.driver}")
        print(f"Config: {self._config}")
        print(f"API Key: {self.__api__key}")


    def __private_method1(self):
        pass

    def __private_method2(self):
        pass

    def work(self):
        self.__private_method1()
        self.__private_method2()


obj = TestExample()
obj.show()
obj.work()


# Access levels:
# print(obj.driver)          # ✅ Public - accessible
# print(obj._config)         # ⚠️ Protected - accessible but discouraged
# print(obj.__api__key)     # ❌ Private - AttributeError
Output
Driver: Chrome
Config: STAG
API Key: ABC12345
  • show() reads all three, because it is inside the class.
  • __private_method1 and __private_method2 are private methods, and work(), a normal method, can call them: absolutely yes, the instructor said.
  • Lines 30 to 32 note what outside code gets. Switched on:
Python
class TestExample:
    def __init__(self):
        self.driver = "Chrome"
        self._config = "STAG"
        self.__api__key = "ABC12345"

    def show(self):
        print(f"Driver: {self.driver}")
        print(f"Config: {self._config}")
        print(f"API Key: {self.__api__key}")


    def __private_method1(self):
        pass

    def __private_method2(self):
        pass

    def work(self):
        self.__private_method1()
        self.__private_method2()


obj = TestExample()
obj.show()
obj.work()


# Access levels:
print(obj.driver)          # ✅ Public - accessible
print(obj._config)         # ⚠️ Protected - accessible but discouraged
print(obj.__api__key)     # ❌ Private - AttributeError
Output
Driver: Chrome
Config: STAG
API Key: ABC12345
Chrome
STAG
Traceback (most recent call last):
  File "chapter_15_Python_PyTest/ex_18_OOPs_Python/04_Encapsulation/138_PPP.py", line 32, in <module>
    print(obj.__api__key)     # ❌ Private - AttributeError
          ^^^^^^^^^^^^^^
AttributeError: 'TestExample' object has no attribute '__api__key'

The configuration is accessible, but discouraged, the instructor said, and the private one is not available at all.

Protected is a convention. The instructor added that protected is available within the same package and not outside it. Python itself does not enforce that: one underscore only asks other code to keep out. Line 31 reads _config from outside the class and prints STAG, and code in a file in another folder can read it the same way. The deck's line puts it as intended for internal use within the class and its subclasses.

Question, file 138. What is the pass keyword?

Show the answer

Answer. pass means the function has no definition: it is just there, empty, as the instructor put it. Lines 13 to 17 use it for the two private methods, so work() calls them and nothing happens, which is why line 26 prints nothing.

And the difference between protected and private? Private is within the class only, the instructor said; protected is within the package, the same folder, where several classes can exist; and public is everywhere. Of the three, only the double underscore changes what Python does, by renaming the member, as the runs above show.

09

Tasks and announcements

  • A certification, now. If you have not done AI Fluency: Framework & Foundations, do it first: watch the 52-minute video and get the certification. If you have done it, pick any other certification from the list that you have not done. Then put it on LinkedIn, and you can tag the instructor. The instructor wanted it done right now.
  • Not Claude Code in Action. The batch had not done that one, the instructor thought, and said it would come off the list, marked red.
  • Recordings. They are already in the SDET Club. Is there a recording for all 10, a learner asked? For most of them, the instructor thought. Meeti was asked to put an announcement and the certification information in the SDET Club, and, if possible, to add the recording links to the sheet.
  • The sheet. The deck's 7 November section links the certification sheet. Its first tab lists ten free Anthropic courses with a status for each.
  • No class on Sunday, the next day, for Diwali. Enjoy it, the instructor said.
  • The coming week. There will be a class, so do not go, as the instructor put it. It might be a certification class, and the instructor will let you know: the plan is to pick one or two certifications from the list and try to do them, such as AI capabilities, subagents or the Claude API. After Diwali, the instructor said, the batch will target subagents, building with Claude, the advanced Model Context Protocol course and AI capabilities, and more. The sheet names them Introduction to Subagents, Building with the Claude API, Model Context Protocol: Advanced Topics, and AI Capabilities and Limitations.
  • What is left in Python: the rest of OOP, then pytest. After that come CrewAI, DeepEval, LangChain and LangGraph. Can DeepEval come first, a learner asked? It can, after CrewAI, the instructor said: CrewAI first, then DeepEval, then LangChain and LangGraph.
  • GitHub Copilot. Is there a free certification for it, a learner asked? As of now, no, the instructor said.

Task: a certification. Finish the AI Fluency certification if you have not done it; if you have, do one you have not done. Post it on LinkedIn.

Show the answer

Answer. AI Fluency: Framework & Foundations comes first, from its 52-minute video. After that, any certification on the list you have not done, except Claude Code in Action, from the recordings in the SDET Club. The certificate goes on LinkedIn, where you can tag the instructor.