PythonPython · Chapterبەش 11 · Lectureوانە 11.3

Encapsulation and Propertiesکەپسوولکردن و خەسڵەتە بەڕێوەبراوەکان

Why a class hides its data: an invariant such as "a balance is never negative", Python's underscore conventions and what name mangling really does, getters and setters as methods and as @property with @name.setter, read-only and computed properties, validation that raises ValueError, abstraction (show what, hide how), a BankAccount worked example, common mistakes and exercises.

بۆچی دەشارێتەوە: وەک «باڵانس هەرگیز نەرێنی نابێت»، ڕێککەوتنەکانی لە و ئەوەی بەڕاستی دەیکات، و وەک و وەک @property لەگەڵ @name.setter، کە تەنیا دەخوێنرێنەوە یان دەژمێردرێن، کە ValueError دروست دەکات، (پیشاندانی چی، شاردنەوەی چۆن)، نموونەیەکی تەواوی BankAccount، هەڵە باوەکان و ڕاهێنان.

19 min read18 خولەک خوێندنەوە · 16 stepsهەنگاو

What you will learnچی فێر دەبیت

  • Explain why a class hides its data, using an invariant such as "the balance is never below 0".
  • Use Python's naming conventions, _name and __name, and say what each one really does.
  • Write getters and setters, first as methods and then as properties with @property and @name.setter, including read-only and computed properties.
  • Validate values in a setter, raise ValueError, and keep the rule true from __init__ on.
  • Explain abstraction and design a small class with a clear public face.
  • ڕوون بکەیتەوە بۆچی دەشارێتەوە، بە وەک «باڵانس هەرگیز لە 0 کەمتر نابێت».
  • ڕێککەوتنەکانی ناونانی ، _name و __name، بەکار بهێنیت و بڵێیت هەر یەکەیان بەڕاستی چی دەکات.
  • و بنووسیت، سەرەتا وەک و پاشان وەک بە @property و @name.setter، لەوانەش ئەوانەی تەنیا دەخوێنرێنەوە یان دەژمێردرێن.
  • لە بپشکنیت، ValueError دروست بکەیت و یاساکە لە __init__ـەوە ڕاست بهێڵیتەوە.
  • ڕوون بکەیتەوە و بچووک بە ڕوویەکی گشتیی ڕوون دیزاین بکەیت.

Why hide an object's data?بۆچی دراوەی ئۆبجێکتێک بشارینەوە؟

In Classes and Objects and Attributes and Methods you made objects whose attributes any code can read and change. That is fine for a student's name, but dangerous for money. In the bank account challenge a function kept the rule "at least 10,000 dinars". Now the account is an object. Watch what happens when every attribute is open:

لە کلاسەکان و ئۆبجێکتەکان و خەسڵەتەکان و میتۆدەکان دروست کرد کە هەر کۆدێک دەتوانێت بخوێنێتەوە و بیانگۆڕێت. ئەمە بۆ ناوی قوتابییەک کێشە نییە، بەڵام بۆ پارە مەترسیدارە. لە ڕاهێنانی هەژماری بانکی یاسای «لانیکەم ⁦10,000⁩ دینار»ـی دەپاراست. ئێستا هەژمارەکە . سەیر بکە کاتێک هەموو کراوە بێت چی ڕوو دەدات:

Any code can change the balanceهەر کۆدێک دەتوانێت باڵانس بگۆڕێتopen_balance.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner, balance):
        self.owner = owner
        self.balance = balance          # any code can change this later


acc = BankAccount("Aram", 50_000)
acc.balance = acc.balance - 80_000      # takes more than there is
print(acc.owner, "has", acc.balance, "dinars")

acc.balance = "a lot"                   # not even a number any more
print(acc.balance + 1_000)
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 8

Nothing checks the new value: the balance is now −30,000.

هیچ شتێک ناپشکنێت: باڵانس ئێستا ⁦−30,000⁩ـە.

Lineدێڕی 11

Not even a number: the program crashes later, on another line.

تەنانەت ژمارەش نییە: دواتر و لە دێڕێکی تردا دەوەستێت.

Two mistakes, and Python stopped neither of them. The first one quietly broke a bank rule. The second one crashed the program later, far from the line that caused it. The cure is encapsulation: keep the data inside the object, and let other code change it only through methods that check every change.

دوو هەڵە و ڕێگەی لە هیچیان نەگرت. یەکەمیان بە بێدەنگی یاسایەکی بانکی شکاند. دووەمیان دواتر وەستاند، دوور لەو دێڕەی هۆکارەکەی بوو. چارەسەر : لە ناو بهێڵەوە و ڕێگە بە کۆدی تر بدە تەنیا لە ڕێگەی ئەو بیگۆڕێت کە هەموو گۆڕانێک دەپشکنن.

Two panels. Left, a plain attribute: an object acc with owner Aram and balance changed from 50000 to -30000 and then to the text a lot, by two lines of outside code; nothing stops a wrong value. Right, behind methods: the object keeps _balance = 50000 behind deposit() and withdraw(); acc.withdraw(80000) is refused with ValueError because 80000 is more than 50000, and the rule balance ≥ 0 stays true.دوو بەش. چەپ، خەسڵەتێکی سادە: ئۆبجێکتی acc بە خاوەنی Aram و باڵانسێک کە دوو دێڕ کۆدی دەرەوە لە 50000ـەوە دەیکەن بە -30000 و پاشان بە دەقی a lot؛ هیچ شتێک ڕێگە لە نرخێکی هەڵە ناگرێت. ڕاست، لە پشت میتۆدەکانەوە: ئۆبجێکتەکە _balance = 50000 لە پشت deposit() و withdraw() هەڵدەگرێت؛ acc.withdraw(80000) بە ValueError ڕەت دەکرێتەوە چونکە 80000 لە 50000 زیاترە و یاسای باڵانس ≥ 0 ڕاست دەمێنێتەوە.
Open attributes accept anything; methods can say no. هەموو شتێک وەردەگرن؛ دەتوانن بڵێن نەخێر.CêIT

A rule that is always true: the invariantیاسایەک کە هەمیشە ڕاستە: مەرجی نەگۆڕ

An invariant is a rule about an object's data that must be true whenever other code looks at the object. Every class with data has a few:

یاسایەکە لەبارەی کە دەبێت هەر کاتێک کۆدی تر سەیری دەکات ڕاست بێت. هەر کە هەبێت چەند دانەیەکی هەیە:

  • A bank balance is never below 0.
  • A mark is between 0 and 100.
  • A temperature is never below −273.15 °C (absolute zero).
  • An owner's name is never empty.
  • باڵانسی هەژماری بانکی هەرگیز لە 0 کەمتر نابێت.
  • نمرە لە نێوان 0 و ⁦100⁩دایە.
  • پلەی گەرمی هەرگیز لە ⁦−273.15 °C⁩ (سفری ڕەها) نزمتر نابێت.
  • ناوی خاوەن هەرگیز بەتاڵ نابێت.

Encapsulation puts the data and the methods that use it together in one class, and lets only those methods change the data. Then the class can keep its invariants true: every change passes through a method that can say no.

و ئەو بەکاری دەهێنن پێکەوە لە یەک کۆ دەکاتەوە و تەنیا ڕێگە بەو دەدات بگۆڕن. ئەوسا دەتوانێت ڕاست بهێڵێتەوە: هەموو گۆڕانێک بە تێدەپەڕێت کە دەتوانێت بڵێت نەخێر.

The bank counterمێزی بانک

In a bank in Erbil you never walk into the safe to take your money. You ask the cashier at the counter, who checks your account and refuses if the balance is too small. The counter is the method; the safe is the hidden data.

