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، هەڵە باوەکان و ڕاهێنان.
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,
_nameand__name, and say what each one really does. - Write getters and setters, first as methods and then as properties with
@propertyand@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 دینار»ـی دەپاراست. ئێستا هەژمارەکە . سەیر بکە کاتێک هەموو کراوە بێت چی ڕوو دەدات:
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)Aram has -30000 dinars
Traceback (most recent call last):
File "open_balance.py", line 12, in <module>
print(acc.balance + 1_000)
~~~~~~~~~~~~^~~~~~~
TypeError: can only concatenate str (not "int") to strExplain 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.
دوو هەڵە و ڕێگەی لە هیچیان نەگرت. یەکەمیان بە بێدەنگی یاسایەکی بانکی شکاند. دووەمیان دواتر وەستاند، دوور لەو دێڕەی هۆکارەکەی بوو. چارەسەر : لە ناو بهێڵەوە و ڕێگە بە کۆدی تر بدە تەنیا لە ڕێگەی ئەو بیگۆڕێت کە هەموو گۆڕانێک دەپشکنن.
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.
و ئەو بەکاری دەهێنن پێکەوە لە یەک کۆ دەکاتەوە و تەنیا ڕێگە بەو دەدات بگۆڕن. ئەوسا دەتوانێت ڕاست بهێڵێتەوە: هەموو گۆڕانێک بە تێدەپەڕێت کە دەتوانێت بڵێت نەخێر.
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.
بانکی ڕاستەقینە قاسەکە قوفڵ دەکات. هیچ شتێک قوفڵ ناکات: متمانە بە دەکات کە مێزەکە بەکار بهێنن، وەک لە پەڕەکانی داهاتوودا دەبینیت.
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ـە؛ پەڕەی داهاتوو ڕوون دەکاتەوە.
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())75000 Refused: not enough money: the balance is 75000 75000
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.
هەڵەکە دەگرێت و بڕیار دەدات چی بکات.


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:
تەنیا ڕێککەوتنێکە. خودی ڕێگە لە کەس ناگرێت:
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))Karwan: -999 dinars
{'owner': 'Karwan', '_balance': -999}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 * نا). پاراستنی ڕاستەقینە ڕێککەوتنی .
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، واتە ناوی بە لە پێشیەوە. ئەمە .
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 outsideFalse
{'owner': 'Dilan', '_BankAccount__pin': '4821'}
4821
Traceback (most recent call last):
File "mangling.py", line 14, in <module>
print(acc.__pin) # the short name does not exist outside
^^^^^^^^^
AttributeError: 'BankAccount' object has no attribute '__pin'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.
ناوە درێژەکە لە دەرەوەش هێشتا کار دەکات.
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) کە پێش هەڵگرتن دەپشکنێت. پشکنینی پێش وەرگرتنی .
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)Hana 0 Hana Ahmed Refused: the owner's name cannot be empty
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()سەرەتا بۆشاییەکانی هەردوو لا لادەبات.
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، بەبێ کەوانە، بانگ دەکات و ئەنجامەکەی دەگەڕێنێتەوە.
@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)(the getter runs) 40000 (the getter runs) 45000
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)".
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 وەک سادە دەردەکەوێت، لە کاتێکدا دەیپشکنێت یان دەیژمێرێت.
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.
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 = " "'Rebin'
Rebin Kamal
Traceback (most recent call last):
File "property_setter.py", line 22, in <module>
acc.owner = " "
^^^^^^^^^
File "property_setter.py", line 14, in owner
raise ValueError("the owner's name cannot be empty")
ValueError: the owner's name cannot be emptyExplain 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 inowner(see Common mistakes).لە
_ownerهەڵیبگرە، هەرگیز لەownerنا (بڕوانە هەڵە باوەکان).- Lineدێڕی 22
An empty name is refused with a clear message.
ناوی بەتاڵ بە پەیامێکی ڕوون ڕەت دەکرێتەوە.


