LEARN PYTHON | Methods And Encapsulation

Lesson objective

Use methods and properties to preserve object rules, explain internal-name conventions and avoid exposing mutable state accidentally.

Learn

06.2 | METHODS AND ENCAPSULATION

01 | BEHAVIOUR BELONGS WITH ITS STATE

A robot battery attribute can be assigned any number if every caller edits it freely. The constructor may have checked its initial value, but a later assignment can break the rule. A method can provide a controlled operation such as use_energy(amount).

A method receives self and can read or change that instance’s attributes. Return values and side effects remain separate, as they were for ordinary functions. A method returning None may still have changed the object.

Start by defining an invariant: the battery is an integer from zero through one hundred. Every supported operation must either preserve it or reject the proposal without changing state.

02 | CHECK BEFORE MUTATION

The use_energy method validates the request before subtracting. It rejects unsuitable types, negative amounts and an amount greater than the current battery. It accepts zero because zero consumption preserves the state.

Checking after subtraction would temporarily create an invalid value and require a repair route. Checking first makes a rejected operation easier to reason about and test.

The method returns the remaining battery as useful feedback. It also changes the object. Its contract should mention both, so a caller does not treat it as a pure calculation.

class Battery:
    def __init__(self, level=100):
        if type(level) is not int or not 0 <= level <= 100:
            raise ValueError("Use a whole level from 0 to 100")
        self._level = level

    def use_energy(self, amount):
        if type(amount) is not int or not 0 <= amount <= self._level:
            raise ValueError("Use an amount within the available level")
        self._level -= amount
        return self._level

battery = Battery(80)
print(battery.use_energy(20))

EXPECTED OUTPUT

60

03 | ENCAPSULATION IS A DESIGN BOUNDARY

Encapsulation brings state and its supported operations together behind an interface. Callers use those operations instead of relying on every implementation detail. This lets a component change internally while preserving its public contract.

In Python, a single leading underscore such as _level signals an internal attribute by convention. It does not create enforced privacy. A caller can still deliberately access it, so explain the boundary honestly.

Encapsulation is useful even without absolute access restrictions: it guides ordinary use and puts checks in one place. It is not a security barrier against hostile code in the same process.

04 | A PROPERTY CAN PROVIDE READ ACCESS

A property lets a method be accessed like an attribute. @property is a decorator: syntax asking Python to transform the following definition into a special attribute descriptor. In this example, battery.level runs the getter and returns _level.

Without a setter, ordinary assignment to battery.level raises AttributeError. The implementation can still change _level through its methods. A read-only public view is different from making the entire object immutable.

Do not add parentheses when reading this property: battery.level, not battery.level(). The returned integer is a value, not another callable method.

class Battery:
    def __init__(self, level=100):
        if type(level) is not int or not 0 <= level <= 100:
            raise ValueError("Use 0 to 100")
        self._level = level

    @property
    def level(self):
        return self._level

    def use_energy(self, amount):
        if type(amount) is not int or not 0 <= amount <= self._level:
            raise ValueError("Invalid consumption")
        self._level -= amount

battery = Battery(80)
battery.use_energy(20)
print(battery.level)

EXPECTED OUTPUT

60

05 | USE A SETTER ONLY WHEN ASSIGNMENT FITS

A property setter can validate a public assignment such as thermostat.temperature = 22. It suits a rule where callers intentionally set a new value. An operation like lend(copy_id, member_id, day) needs several checks and is clearer as a method.

A setter uses @temperature.setter after the property getter. Inside it, assign the internal attribute _temperature, not the property itself, which would call the setter again and cause recursion.

Validate the proposal before assigning. Centralising the rule in the setter also lets the initializer use that same supported assignment.

class Thermostat:
    def __init__(self, temperature):
        self.temperature = temperature

    @property
    def temperature(self):
        return self._temperature

    @temperature.setter
    def temperature(self, value):
        if type(value) is not int or not 10 <= value <= 30:
            raise ValueError("Use a whole temperature from 10 to 30")
        self._temperature = value

room = Thermostat(20)
room.temperature = 22
print(room.temperature)

EXPECTED OUTPUT

22

06 | INTERNAL NAMES ARE NOT ENCRYPTION

Two leading underscores, as in __level, trigger name mangling inside a class definition. Python transforms the name to reduce accidental clashes with subclass names. It is not encryption or an absolute ban on access.

For these beginner designs, one underscore and a clear documented interface are enough. Use double underscores only when you understand the inheritance-related purpose.

Do not promise that a property or underscore protects personal data. Security needs separate controls such as access permissions and appropriate storage. This course’s examples use fictional records.

07 | A GETTER CAN STILL LEAK MUTABLE STATE