لە بانکێکی هەولێر هەرگیز خۆت ناچیتە ناو قاسەکە بۆ هێنانی پارەکەت. داوا لە کارمەندی سەر مێزەکە دەکەیت، ئەویش هەژمارەکەت دەپشکنێت و ئەگەر باڵانس کەم بێت ڕەتی دەکاتەوە. مێزەکە ؛ قاسەکە .

Where the analogy stops workingسنووری ئەم نموونەیە

A real bank locks the safe. Python locks nothing: it trusts programmers to use the counter, as the next screens show.

بانکی ڕاستەقینە قاسەکە قوفڵ دەکات. هیچ شتێک قوفڵ ناکات: متمانە بە دەکات کە مێزەکە بەکار بهێنن، وەک لە پەڕەکانی داهاتوودا دەبینیت.

A capsule shape labelled with the invariant balance ≥ 0, always. Inside, behind a dashed wall, the hidden data _owner and _balance. On the capsule's edge four doors: deposit(), withdraw(), balance and owner. Outside, acc.withdraw(30000) goes through a door; acc._balance = -1 reaches inside and is marked: do not reach inside.شێوەیەکی کەپسوولی کە مەرجە نەگۆڕەکەی لەسەر نووسراوە: باڵانس ≥ 0، هەمیشە. لە ناوەوە، لە پشت دیوارێکی پچڕپچڕ، دراوە شاراوەکە: _owner و _balance. لەسەر لێواری کەپسوولەکە چوار دەرگا: deposit()، withdraw()، balance و owner. لە دەرەوە، acc.withdraw(30000) بە دەرگایەکدا دەڕوات؛ acc._balance = -1 دەست دەبات بۆ ناوەوە و نیشانە کراوە: دەست مەبە بۆ ناوەوە.
The methods are the doors; the invariant is the rule they guard. دەرگاکانن؛ ئەو یاسایەیە کە پاسەوانی دەکەن.CêIT

Methods that guard the dataمیتۆدەکان: پاسەوانی دراوە

You met the idea in Methods that guard the object. Now it protects money: put the balance behind two methods. deposit() and withdraw() check the amount before they change anything. If it is wrong, they raise a ValueError (raising your own exceptions) and the balance stays as it was. The attribute is now called _balance; the next screen explains the underscore.

ئەم بیرۆکەیەت لە ئەو میتۆدانەی ئۆبجێکتەکە دەپارێزن بینیوە. ئێستا پارە دەپارێزێت: باڵانس بخە پشت دوو . deposit() و withdraw() پێش گۆڕینی هەر شتێک بڕەکە دەپشکنن. ئەگەر هەڵە بێت، ValueError دروست دەکەن (دروستکردنی هەڵاوێردەکانی خۆت) و باڵانس وەک خۆی دەمێنێتەوە. ئێستا ناوی _balanceـە؛ پەڕەی داهاتوو ڕوون دەکاتەوە.

deposit() and withdraw() check first⁦deposit()⁩ و ⁦withdraw()⁩ سەرەتا دەپشکننguarded_methods.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner, balance=0):
        if balance < 0:
            raise ValueError("a new account cannot start below 0")
        self.owner = owner
        self._balance = balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("a deposit must be more than 0")
        self._balance += amount

    def withdraw(self, amount):
        if amount <= 0:
            raise ValueError("a withdrawal must be more than 0")
        if amount > self._balance:
            raise ValueError(f"not enough money: the balance is {self._balance}")
        self._balance -= amount

    def get_balance(self):
        return self._balance


acc = BankAccount("Shilan", 50_000)
acc.deposit(25_000)
print(acc.get_balance())

try:
    acc.withdraw(100_000)
except ValueError as error:
    print("Refused:", error)

print(acc.get_balance())
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 3-4

A new account must obey the rule too.

هەژماری نوێش دەبێت پەیڕەوی یاساکە بکات.

Lineدێڕی 14-18

Two checks, and only then the change.

دوو پشکنین، ئینجا گۆڕانەکە.

Lineدێڕی 28-31

The caller catches the error and decides what to do.

هەڵەکە دەگرێت و بڕیار دەدات چی بکات.

An animation. Shilan's account holds 75000. acc.withdraw(100000) is called: check 1, is the amount more than 0, passes; check 2, is there enough money, fails because 100000 > 75000, so ValueError is raised and the balance stays 75000. Then acc.withdraw(30000) passes both checks and the balance becomes 45000.
An animation. Shilan's account holds 75000. acc.withdraw(100000) is called: check 1, is the amount more than 0, passes; check 2, is there enough money, fails because 100000 > 75000, so ValueError is raised and the balance stays 75000. Then acc.withdraw(30000) passes both checks and the balance becomes 45000.
withdraw() says no before the data changes.⁦withdraw()⁩ پێش گۆڕانی دەڵێت نەخێر.CêIT

One underscore: "internal, please use the methods"یەک بنهێڵ: «ناوخۆییە، تکایە میتۆدەکان بەکار بهێنە»

Java has a private keyword (access modifiers); Python does not. Instead there is a convention: a name that starts with one underscore, such as _balance, is internal. It tells other programmers: use the methods, not this. Python's documentation calls such names a non-public part of the class that may change without notice.

Java وشەی privateـی هەیە (دیاریکەرەکانی دەستڕاگەیشتن)؛ نییەتی. لەبری ئەوە ڕێککەوتنێک هەیە: ناوێک کە بە یەک دەست پێدەکات، وەک _balance، ناوخۆییە. بە تر دەڵێت: بەکار بهێنە، نەک ئەمە. بەم جۆرە ناوانە دەڵێن بەشێکی نا-گشتیی کە دەکرێت بەبێ ئاگادارکردنەوە بگۆڕێت.

It is only an agreement. Python itself stops nobody:

تەنیا ڕێککەوتنێکە. خودی ڕێگە لە کەس ناگرێت:

One underscore does not lock anythingیەک بنهێڵ هیچ شتێک قوفڵ ناکاتunderscore.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance         # _ means: internal, use the methods

    def balance_text(self):
        return f"{self.owner}: {self._balance} dinars"


acc = BankAccount("Karwan", 10_000)
acc._balance = -999                     # Python allows it ...
print(acc.balance_text())               # ... but the rule "never below 0" is broken
print(vars(acc))
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 11

This runs with no error: the convention is broken, not a rule of Python.

ئەمە بەبێ هەڵە جێبەجێ دەبێت: ڕێککەوتنەکە شکێنراوە، نەک یاسایەکی .

Code checkers (linters) and editors warn when outside code touches _balance, and from module import * skips module names that start with _ (why not import *). The real protection is the programmers' agreement.

پشکنەرەکانی کۆد ⁦(linter)⁩ و دەستکاریکەرەکان ئاگادار دەکەنەوە کاتێک کۆدی دەرەوە دەست لە _balance دەدات و from module import * ئەو ناوانەی کە بە _ دەست پێدەکەن نایانهێنێت (بۆچی import * نا). پاراستنی ڕاستەقینە ڕێککەوتنی .

Three kinds of names in a Python class. balance: public, use it from anywhere. _balance: internal, one underscore; a convention that says please use the methods; Python does not stop you. __pin: mangled, two underscores; Python renames it to _BankAccount__pin so a subclass cannot clash with it.سێ جۆر ناو لە کلاسێکی Pythonـدا. balance: گشتی، لە هەر شوێنێکەوە بەکاری بهێنە. _balance: ناوخۆیی، یەک بنهێڵ؛ ڕێککەوتنێک کە دەڵێت تکایە میتۆدەکان بەکار بهێنە؛ Python ڕێگەت لێ ناگرێت. __pin: شێوێنراو، دوو بنهێڵ؛ Python ناوەکەی دەگۆڕێت بۆ _BankAccount__pin بۆ ئەوەی کلاسێکی منداڵ تێکەڵی نەبێت.
Public, internal by convention, and mangled.گشتی، ناوخۆیی بە ڕێککەوتن و شێوێنراو.CêIT