acc.owner = … passes the same three steps.هەموو acc.owner = …ـێک بە هەمان سێ هەنگاودا تێدەپەڕێت.CêITThe 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() دەجووڵێت، کە بڕەکە دەپشکنن.
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: refused25000
Traceback (most recent call last):
File "read_only.py", line 19, in <module>
acc.balance = 1_000_000 # no setter: refused
^^^^^^^^^^^
AttributeError: property 'balance' of 'BankAccount' object has no setterExplain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 6-8
A getter, and no
@balance.setteranywhere.و هیچ
@balance.setterـێک نییە.- Lineدێڕی 19
Python 3.13 says exactly what is missing: a setter.
3.13 ڕێک دەڵێت چی کەمە: .
@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 دەگەڕێنێتەوە، کۆپییەک کە کەس ناتوانێت بیگۆڕێت، بۆیە هیچ بانگکەرێک ناتوانێت پارە دانانێکی ساختە زیاد بکات (تاپڵەکان، کۆپیی ڕاستەقینە).
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)85000
(100000, -30000, 15000)
Traceback (most recent call last):
File "computed.py", line 33, in <module>
acc.history.append(999_999)
^^^^^^^^^^^^^^^^^^
AttributeError: 'tuple' object has no attribute 'append'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()ـی نییە: مێژووەکە پارێزراوە.
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 هەمان بیرۆکە بە و دروست دەکات (چی، نەک چۆن)؛ لە وانەی داهاتوودا دێن.
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() بە دەست پێدەکات چونکە تەنیا خۆی بەکاری دەهێنێت.
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())Aram: 75,000 IQD Shilan: 45,000 IQD Refused: not enough money: 75,000 left Refused: the amount must be more than 0 Statement for Aram open 120,000 withdraw 45,000 balance 75,000
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 بانگکردن واز دەهێنێت (کاتێک خۆبانگکردنەوە هەرگیز ناوەستێت):
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")Traceback (most recent call last):
File "setter_recursion.py", line 16, in <module>
acc = BankAccount("Lana")
File "setter_recursion.py", line 3, in __init__
self.owner = owner
^^^^^^^^^^
File "setter_recursion.py", line 13, in owner
self.owner = name # wrong: this calls the setter again
^^^^^^^^^^
File "setter_recursion.py", line 13, in owner
self.owner = name # wrong: this calls the setter again
^^^^^^^^^^
File "setter_recursion.py", line 13, in owner
self.owner = name # wrong: this calls the setter again
^^^^^^^^^^
[Previous line repeated 995 more times]
RecursionError: maximum recursion depth exceededExplain the codeڕوونکردنەوەی کۆدەکە
- Lineدێڕی 13
This calls the setter again: endless recursion.
ئەمە دیسان بانگ دەکات: بێکۆتایی.
_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)Lana
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_owner | AttributeError: property 'owner' of 'BankAccount' object has no setter | Give 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 callable | Read it without brackets: acc.balanceبەبێ کەوانە بیخوێنەوە: acc.balance |
__init__ writes self._owner = owner directly__init__ ڕاستەوخۆ دەنووسێت self._owner = owner | A 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._moves | Callers 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ڕاهێنانەکان
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وەڵامەکە پیشان بدە
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)25 77.0 -10 14.0 Refused: -300 °C is below absolute zero Refused: property 'fahrenheit' of 'Thermometer' object has no setter
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وەڵامەکە پیشان بدە
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)28 42000 Refused: the price must be more than 0 Refused: only 28 left
sell() changes self.quantity, so even the class's own methods go through the setter's check.
sell() self.quantity دەگۆڕێت، بۆیە تەنانەت خودی بە پشکنینی تێدەپەڕن.
What does this program print? Explain each of the three names.
ئەم چی چاپ دەکات؟ هەر یەکێک لە سێ ناوەکە ڕوون بکەرەوە.
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 گشتییە.
{'_owner': 'Soran', '_Locker__code': '2580', 'number': 7}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وەڵامەکە پیشان بدە
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 = " "'Rebin'
Rebin Kamal
Traceback (most recent call last):
File "property_setter.py", line 22, in <module>
acc.owner = " "
^^^^^^^^^
File "property_setter.py", line 14, in owner
raise ValueError("the owner's name cannot be empty")
ValueError: the owner's name cannot be emptyIn 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وەڵامەکە پیشان بدە
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)30000 Refused: you can take at most 20,000 30000
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. _namemeans internal by convention;__nameis 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:
@propertyand@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 inself.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.
دواتر: بۆماوە و فرەشێوەیی، کە لەسەر تر دروست دەکرێن و یەک کە بۆ زۆر جۆر کار دەکات.
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زاراوە سەرەکییەکان
Inheritance and Polymorphismبۆماوە و فرەشێوەیی
Related lecturesوانە پەیوەندیدارەکان
- Inheritance and Polymorphismبۆماوە و فرەشێوەیی
- Constructors, Modifiers and Encapsulationدروستکەرەکان، دیاریکەرەکان و کەپسوولکردن
- Abstract Classes, Interfaces, Inner Classes and Enumsکلاسە ڕووتەکان، نێوبەستەکان، کلاسە ناوەکییەکان و ئینەمەکان
- Defining and Calling Functionsناساندن و بانگکردنی کردارەکان
- Recursionخۆبانگکردنەوە
- Tuples, Sets and Dictionariesتاپڵەکان، کۆمەڵەکان و فەرهەنگەکان
- Security by Obscurity, by Legislation, and the Trade-off Triangleئاسایش بە شاردنەوە، ئاسایش بە یاسا و سێگۆشەی هاوسەنگی
References and further readingسەرچاوەکان و خوێندنەوەی زیاتر
- Python Tutorial 9.6: Private Variables (the underscore convention and name mangling)
- Python Library Reference: Built-in Functions, property (getter, setter, deleter, read-only attributes)
- Python Glossary (attribute, decorator, descriptor)
- Harvard CS50P, Lecture 8: Object-Oriented Programming (raise in __init__, properties and setters)
- University of Helsinki, Python Programming MOOC 2024, Part 9: Scope of methods (private members, properties with validation)
This lecture is a first edition. The Kurdish text and terminology are waiting for review by Kurdish IT teachers.ئەم وانەیە چاپی یەکەمە. دەقە کوردییەکە و زاراوەکانی چاوەڕێی پێداچوونەوەی مامۆستایانی کوردی بواری IT ن.