Returning self._items gives a caller the actual internal list. They can append without using the supported method, bypassing validation. A property does not fix this if its getter returns that same list.

A tuple or shallow copy provides a new outer view, but contained mutable objects can still be shared. For a tuple of immutable strings, that is straightforward. For a tuple of mutable records, you must consider their effects separately.

The library project returns tuple snapshots containing frozen records whose fields are immutable values. That is an intentional design choice; it does not turn every use of tuples into deep protection.

class ReadingList:
    def __init__(self):
        self._titles = []

    def add(self, title):
        if type(title) is not str or not title.strip():
            raise ValueError("Use non-blank text")
        self._titles.append(title.strip())

    def titles(self):
        return tuple(self._titles)

reading = ReadingList()
reading.add("Python Adventures")
snapshot = reading.titles()
reading.add("Robots in Action")
print(snapshot)
print(reading.titles())

EXPECTED OUTPUT

('Python Adventures',)
('Python Adventures', 'Robots in Action')

08 | TEST SUCCESS AND REJECTION

A test of use_energy should inspect the returned value and remaining level. A rejected amount should leave the earlier level unchanged. Test zero, the exact available level, one beyond it, a negative value and a Boolean.

Tests should use the supported interface. They may inspect internals deliberately for a focused diagnostic, but ordinary application code should not depend on those implementation details.

A method name also matters: use_energy describes consumption, while set_level describes replacement. Clear names help a reader predict whether a call has a side effect.

09 | TRY A CONTROLLED BATTERY OPERATION

The session model starts at eighty. Valid requests consume energy; rejected requests leave the level unchanged. Reset starts a fresh battery.

10 | MAKE THE PUBLIC CONTRACT SMALL

Give callers the operations they need and a clear way to observe results. Avoid exposing a mutable internal dictionary just because it is convenient. Each extra supported route is another place where an invariant can be broken.

A well-encapsulated component can change storage later without rewriting every caller. The library can replace its internal dictionaries with another representation while keeping lend, return_book and overdue meaningful.

The next lesson introduces inheritance. Preserving a public contract becomes especially important when a subclass replaces a method that other code expects to use.

PUT YOUR SKILLS TO WORK

Use the Section 6 challenges to practise these ideas independently. Work in your Python editor, predict results and keep a short test record.

Section 6 challenges Python course navigation

MATCH THE PYTHON TERM TO ITS MEANING

Terminology

Terminology

Encapsulation

Keeping state and supported operations behind a clear interface.

Public interface

The operations and observations callers are intended to use.

Internal attribute

State marked as an implementation detail.

Invariant

A condition supported operations should preserve.

Mutation

A change to an existing object’s state.

Side effect

An observable effect beyond a returned value.

Property

An attribute interface implemented through accessor methods.

Getter

Code supplying a property’s value.

Setter

Code handling assignment to a property.

Decorator

Syntax applying a transformation to a following definition.

Read-only property

A property without an ordinary public assignment setter.

AttributeError

An error from unsupported attribute access or assignment.

Leading underscore

A Python convention marking an internal name.

Name mangling

A class-name transformation for certain double-underscore names.

Snapshot

A view of state captured at a particular time.

Shallow copy

A new outer container still referencing its contained objects.

Validation boundary

A point at which proposals must satisfy rules before acceptance.

Contract

The agreed inputs, results, effects and rejection behaviour.

Questions

Questions

CHECK YOUR UNDERSTANDING

Select all correct choices. Each exact set earns one point.

1. What is encapsulation?
2. What does a single leading underscore do?
3. How is a property getter normally read?
4. Which can be true of a method returning None?
5. Why check consumption before subtracting?
6. What is the purpose of double-underscore name mangling?
7. A read-only property returns an internal mutable list. What is possible?
8. Why should a setter assign _temperature rather than temperature?

EXPLAIN AND COMPARE

Write your answers before revealing each sample.

1. Explain encapsulation without claiming absolute privacy.

2. Trace valid energy use.

3. Explain rejection before mutation.

4. Describe a read-only property.

5. Explain a recursive-setter error.

6. Explain name mangling.

7. Evaluate returning an internal list.

8. Design a rejection test.

CODE IT | YOUR INDEPENDENT CHALLENGE

Build a checked battery method and read-only property. Test rejected requests and unchanged state.

Work in your Python editor. Keep a working version, explain one design choice and record at least two tests. Use the Learn tab for worked examples before comparing your result.

Flashcards

Flashcards

Click to flip. Select the ideas you need to revisit.

0 cards selected for revision.

    Selections are kept while this page is open.

    Workbook

    Workbook

    COMING SOON

    The workbook for Methods And Encapsulation is coming soon. Complete the matching task and written questions in your exercise book.