One underscore: "internal, please use the methods"یەک بنهێڵ: «ناوخۆییە، تکایە میتۆدەکان بەکار بهێنە»

Two underscores: name manglingدوو بنهێڵ: شێواندنی ناو

A name with two underscores at the start (and at most one at the end), such as __pin, is treated specially inside a class: Python renames it to _BankAccount__pin, the class name with an underscore in front. This is name mangling.

ناوێک کە لە سەرەتادا دوو هەبێت (و لە کۆتاییدا لە یەک زیاتر نەبێت)، وەک __pin، لە ناو بە شێوەیەکی تایبەت مامەڵەی لەگەڵ دەکرێت: ناوەکەی دەگۆڕێت بۆ _BankAccount__pin، واتە ناوی بە لە پێشیەوە. ئەمە .

Python renames __pinPython ناوی ⁦__pin⁩ دەگۆڕێتmangling.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner, pin):
        self.owner = owner
        self.__pin = pin                # two underscores: Python renames it

    def check_pin(self, pin):
        return pin == self.__pin        # inside the class the short name works


acc = BankAccount("Dilan", "4821")
print(acc.check_pin("1111"))
print(vars(acc))
print(acc._BankAccount__pin)            # the new name still opens it
print(acc.__pin)                        # the short name does not exist outside
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 4

Inside the class you write the short name.

لە ناو ناوە کورتەکە دەنووسیت.

Lineدێڕی 12

vars() shows the attributes the object really has.

vars() ئەو پیشان دەدات کە بەڕاستی هەیەتی.

Lineدێڕی 13

The long name still works from outside.

ناوە درێژەکە لە دەرەوەش هێشتا کار دەکات.

Left, the code self.__pin = pin inside class BankAccount. An arrow labelled renamed leads to vars(acc), which shows {'owner': 'Dilan', '_BankAccount__pin': '4821'}. Below: acc.__pin gives AttributeError because outside the class the short name does not exist; acc._BankAccount__pin gives '4821' because the new name still works, so mangling is not a lock. A box explains what it is for: a subclass can use the same name without overwriting this one. Not for secrets.چەپ، کۆدی self.__pin = pin لە ناو کلاسی BankAccount. تیرێک بە ناوی گۆڕدرا دەچێت بۆ vars(acc) کە {'owner': 'Dilan', '_BankAccount__pin': '4821'} پیشان دەدات. لە خوارەوە: acc.__pin هەڵەی AttributeError دەدات چونکە لە دەرەوەی کلاسەکە ناوە کورتەکە بوونی نییە؛ acc._BankAccount__pin نرخی '4821' دەدات چونکە ناوە نوێیەکە هێشتا کار دەکات، بۆیە شێواندنی ناو قوفڵ نییە. سندوقێک ڕوونی دەکاتەوە بۆ چییە: کلاسێکی منداڵ دەتوانێت هەمان ناو بەکار بهێنێت بەبێ ئەوەی ئەمە بسڕێتەوە. بۆ نهێنی نییە.
Name mangling changes a name; it does not hide it. ناوێک دەگۆڕێت؛ نایشارێتەوە.CêIT

So what is it for? Not for secrets. It protects a name from subclasses (Inheritance and Polymorphism): a subclass may give its own attribute the same name without overwriting the parent's one. Most Python code uses one underscore; use two only when you really fear such a clash.

کەواتە بۆ چییە؟ بۆ نهێنی نییە. ناوێک لە دەپارێزێت (بۆماوە و فرەشێوەیی): دەتوانێت هەمان ناو بداتە خۆی بەبێ ئەوەی هی بسڕێتەوە. زۆربەی کۆدی یەک بەکار دەهێنێت؛ دوو تەنیا ئەو کاتە بەکار بهێنە کە بەڕاستی لەو تێکەڵبوونە دەترسیت.

Getters and setters as methodsمیتۆدەکانی وەرگرتن و میتۆدەکانی دانان وەک میتۆد

How should other code read _owner, or change it safely? The classic way, used in Java (getters and setters), is two methods: a getter get_owner() that gives the value back, and a setter set_owner(name) that checks the new value before storing it. Checking a value before you accept it is validation.

کۆدی تر چۆن _owner بخوێنێتەوە، یان بە سەلامەتی بیگۆڕێت؟ ڕێگا کلاسیکییەکە، کە لە Javaـدا بەکار دێت (میتۆدەکانی وەرگرتن و میتۆدەکانی دانان)، دوو : get_owner() کە دەگەڕێنێتەوە و set_owner(name) کە پێش هەڵگرتن دەپشکنێت. پشکنینی پێش وەرگرتنی .

get_owner() and set_owner()⁦get_owner()⁩ و ⁦set_owner()⁩get_set_methods.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner):
        self.set_owner(owner)           # the check runs for a new account too
        self._balance = 0

    def get_owner(self):
        return self._owner

    def set_owner(self, name):
        name = name.strip()
        if name == "":
            raise ValueError("the owner's name cannot be empty")
        self._owner = name

    def get_balance(self):
        return self._balance


acc = BankAccount("  Hana ")
print(acc.get_owner(), acc.get_balance())
acc.set_owner("Hana Ahmed")
print(acc.get_owner())

try:
    acc.set_owner("   ")
except ValueError as error:
    print("Refused:", error)
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 3

__init__ calls the setter, so a new account is checked too.

__init__ بانگ دەکات، بۆیە هەژماری نوێش دەپشکنرێت.

Lineدێڕی 10

strip() removes the spaces at both ends first.

strip() سەرەتا بۆشاییەکانی هەردوو لا لادەبات.

An object acc holds _owner = 'Hana' behind two method buttons, get_owner and set_owner. From the left, acc.get_owner() goes in and 'Hana' comes back: the getter gives the value back. From the right, acc.set_owner with three spaces goes in and ValueError comes out: the setter checks first and refuses an empty name. Below: it works, but every caller must write get_owner() and set_owner(…).ئۆبجێکتی acc لە پشت دوو دوگمەی میتۆد، get_owner و set_owner، _owner = 'Hana' هەڵدەگرێت. لە چەپەوە acc.get_owner() دەچێتە ژوورەوە و 'Hana' دەگەڕێتەوە: میتۆدی وەرگرتن نرخەکە دەگەڕێنێتەوە. لە ڕاستەوە acc.set_owner بە سێ بۆشایی دەچێتە ژوورەوە و ValueError دەردەچێت: میتۆدی دانان سەرەتا دەپشکنێت و ناوی بەتاڵ ڕەت دەکاتەوە. لە خوارەوە: کار دەکات، بەڵام هەموو بانگکەرێک دەبێت ئەم دوو میتۆدە بنووسێت.
The Java way: a method to read, a method to change.ڕێگای Java: بۆ خوێندنەوە، بۆ گۆڕین.CêIT

The rule is safe now, but there is a cost: every caller writes acc.get_owner() instead of acc.owner. Worse, if a class starts with a plain attribute and later needs a check, every line that used acc.owner must change. Python has a better tool.

ئێستا یاساکە سەلامەتە، بەڵام نرخێکی هەیە: هەموو بانگکەرێک لەبری acc.owner دەنووسێت acc.get_owner(). خراپتر، ئەگەر بە سادە دەست پێبکات و دواتر پێویستی بە پشکنین بێت، دەبێت هەموو ئەو دێڕانە بگۆڕدرێن کە acc.ownerـیان بەکار هێناوە. ئامرازێکی باشتری هەیە.

@property: a method you read like an attribute@property: میتۆدێک کە وەک خەسڵەتێک دەیخوێنیتەوە

A property looks like an attribute from outside but runs a method inside. Write @property on the line above a method. That line is a decorator: it turns the method into a getter. Now acc.balance, with no brackets, calls the method and gives back its result.

لە دەرەوە وەک دەردەکەوێت، بەڵام لە ناوەوە جێبەجێ دەکات. لە دێڕی سەرووی بنووسە @property. ئەو دێڕە : دەکاتە . ئێستا acc.balance، بەبێ کەوانە، بانگ دەکات و ئەنجامەکەی دەگەڕێنێتەوە.

A getter with @propertyمیتۆدی وەرگرتن بە @propertyproperty_getter.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance

    @property
    def balance(self):                  # a method that is read like an attribute
        print("(the getter runs)")
        return self._balance


acc = BankAccount("Lana", 40_000)
print(acc.balance)                      # no ( ): Python calls the method for you
print(acc.balance + 5_000)
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 6-9

The decorator turns balance() into a property.

balance() دەکاتە .

Lineدێڕی 13

No brackets, but the method runs: see "(the getter runs)".

بێ کەوانە، بەڵام جێبەجێ دەبێت: بڕوانە "⁦(the getter runs)"⁩.

Definitionپێناسە

An attribute whose reading and writing run . In Python, @property above a method makes it a , and @name.setter adds a , so acc.balance looks like a plain value while the checks it or works it out.

بریتییە لە کە کاتێک دەیخوێنیتەوە یان دەیگۆڕیت، جێبەجێ دەبێت. لە @property لە سەرووی دەیکاتە و @name.setter زیاد دەکات، بۆیە acc.balance وەک سادە دەردەکەوێت، لە کاتێکدا دەیپشکنێت یان دەیژمێرێت.

print(acc.balance) on the left: no brackets, it looks like an attribute. An arrow leads to the code @property, def balance(self), return self._balance, and from there to the value 40000. Three numbered steps: 1, you read acc.balance; 2, Python calls the getter; 3, its return value comes back. The data stays inside the object; the property is the door to it.print(acc.balance) لە چەپ: بێ کەوانە، وەک خەسڵەتێک دەردەکەوێت. تیرێک دەچێت بۆ کۆدی @property، def balance(self)، return self._balance و لەوێوە بۆ نرخی 40000. سێ هەنگاوی ژمارەدار: 1، تۆ acc.balance دەخوێنیتەوە؛ 2، Python میتۆدی وەرگرتن بانگ دەکات؛ 3، نرخە گەڕاوەکەی دەگەڕێتەوە. دراوەکە لە ناو ئۆبجێکتەکەدا دەمێنێتەوە؛ خەسڵەتە بەڕێوەبراوەکە دەرگاکەیەتی.
Reading acc.balance quietly calls a method.خوێندنەوەی acc.balance بە بێدەنگی بانگ دەکات.CêIT

@property: a method you read like an attribute@property: میتۆدێک کە وەک خەسڵەتێک دەیخوێنیتەوە

@owner.setter: every assignment is checked@owner.setter: هەموو دانانێک دەپشکنرێت

To let other code change a property, add a setter: a second method with the same name, marked @owner.setter (the property's name, a dot, setter). Now acc.owner = "Rebin Kamal" calls the setter, which can clean, check and refuse. The value itself is stored under another name, _owner.

بۆ ئەوەی ڕێگە بە کۆدی تر بدەیت بگۆڕێت، زیاد بکە: دووەم بە هەمان ناو کە بە @owner.setter نیشانە کراوە (ناوی ، خاڵێک، setter). ئێستا acc.owner = "Rebin Kamal" بانگ دەکات، کە دەتوانێت پاکی بکاتەوە، بیپشکنێت و ڕەتی بکاتەوە. خودی بە ناوێکی ترەوە هەڵدەگیرێت: _owner.

A property with a getter and a setterخەسڵەتی بەڕێوەبراو بە میتۆدی وەرگرتن و میتۆدی دانانەوەproperty_setter.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner):
        self.owner = owner              # this line already uses the setter
        self._balance = 0

    @property
    def owner(self):
        return self._owner

    @owner.setter
    def owner(self, name):
        name = name.strip()
        if name == "":
            raise ValueError("the owner's name cannot be empty")
        self._owner = name              # stored in _owner, not in owner


acc = BankAccount("  Rebin  ")
print(repr(acc.owner))
acc.owner = "Rebin Kamal"
print(acc.owner)
acc.owner = "   "
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 3

This line already goes through the setter: a new account is checked too.

ئەم دێڕە خۆی بە تێدەپەڕێت: هەژماری نوێش دەپشکنرێت.

Lineدێڕی 10-11

The setter has the same name as the getter.

هەمان ناوی هەیە.

Lineدێڕی 15

Store in _owner, never in owner (see Common mistakes).

لە _owner هەڵیبگرە، هەرگیز لە owner نا (بڕوانە هەڵە باوەکان).

Lineدێڕی 22

An empty name is refused with a clear message.

ناوی بەتاڵ بە پەیامێکی ڕوون ڕەت دەکرێتەوە.

An animation of the owner setter. BankAccount("  Rebin  ") makes __init__ run self.owner = owner; the setter strips the spaces, checks the name is not empty and stores 'Rebin'. acc.owner = "Rebin Kamal" goes through the same three steps and is stored. acc.owner with three spaces fails at step 2 with ValueError: the owner's name cannot be empty; step 3 never runs, so _owner keeps 'Rebin Kamal'.
An animation of the owner setter. BankAccount("  Rebin  ") makes __init__ run self.owner = owner; the setter strips the spaces, checks the name is not empty and stores 'Rebin'. acc.owner = "Rebin Kamal" goes through the same three steps and is stored. acc.owner with three spaces fails at step 2 with ValueError: the owner's name cannot be empty; step 3 never runs, so _owner keeps 'Rebin Kamal'.
Every acc.owner = … passes the same three steps.هەموو acc.owner = …ـێک بە هەمان سێ هەنگاودا تێدەپەڕێت.CêIT

The callers did not change: they still write acc.owner = .... So you can start with a plain attribute and turn it into a property later, without touching any code that uses it. That is why Python programmers seldom write get_ and set_ methods.

بانگکەرەکان نەگۆڕان: هێشتا دەنووسن acc.owner = .... کەواتە دەتوانیت بە سادە دەست پێبکەیت و دواتر بیکەیتە ، بەبێ دەستدان لە هیچ کۆدێک کە بەکاری دەهێنێت. بۆیە کەم جار get_ و set_ دەنووسن.

@property: a method you read like an attribute@property: میتۆدێک کە وەک خەسڵەتێک دەیخوێنیتەوە

Read-only propertiesخەسڵەتە بەڕێوەبراوەکان کە تەنیا دەخوێنرێنەوە

A property with a getter and no setter can be read but not assigned. The balance should work exactly like this: nobody may write acc.balance = 1_000_000; money moves only through deposit() and withdraw(), which check the amount.

کە هەیە و نییە دەخوێنرێتەوە بەڵام وەرناگرێت. باڵانس دەبێت ڕێک بەم شێوەیە کار بکات: کەس بۆی نییە بنووسێت acc.balance = 1_000_000؛ پارە تەنیا بە deposit() و withdraw() دەجووڵێت، کە بڕەکە دەپشکنن.

balance can be read, not assignedbalance دەخوێنرێتەوە، بەڵام ناگۆڕدرێتread_only.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance

    @property
    def balance(self):                  # a getter and no setter
        return self._balance

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("a deposit must be more than 0")
        self._balance += amount


acc = BankAccount("Soran", 20_000)
acc.deposit(5_000)                      # the way in is a method
print(acc.balance)
acc.balance = 1_000_000                 # no setter: refused
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 6-8

A getter, and no @balance.setter anywhere.

و هیچ @balance.setterـێک نییە.

Lineدێڕی 19

Python 3.13 says exactly what is missing: a setter.

⁦3.13⁩ ڕێک دەڵێت چی کەمە: .

The property balance has a getter (@property) and no setter, shown with a padlock. acc.balance reads 25000: fine. acc.balance = 1_000_000 gives AttributeError: there is no setter. acc.deposit(5_000) is the checked way in. Only deposit() and withdraw() change the balance, and they check every amount.خەسڵەتە بەڕێوەبراوەکە balance میتۆدی وەرگرتنی هەیە (@property) و میتۆدی دانانی نییە، بە قوفڵێک پیشان دراوە. acc.balance نرخی 25000 دەخوێنێتەوە: باشە. acc.balance = 1_000_000 هەڵەی AttributeError دەدات: میتۆدی دانان نییە. acc.deposit(5_000) ڕێگای پشکنراوی چوونەژوورەوەیە. تەنیا deposit() و withdraw() باڵانس دەگۆڕن و هەموو بڕێک دەپشکنن.
A getter without a setter: read yes, write no. بەبێ : خوێندنەوە بەڵێ، نووسین نا.CêIT

@property: a method you read like an attribute@property: میتۆدێک کە وەک خەسڵەتێک دەیخوێنیتەوە

Computed propertiesخەسڵەتە بەڕێوەبراوەکان کە دەژمێردرێن

A property does not have to return a stored value: it can work one out each time it is read. This account keeps a list of every deposit (+) and withdrawal (−), and balance adds them up with sum(). The history property returns a tuple, a copy nobody can change, so no caller can append a fake deposit (tuples, real copies).

پێویست ناکات هەڵگیراو بگەڕێنێتەوە: دەتوانێت هەر جارێک دەخوێنرێتەوە بیژمێرێت. ئەم هەژمارە لە هەموو پارە دانان ⁦(+)⁩ و پارە ڕاکێشانێک ⁦(−)⁩ هەڵدەگرێت و balance بە sum() کۆیان دەکاتەوە. history دەگەڕێنێتەوە، کۆپییەک کە کەس ناتوانێت بیگۆڕێت، بۆیە هیچ بانگکەرێک ناتوانێت پارە دانانێکی ساختە زیاد بکات (تاپڵەکان، کۆپیی ڕاستەقینە).

balance is computed from the movesbalance لە جووڵەکانەوە دەژمێردرێتcomputed.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner):
        self.owner = owner
        self._moves = []                # +amount = deposit, -amount = withdrawal

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("a deposit must be more than 0")
        self._moves.append(amount)

    def withdraw(self, amount):
        if amount <= 0:
            raise ValueError("a withdrawal must be more than 0")
        if amount > self.balance:
            raise ValueError("not enough money")
        self._moves.append(-amount)

    @property
    def balance(self):                  # worked out every time, never stored
        return sum(self._moves)

    @property
    def history(self):                  # a tuple: read it, but you cannot change it
        return tuple(self._moves)


acc = BankAccount("Dilan")
acc.deposit(100_000)
acc.withdraw(30_000)
acc.deposit(15_000)
print(acc.balance)
print(acc.history)
acc.history.append(999_999)
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 14

Methods inside the class can use the property too.

ناو دەتوانن بەکار بهێنن.

Lineدێڕی 20

Worked out every time: it can never disagree with the list.

هەموو جارێک دەژمێردرێت: هەرگیز لەگەڵ ناکۆک نابێت.

Lineدێڕی 33

A tuple has no append(): the history is safe.

append()ـی نییە: مێژووەکە پارێزراوە.

The list _moves holds +100000, -30000 and +15000; sum() turns it into acc.balance = 85000. Below, acc.history gives the tuple (100000, -30000, 15000): a copy you can read, but adding to it fails. Nothing stores the balance, so it can never disagree with the list.لیستی _moves ئەم نرخانەی تێدایە: +100000، -30000 و +15000؛ sum() دەیکاتە acc.balance = 85000. لە خوارەوە، acc.history تاپڵەکە (100000, -30000, 15000) دەدات: کۆپییەک کە دەتوانیت بیخوێنیتەوە، بەڵام زیادکردن پێی هەڵە دەدات. هیچ شتێک باڵانس هەڵناگرێت، بۆیە هەرگیز لەگەڵ لیستەکە ناکۆک نابێت.
The balance is computed from the history each time you ask.باڵانس هەر جارێک داوای بکەیت لە مێژووەکەوە دەژمێردرێت.CêIT

Abstraction: show what, hide howڕووتکردنەوە: چی پیشان بدە، چۆن بشارەوە

Look at the account from outside: an owner, a balance, deposit, withdraw, transfer, a statement. That is what the class does. How it does it (one number or a list of moves, which checks run in which order) stays hidden. Showing what and hiding how is abstraction. You meet it every day: you tell an Erbil taxi driver the address, and you do not need to know which streets he takes.

لە دەرەوە سەیری هەژمارەکە بکە: خاوەنێک، باڵانسێک، پارە دانان، پارە ڕاکێشان، گواستنەوە، کەشف حیسابێک. ئەمە ئەوەیە کە چی دەکات. چۆن دەیکات (یەک ژمارە یان لە جووڵەکان، کام پشکنین بە چ ڕیزێک) شاراوە دەمێنێتەوە. پیشاندانی چی و شاردنەوەی چۆن . هەموو ڕۆژێک دەیبینیت: ناونیشانەکە بە شۆفێری تەکسییەکی هەولێر دەڵێیت و پێویست ناکات بزانیت بە چ شەقامێکدا دەڕوات.

Encapsulation is the tool (data and methods together, the data hidden); abstraction is the result (a simple public face). The prize: the inside can change without breaking anyone. Version 1 stores _balance; version 2 stores a list of moves and computes balance. Code that uses only the public face works with both. Java builds the same idea with abstract classes and interfaces (what, not how); Python's abstract classes come in the next lesson.

ئامرازەکەیە ( و پێکەوە، شاراوە)؛ ئەنجامەکەیە (ڕوویەکی گشتیی سادە). خەڵاتەکە: ناوەوە دەگۆڕدرێت بەبێ ئەوەی هیچ کەسێک تێک بشکێت. وەشانی 1 _balance هەڵدەگرێت؛ وەشانی 2 لە جووڵەکان هەڵدەگرێت و balance دەژمێرێت. ئەو کۆدەی تەنیا ڕووە گشتییەکە بەکار دەهێنێت لەگەڵ هەردووکیان کار دەکات. Java هەمان بیرۆکە بە و دروست دەکات (چی، نەک چۆن)؛ لە وانەی داهاتوودا دێن.

Left, what other code sees: owner, balance, deposit(amount), withdraw(amount), transfer_to(other, amount), statement(). Right, hidden inside and free to change: version 1 with _balance = 75000 and _history; version 2 with _moves = [120000, -45000] and balance = sum(_moves). An arrow marked swap goes from version 1 to version 2. Change the inside, keep the outside: no caller has to change.چەپ، ئەوەی کۆدی تر دەیبینێت: owner، balance، deposit(amount)، withdraw(amount)، transfer_to(other, amount)، statement(). ڕاست، شاراوە لە ناوەوە و ئازاد بۆ گۆڕین: وەشانی 1 بە _balance = 75000 و _history؛ وەشانی 2 بە _moves = [120000, -45000] و balance = sum(_moves). تیرێک بە ناوی گۆڕین لە وەشانی 1ـەوە دەچێت بۆ وەشانی 2. ناوەوە بگۆڕە و دەرەوە وەک خۆی بهێڵە: پێویست ناکات هیچ بانگکەرێک بگۆڕێت.
One public face, two possible insides.یەک ڕووی گشتی، دوو ناوەوەی جیاواز.CêIT

Worked example: a complete BankAccountنموونەی تەواو: BankAccount

Everything together: a validated owner property, a read-only balance, deposit() and withdraw() that raise ValueError, transfer_to() that moves money between two accounts, a statement() and __str__(). The helper _check() starts with an underscore because only the class uses it.

هەموو شتێک پێکەوە: owner کە دەپشکنرێت، balance کە تەنیا دەخوێنرێتەوە، deposit() و withdraw() کە ValueError دروست دەکەن، transfer_to() کە پارە لە نێوان دوو هەژماردا دەگوازێتەوە، statement() و __str__(). یارمەتیدەری _check() بە دەست پێدەکات چونکە تەنیا خۆی بەکاری دەهێنێت.

A class box for BankAccount. Public: owner, a property that is checked; balance, a read-only property; deposit(amount), withdraw(amount), transfer_to(other, amount), statement() and __str__(). Below a dashed line, internal, with one underscore: the data _owner, _balance and _history, and the helper _check(amount). A note: transfer_to() first calls withdraw(); if that raises ValueError, deposit() never runs and no money moves.سندوقی کلاسی BankAccount. گشتی: owner، خەسڵەتێکی بەڕێوەبراو کە دەپشکنرێت؛ balance، خەسڵەتێکی بەڕێوەبراو کە تەنیا دەخوێنرێتەوە؛ deposit(amount)، withdraw(amount)، transfer_to(other, amount)، statement() و __str__(). لە ژێر هێڵێکی پچڕپچڕ، ناوخۆیی بە یەک بنهێڵ: دراوەکە _owner، _balance و _history و یارمەتیدەری _check(amount). تێبینییەک: transfer_to() سەرەتا withdraw() بانگ دەکات؛ ئەگەر ValueError ڕوو بدات، deposit() جێبەجێ نابێت و هیچ پارەیەک ناجووڵێت.
The public face on top, the hidden parts below.ڕووە گشتییەکە لە سەرەوە، بەشە شاراوەکان لە خوارەوە.CêIT
BankAccount: the whole classBankAccount: هەموو کلاسەکەbank_account.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    """A bank account whose balance is never below 0."""

    def __init__(self, owner, balance=0):
        if balance < 0:
            raise ValueError("an account cannot start below 0")
        self.owner = owner                     # checked by the owner setter
        self._balance = balance
        self._history = [("open", balance)]

    @property
    def owner(self):
        return self._owner

    @owner.setter
    def owner(self, name):
        name = name.strip()
        if not name:
            raise ValueError("the owner's name cannot be empty")
        self._owner = name

    @property
    def balance(self):                         # read-only: there is no setter
        return self._balance

    def deposit(self, amount):
        self._check(amount)
        self._balance += amount
        self._history.append(("deposit", amount))

    def withdraw(self, amount):
        self._check(amount)
        if amount > self._balance:
            raise ValueError(f"not enough money: {self._balance:,} left")
        self._balance -= amount
        self._history.append(("withdraw", amount))

    def transfer_to(self, other, amount):
        self.withdraw(amount)                  # if this fails, nothing moves
        other.deposit(amount)

    def statement(self):
        lines = [f"Statement for {self.owner}"]
        for kind, amount in self._history:
            lines.append(f"  {kind:<9}{amount:>10,}")
        lines.append(f"  {'balance':<9}{self.balance:>10,}")
        return "\n".join(lines)

    def _check(self, amount):                  # a helper for this class only
        if amount <= 0:
            raise ValueError("the amount must be more than 0")

    def __str__(self):
        return f"{self.owner}: {self.balance:,} IQD"


aram = BankAccount("Aram", 120_000)
shilan = BankAccount("Shilan")
aram.transfer_to(shilan, 45_000)
print(aram)
print(shilan)

for amount in (500_000, -5):
    try:
        aram.transfer_to(shilan, amount)
    except ValueError as error:
        print("Refused:", error)

print(aram.statement())
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 5-7

The opening balance and the owner are checked like every later change.

باڵانسی سەرەتا و خاوەنەکە وەک هەموو گۆڕانێکی دواتر دەپشکنرێن.

Lineدێڕی 38-40

withdraw first: if it raises, deposit never runs, so money is never created or lost.

سەرەتا withdraw: ئەگەر دروست بکات، deposit جێبەجێ نابێت، بۆیە هەرگیز پارە لە هیچەوە دروست نابێت و ون نابێت.

Lineدێڕی 49

One underscore: an internal helper, not part of the public face.

یەک : یارمەتیدەرێکی ناوخۆیی، بەشێک نییە لە ڕووە گشتییەکە.

Read the output: the 500,000 transfer is refused before anything moves, so Aram still has 75,000 and Shilan 45,000. The −5 transfer is refused by _check(). Money is never created or lost: the invariant holds for both accounts.

بخوێنەوە: گواستنەوەی ⁦500,000⁩ پێش جووڵانی هەر شتێک ڕەت دەکرێتەوە، بۆیە ئارام هێشتا ⁦75,000⁩ـی هەیە و شیلان ⁦45,000⁩. گواستنەوەی ⁦−5⁩ لە لایەن _check()ـەوە ڕەت دەکرێتەوە. هەرگیز پارە لە هیچەوە دروست نابێت و ون نابێت: بۆ هەردوو هەژمار ڕاستە.

Common mistake: the setter that calls itselfهەڵەی باو: میتۆدێکی دانان کە خۆی بانگ دەکاتەوە

The most common property bug: inside the setter the value is stored with self.owner = name. But self.owner = ... is exactly what calls the setter. So the setter calls itself, again and again, until Python gives up after about 1,000 calls (when recursion never stops):

باوترین : لە ناو بە self.owner = name هەڵدەگیرێت. بەڵام self.owner = ... ڕێک ئەوەیە کە بانگ دەکات. بۆیە خۆی بانگ دەکاتەوە، دووپاتە و دووپاتە، تا دوای نزیکەی ⁦1,000⁩ بانگکردن واز دەهێنێت (کاتێک خۆبانگکردنەوە هەرگیز ناوەستێت):

Wrong: the setter assigns to itselfهەڵە: میتۆدی دانانەکە نرخ دەداتە خۆیsetter_recursion.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner):
        self.owner = owner

    @property
    def owner(self):
        return self._owner

    @owner.setter
    def owner(self, name):
        if not name.strip():
            raise ValueError("the owner's name cannot be empty")
        self.owner = name               # wrong: this calls the setter again


acc = BankAccount("Lana")
Explain the codeڕوونکردنەوەی کۆدەکە
Lineدێڕی 13

This calls the setter again: endless recursion.

ئەمە دیسان بانگ دەکات: بێکۆتایی.

Left, wrong: inside @owner.setter the line self.owner = name has an arrow looping back to the setter; it calls the setter again, and again, about 1000 times, ending in RecursionError. Right, right: the line self._owner = name stores the value, and _owner = 'Lana' appears. Below: inside the property, store the value under another name; self.owner = … would go through the setter again.چەپ، هەڵە: لە ناو @owner.setter دێڕی self.owner = name تیرێکی هەیە کە دەگەڕێتەوە بۆ میتۆدی دانانەکە؛ دیسان و دیسان، نزیکەی 1000 جار، میتۆدی دانانەکە بانگ دەکاتەوە و بە RecursionError کۆتایی دێت. ڕاست، ڕاست: دێڕی self._owner = name نرخەکە هەڵدەگرێت و _owner = 'Lana' دەردەکەوێت. لە خوارەوە: لە ناو خەسڵەتە بەڕێوەبراوەکەدا، نرخەکە بە ناوێکی ترەوە هەڵبگرە؛ self.owner = … دیسان بە میتۆدی داناندا تێدەپەڕێت.
One missing underscore makes an endless loop.یەک کەم بێکۆتایی دروست دەکات.CêIT
Right: store the value under _ownerڕاست: نرخەکە لە _owner هەڵبگرەsetter_fixed.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner):
        self.owner = owner

    @property
    def owner(self):
        return self._owner

    @owner.setter
    def owner(self, name):
        if not name.strip():
            raise ValueError("the owner's name cannot be empty")
        self._owner = name              # right: store in the hidden attribute


acc = BankAccount("Lana")
print(acc.owner)

Common mistake: the setter that calls itselfهەڵەی باو: میتۆدێکی دانان کە خۆی بانگ دەکاتەوە

More common mistakesهەڵە باوەکانی تر

MistakeهەڵەWhat happensچی ڕوو دەداتFixچارەسەر
The setter has another name: @owner.setter above def set_owner ناوێکی تری هەیە: @owner.setter لە سەرووی def set_ownerAttributeError: property 'owner' of 'BankAccount' object has no setterGive the setter the property's own name: def ownerناوی خودی بدە : def owner
Calling a property with brackets: acc.balance()بانگکردنی بە کەوانەوە: acc.balance()TypeError: 'int' object is not callableRead it without brackets: acc.balanceبەبێ کەوانە بیخوێنەوە: acc.balance
__init__ writes self._owner = owner directly__init__ ڕاستەوخۆ دەنووسێت self._owner = ownerA new object skips the check and can start broken نوێ پشکنینەکە تێدەپەڕێنێت و دەکرێت شکاو دەست پێبکاتWrite self.owner = owner in __init__لە __init__ـدا بنووسە self.owner = owner
Returning the inside list: return self._movesگەڕاندنەوەی ناوەوە: return self._movesCallers can append to it and break the invariantبانگکەرەکان دەتوانن شتی بۆ زیاد بکەن و بشکێننReturn a copy or a tuple: tuple(self._moves)کۆپییەک یان بگەڕێنەوە: tuple(self._moves)
Thinking _balance or __pin is a lockوا بیر بکەیتەوە _balance یان __pin قوفڵەOutside code can still read and change itکۆدی دەرەوە هێشتا دەتوانێت بیخوێنێتەوە و بیگۆڕێتTreat the names as an agreement; protect secrets with real securityناوەکان وەک ڕێککەوتن ببینە؛ نهێنییەکان بە ڕاستەقینە بپارێزە
A getter and a setter for every attribute "just in case" و بۆ هەموو «بۆ ئەوەی هەبێت»Long code that hides nothingکۆدی درێژ کە هیچ ناشارێتەوەStart with plain attributes; make one a property when it needs a ruleبە سادە دەست پێبکە؛ کاتێک یەکێکیان پێویستی بە یاسا بوو بیکە بە

All error messages above are real output of Python 3.13; older versions word some of them a little differently.

هەموو پەیامەکانی هەڵەی سەرەوە ڕاستەقینەی ⁦3.13⁩ـن؛ وەشانە کۆنترەکان هەندێکیان کەمێک بە شێوەیەکی تر دەنووسن.

Exercisesڕاهێنانەکان

ExerciseڕاهێنانA thermometerگەرمیپێو

Write a class Thermometer with a celsius property that refuses values below −273.15 (absolute zero) with ValueError, and a read-only property fahrenheit computed as celsius * 9 / 5 + 32. Test both refusals.

Thermometer بنووسە کە celsiusـی هەبێت و خوارووی ⁦−273.15⁩ (سفری ڕەها) بە ValueError ڕەت بکاتەوە، لەگەڵ fahrenheit کە تەنیا دەخوێنرێتەوە و بە celsius * 9 / 5 + 32 دەژمێردرێت. هەردوو ڕەتکردنەوەکە تاقی بکەرەوە.

Hintئاماژە

Use self.celsius = celsius in __init__, and give fahrenheit only a getter.

لە __init__ـدا self.celsius = celsius بەکار بهێنە و تەنیا بدە fahrenheit.

Show answerوەڵامەکە پیشان بدە
One possible answerوەڵامێکی گونجاوex_thermometer.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class Thermometer:
    ABSOLUTE_ZERO = -273.15

    def __init__(self, celsius=0.0):
        self.celsius = celsius                 # uses the setter

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, value):
        if value < self.ABSOLUTE_ZERO:
            raise ValueError(f"{value} °C is below absolute zero")
        self._celsius = value

    @property
    def fahrenheit(self):                      # computed, read-only
        return self._celsius * 9 / 5 + 32


t = Thermometer(25)
print(t.celsius, t.fahrenheit)
t.celsius = -10
print(t.celsius, t.fahrenheit)

try:
    t.celsius = -300
except ValueError as error:
    print("Refused:", error)

try:
    t.fahrenheit = 100
except AttributeError as error:
    print("Refused:", error)
ExerciseڕاهێنانTea glasses in the bazaarپەرداخی چا لە بازاڕ

A shop sells tea glasses. Write Product(name, price, quantity) where price must be more than 0 and quantity can never be negative. Add a read-only stock_value (price × quantity) and a method sell(count) that refuses to sell more than there is.

دوکانێک پەرداخی چا دەفرۆشێت. Product(name, price, quantity) بنووسە کە price دەبێت لە 0 زیاتر بێت و quantity هەرگیز نەرێنی نەبێت. stock_value (نرخ × ژمارە) زیاد بکە کە تەنیا دەخوێنرێتەوە و sell(count) کە ڕازی نابێت لەوەی هەیە زیاتر بفرۆشێت.

Show answerوەڵامەکە پیشان بدە
One possible answerوەڵامێکی گونجاوex_product.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class Product:
    def __init__(self, name, price, quantity=0):
        self.name = name
        self.price = price
        self.quantity = quantity

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, value):
        if value <= 0:
            raise ValueError("the price must be more than 0")
        self._price = value

    @property
    def quantity(self):
        return self._quantity

    @quantity.setter
    def quantity(self, value):
        if value < 0:
            raise ValueError("the quantity cannot be negative")
        self._quantity = value

    @property
    def stock_value(self):
        return self.price * self.quantity

    def sell(self, count):
        if count > self.quantity:
            raise ValueError(f"only {self.quantity} left")
        self.quantity -= count                 # the setter checks this too


tea = Product("tea glass", 1_500, 40)
tea.sell(12)
print(tea.quantity, tea.stock_value)

try:
    tea.price = 0
except ValueError as error:
    print("Refused:", error)

try:
    tea.sell(100)
except ValueError as error:
    print("Refused:", error)

sell() changes self.quantity, so even the class's own methods go through the setter's check.

sell() self.quantity دەگۆڕێت، بۆیە تەنانەت خودی بە پشکنینی تێدەپەڕن.

ExerciseڕاهێنانPredict the namesناوەکان پێشبینی بکە

What does this program print? Explain each of the three names.

ئەم چی چاپ دەکات؟ هەر یەکێک لە سێ ناوەکە ڕوون بکەرەوە.

Three namesسێ ناوex_locker.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class Locker:
    def __init__(self, owner, code):
        self._owner = owner
        self.__code = code
        self.number = 7


box = Locker("Soran", "2580")
print(vars(box))
Show answerوەڵامەکە پیشان بدە

_owner keeps its name: one underscore is only a convention. __code is mangled to _Locker__code. number is public.

_owner ناوەکەی دەپارێزێت: یەک تەنیا ڕێککەوتنە. __code دەشێوێنرێت بۆ _Locker__code. number گشتییە.

Outputدەرئەنجام
{'_owner': 'Soran', '_Locker__code': '2580', 'number': 7}
ExerciseڕاهێنانFrom get/set to a propertyلە ⁦get/set⁩ بۆ خەسڵەتی بەڕێوەبراو

Rewrite the class from "Getters and setters as methods" so that callers write acc.owner and acc.owner = ... instead of get_owner() and set_owner(). The empty-name rule must still hold, also for new accounts.

پەڕەی « و وەک » بنووسەوە بۆ ئەوەی بانگکەرەکان لەبری get_owner() و set_owner() بنووسن acc.owner و acc.owner = .... یاسای ناوی بەتاڵ دەبێت هێشتا ڕاست بێت، بۆ هەژماری نوێش.

Show answerوەڵامەکە پیشان بدە
One possible answer: the same check, now in a setterوەڵامێکی گونجاو: هەمان پشکنین، ئێستا لە میتۆدێکی دانانداproperty_setter.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class BankAccount:
    def __init__(self, owner):
        self.owner = owner              # this line already uses the setter
        self._balance = 0

    @property
    def owner(self):
        return self._owner

    @owner.setter
    def owner(self, name):
        name = name.strip()
        if name == "":
            raise ValueError("the owner's name cannot be empty")
        self._owner = name              # stored in _owner, not in owner


acc = BankAccount("  Rebin  ")
print(repr(acc.owner))
acc.owner = "Rebin Kamal"
print(acc.owner)
acc.owner = "   "
ExerciseڕاهێنانA savings accountهەژماری پاشەکەوت

In the bank account challenge the rule "at least 10,000 dinars must stay" lived in a function. Make it a class rule: SavingsAccount(owner, balance) with a read-only balance. Opening with less than 10,000, or a withdrawal that would leave less, is refused with ValueError.

لە ڕاهێنانی هەژماری بانکی یاسای «لانیکەم ⁦10,000⁩ دینار دەبێت بمێنێتەوە» لە ناو بوو. بیکە بە یاسای : SavingsAccount(owner, balance) بە balanceـێک کە تەنیا دەخوێنرێتەوە. کردنەوە بە کەمتر لە ⁦10,000⁩، یان ڕاکێشانێک کە کەمتر بهێڵێتەوە، بە ValueError ڕەت دەکرێتەوە.

Show answerوەڵامەکە پیشان بدە
One possible answerوەڵامێکی گونجاوex_savings.pyPYTHONType in a terminalلە تێرمیناڵدا بینووسە
class SavingsAccount:
    MINIMUM = 10_000                            # this account must always keep 10,000

    def __init__(self, owner, balance):
        if balance < self.MINIMUM:
            raise ValueError(f"open it with at least {self.MINIMUM:,}")
        self.owner = owner
        self._balance = balance

    @property
    def balance(self):
        return self._balance

    def withdraw(self, amount):
        if amount <= 0:
            raise ValueError("the amount must be more than 0")
        if self._balance - amount < self.MINIMUM:
            raise ValueError(f"you can take at most {self._balance - self.MINIMUM:,}")
        self._balance -= amount


acc = SavingsAccount("Hana", 60_000)
acc.withdraw(30_000)
print(acc.balance)
try:
    acc.withdraw(25_000)
except ValueError as error:
    print("Refused:", error)
print(acc.balance)

Summaryپوختە

  • Encapsulation: data and the methods that use it live together in a class, and only those methods change the data.
  • An invariant is a rule that is always true, such as "balance ≥ 0"; methods keep it true by raising ValueError.
  • _name means internal by convention; __name is renamed to _Class__name (name mangling) to avoid clashes in subclasses. Neither is a lock.
  • A getter reads a value and a setter checks and stores it. In Python they are usually a property: @property and @name.setter.
  • Callers read and assign a property like an attribute, with no brackets.
  • No setter = read-only. A property can also compute its value each time.
  • Inside a setter, store the value in self._name, never in self.name (RecursionError).
  • Abstraction: show what the class does, hide how, so the inside can change without breaking callers.
  • : و ئەو بەکاری دەهێنن پێکەوە لە دەژین و تەنیا ئەو دەگۆڕن.
  • یاسایەکە کە هەمیشە ڕاستە، وەک «باڵانس ≥ 0»؛ بە دروستکردنی ValueError ڕاستی دەهێڵنەوە.
  • _name بە ڕێککەوتن واتە ناوخۆیی؛ __name دەکرێتە _Class__name () بۆ ئەوەی لە تێکەڵ نەبێت. هیچیان قوفڵ نین.
  • دەخوێنێتەوە و دەیپشکنێت و هەڵیدەگرێت. لە زۆرجار ن: @property و @name.setter.
  • بانگکەرەکان وەک دەخوێننەوە و دەیگۆڕن، بەبێ کەوانە.
  • بێ = تەنیا دەخوێنرێتەوە. دەتوانێت هەر جارێک بژمێرێت.
  • لە ناو لە self._name هەڵبگرە، هەرگیز لە self.name نا ⁦(RecursionError)⁩.
  • : پیشاندانی ئەوەی چی دەکات و شاردنەوەی چۆنییەتییەکەی، بۆ ئەوەی ناوەوە بگۆڕدرێت بەبێ تێکشکانی بانگکەرەکان.

Next: Inheritance and Polymorphism, classes built on other classes, and one loop that works for many kinds of objects.

دواتر: بۆماوە و فرەشێوەیی، کە لەسەر تر دروست دەکرێن و یەک کە بۆ زۆر جۆر کار دەکات.

Review questionsپرسیارەکانی پێداچوونەوە
What is the difference between encapsulation and abstraction?جیاوازیی نێوان کەپسوولکردن و ڕووتکردنەوە چییە؟

Encapsulation is the tool: data and methods together, the data reachable only through the methods. Abstraction is the result: a simple public face that shows what the object does and hides how.

ئامرازەکەیە: و پێکەوە و تەنیا لە ڕێگەی دەگاتێ. ئەنجامەکەیە: ڕوویەکی گشتیی سادە کە پیشان دەدات چی دەکات و چۆنییەتییەکەی دەشارێتەوە.

Why is __pin not a safe place for a secret?بۆچی __pin شوێنێکی سەلامەت نییە بۆ نهێنی؟

Name mangling only renames it to _BankAccount__pin, which anyone can still read. Secrets need real security, for example storing a salted hash instead of the PIN.

تەنیا ناوەکەی دەگۆڕێت بۆ _BankAccount__pin کە هەر کەسێک هێشتا دەتوانێت بیخوێنێتەوە. نهێنی پێویستی بە ڕاستەقینەیە، بۆ نموونە هەڵگرتنی هاشێکی خوێدار لەبری PIN.

When should a plain attribute become a property?کەی خەسڵەتێکی سادە دەبێت ببێتە خەسڵەتی بەڕێوەبراو؟

When it needs a rule (validation), must be read-only, or should be computed. Because callers use the same syntax, the change does not break their code.

کاتێک پێویستی بە یاسایەک () هەبێت، دەبێت تەنیا بخوێنرێتەوە، یان پێویستە بژمێردرێت. چونکە بانگکەرەکان هەمان بەکار دەهێنن، گۆڕانەکە کۆدەکەیان تێک ناشکێنێت.

Lesson complete!وانەکە تەواو بوو!

Well done. Review the key terms, then go on to the next lesson.دەستخۆش. سەیرێکی زاراوە سەرەکییەکان بکەرەوە، پاشان بڕۆ بۆ وانەی داهاتوو.

Key termsزاراوە سەرەکییەکان

Next lessonوانەی داهاتوو

Inheritance and Polymorphismبۆماوە و فرەشێوەیی

References and further readingسەرچاوەکان و خوێندنەوەی زیاتر

This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.

Appearanceڕووکار

Modeدۆخ
Styleشێواز
Coloursڕەنگەکان

Saved on this device only.تەنیا لەسەر ئەم ئامێرە پاشەکەوت دەکرێت.