Приватные атрибуты и свойства (Private Attributes and Properties)

PythonPythonIntermediate
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

В этой лабораторной работе вы узнаете, как инкапсулировать внутренние компоненты объекта, используя приватные атрибуты, и как реализовать декораторы свойств (property decorators) для контроля доступа к атрибутам. Эти методы необходимы для поддержания целостности ваших объектов и обеспечения правильной обработки данных.

Вы также поймете, как ограничить создание атрибутов с помощью __slots__. Мы будем изменять файл stock.py на протяжении всей этой лабораторной работы, чтобы применить эти концепции.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ErrorandExceptionHandlingGroup(["Error and Exception Handling"]) python(("Python")) -.-> python/AdvancedTopicsGroup(["Advanced Topics"]) python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/ControlFlowGroup -.-> python/conditional_statements("Conditional Statements") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/encapsulation("Encapsulation") python/ErrorandExceptionHandlingGroup -.-> python/raising_exceptions("Raising Exceptions") python/AdvancedTopicsGroup -.-> python/decorators("Decorators") subgraph Lab Skills python/conditional_statements -.-> lab-132494{{"Приватные атрибуты и свойства (Private Attributes and Properties)"}} python/classes_objects -.-> lab-132494{{"Приватные атрибуты и свойства (Private Attributes and Properties)"}} python/encapsulation -.-> lab-132494{{"Приватные атрибуты и свойства (Private Attributes and Properties)"}} python/raising_exceptions -.-> lab-132494{{"Приватные атрибуты и свойства (Private Attributes and Properties)"}} python/decorators -.-> lab-132494{{"Приватные атрибуты и свойства (Private Attributes and Properties)"}} end

Реализация приватных атрибутов

В Python мы используем соглашение об именовании, чтобы указать, что атрибут предназначен для внутреннего использования внутри класса. Мы добавляем префикс в виде символа подчеркивания (_) к таким атрибутам. Это сигнализирует другим разработчикам, что эти атрибуты не являются частью публичного API (public API) и не должны быть доступны напрямую извне класса.

Давайте посмотрим на текущий класс Stock в файле stock.py. У него есть переменная класса (class variable) с именем types.

class Stock:
    ## Class variable for type conversions
    types = (str, int, float)

    ## Rest of the class...

Переменная класса types используется внутри класса для преобразования данных строки. Чтобы указать, что это деталь реализации (implementation detail), мы отметим ее как приватную.

Инструкции:

  1. Откройте файл stock.py в редакторе.

  2. Измените переменную класса types, добавив в начало символ подчеркивания, изменив ее на _types.

    class Stock:
        ## Class variable for type conversions
        _types = (str, int, float)
    
        ## Rest of the class...
  3. Обновите метод from_row, чтобы он использовал переименованную переменную _types.

    @classmethod
    def from_row(cls, row):
        values = [func(val) for func, val in zip(cls._types, row)]
        return cls(*values)
  4. Сохраните файл stock.py.

  5. Создайте Python-скрипт с именем test_stock.py для проверки ваших изменений. Вы можете создать файл в редакторе, используя следующую команду:

    touch /home/labex/project/test_stock.py
  6. Добавьте следующий код в файл test_stock.py. Этот код создает экземпляры класса Stock и выводит информацию о них.

    from stock import Stock
    
    ## Create a stock instance
    s = Stock('GOOG', 100, 490.10)
    print(f"Name: {s.name}, Shares: {s.shares}, Price: {s.price}")
    print(f"Cost: {s.cost()}")
    
    ## Create from row
    row = ['AAPL', '50', '142.5']
    apple = Stock.from_row(row)
    print(f"Name: {apple.name}, Shares: {apple.shares}, Price: {apple.price}")
    print(f"Cost: {apple.cost()}")
  7. Запустите тестовый скрипт, используя следующую команду в терминале:

    python /home/labex/project/test_stock.py

    Вы должны увидеть вывод, похожий на:

    Name: GOOG, Shares: 100, Price: 490.1
    Cost: 49010.0
    Name: AAPL, Shares: 50, Price: 142.5
    Cost: 7125.0

Преобразование методов в свойства (Properties)

Свойства (properties) в Python позволяют вам получать доступ к вычисляемым значениям, как к атрибутам. Это устраняет необходимость в круглых скобках при вызове метода, делая ваш код чище и более последовательным.

В настоящее время в нашем классе Stock есть метод cost(), который вычисляет общую стоимость акций.

def cost(self):
    return self.shares * self.price

Чтобы получить значение стоимости, мы должны вызвать его с круглыми скобками:

s = Stock('GOOG', 100, 490.10)
print(s.cost())  ## Calls the method

Мы можем улучшить это, преобразовав метод cost() в свойство (property), что позволит нам получать доступ к значению стоимости без круглых скобок:

s = Stock('GOOG', 100, 490.10)
print(s.cost)  ## Accesses the property

Инструкции:

  1. Откройте файл stock.py в редакторе.

  2. Замените метод cost() свойством (property), используя декоратор @property:

    @property
    def cost(self):
        return self.shares * self.price
  3. Сохраните файл stock.py.

  4. Создайте новый файл с именем test_property.py в редакторе:

    touch /home/labex/project/test_property.py
  5. Добавьте следующий код в файл test_property.py, чтобы создать экземпляр Stock и получить доступ к свойству cost:

    from stock import Stock
    
    ## Create a stock instance
    s = Stock('GOOG', 100, 490.10)
    
    ## Access cost as a property (no parentheses)
    print(f"Stock: {s.name}")
    print(f"Shares: {s.shares}")
    print(f"Price: {s.price}")
    print(f"Cost: {s.cost}")  ## Using the property
  6. Запустите тестовый скрипт:

    python /home/labex/project/test_property.py

    Вы должны увидеть вывод, похожий на:

    Stock: GOOG
    Shares: 100
    Price: 490.1
    Cost: 49010.0
✨ Проверить решение и практиковаться

Реализация валидации свойств (Property Validation)

Свойства (properties) также позволяют вам контролировать, как значения атрибутов извлекаются, устанавливаются и удаляются. Это полезно для добавления валидации (validation) к вашим атрибутам, гарантируя, что значения соответствуют определенным критериям.

В нашем классе Stock мы хотим убедиться, что shares является неотрицательным целым числом, а price - неотрицательным числом с плавающей точкой. Мы будем использовать декораторы свойств (property decorators) вместе с геттерами (getters) и сеттерами (setters) для достижения этой цели.

Инструкции:

  1. Откройте файл stock.py в редакторе.

  2. Добавьте приватные атрибуты _shares и _price в класс Stock и измените конструктор, чтобы использовать их:

    def __init__(self, name, shares, price):
        self.name = name
        self._shares = shares  ## Using private attribute
        self._price = price    ## Using private attribute
  3. Определите свойства (properties) для shares и price с валидацией (validation):

    @property
    def shares(self):
        return self._shares
    
    @shares.setter
    def shares(self, value):
        if not isinstance(value, int):
            raise TypeError("Expected integer")
        if value < 0:
            raise ValueError("shares must be >= 0")
        self._shares = value
    
    @property
    def price(self):
        return self._price
    
    @price.setter
    def price(self, value):
        if not isinstance(value, float):
            raise TypeError("Expected float")
        if value < 0:
            raise ValueError("price must be >= 0")
        self._price = value
  4. Обновите конструктор, чтобы использовать сеттеры свойств (property setters) для валидации (validation):

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares  ## Using property setter
        self.price = price    ## Using property setter
  5. Сохраните файл stock.py.

  6. Создайте тестовый скрипт с именем test_validation.py:

    touch /home/labex/project/test_validation.py
  7. Добавьте следующий код в файл test_validation.py:

    from stock import Stock
    
    ## Create a valid stock instance
    s = Stock('GOOG', 100, 490.10)
    print(f"Initial: Name={s.name}, Shares={s.shares}, Price={s.price}, Cost={s.cost}")
    
    ## Test valid updates
    try:
        s.shares = 50  ## Valid update
        print(f"After setting shares=50: Shares={s.shares}, Cost={s.cost}")
    except Exception as e:
        print(f"Error setting shares=50: {e}")
    
    try:
        s.price = 123.45  ## Valid update
        print(f"After setting price=123.45: Price={s.price}, Cost={s.cost}")
    except Exception as e:
        print(f"Error setting price=123.45: {e}")
    
    ## Test invalid updates
    try:
        s.shares = "50"  ## Invalid type (string)
        print("This line should not execute")
    except Exception as e:
        print(f"Error setting shares='50': {e}")
    
    try:
        s.shares = -10  ## Invalid value (negative)
        print("This line should not execute")
    except Exception as e:
        print(f"Error setting shares=-10: {e}")
    
    try:
        s.price = "123.45"  ## Invalid type (string)
        print("This line should not execute")
    except Exception as e:
        print(f"Error setting price='123.45': {e}")
    
    try:
        s.price = -10.0  ## Invalid value (negative)
        print("This line should not execute")
    except Exception as e:
        print(f"Error setting price=-10.0: {e}")
  8. Запустите тестовый скрипт:

    python /home/labex/project/test_validation.py

    Вы должны увидеть вывод, показывающий успешные допустимые обновления и соответствующие сообщения об ошибках для недопустимых обновлений.

    Initial: Name=GOOG, Shares=100, Price=490.1, Cost=49010.0
    After setting shares=50: Shares=50, Cost=24505.0
    After setting price=123.45: Price=123.45, Cost=6172.5
    Error setting shares='50': Expected integer
    Error setting shares=-10: shares must be >= 0
    Error setting price='123.45': Expected float
    Error setting price=-10.0: price must be >= 0
✨ Проверить решение и практиковаться

Использование __slots__ для оптимизации памяти (Memory Optimization)

Атрибут __slots__ ограничивает атрибуты, которые может иметь класс. Он предотвращает добавление новых атрибутов к экземплярам и снижает использование памяти.

В нашем классе Stock мы будем использовать __slots__, чтобы:

  1. Ограничить создание атрибутов только атрибутами, которые мы определили.
  2. Повысить эффективность использования памяти, особенно при создании большого количества экземпляров.

Инструкции:

  1. Откройте файл stock.py в редакторе.

  2. Добавьте переменную класса __slots__, перечислив все имена приватных атрибутов, используемых классом:

    class Stock:
        ## Class variable for type conversions
        _types = (str, int, float)
    
        ## Define slots to restrict attribute creation
        __slots__ = ('name', '_shares', '_price')
    
        ## Rest of the class...
  3. Сохраните файл.

  4. Создайте тестовый скрипт с именем test_slots.py:

    touch /home/labex/project/test_slots.py
  5. Добавьте следующий код в файл test_slots.py:

    from stock import Stock
    
    ## Create a stock instance
    s = Stock('GOOG', 100, 490.10)
    
    ## Access existing attributes
    print(f"Name: {s.name}")
    print(f"Shares: {s.shares}")
    print(f"Price: {s.price}")
    print(f"Cost: {s.cost}")
    
    ## Try to add a new attribute
    try:
        s.extra = "This will fail"
        print(f"Extra: {s.extra}")
    except AttributeError as e:
        print(f"Error: {e}")
  6. Запустите тестовый скрипт:

    python /home/labex/project/test_slots.py

    Вы должны увидеть вывод, показывающий, что вы можете получить доступ к определенным атрибутам, но попытка добавить новый атрибут вызывает AttributeError.

    Name: GOOG
    Shares: 100
    Price: 490.1
    Cost: 49010.0
    Error: 'Stock' object has no attribute 'extra'
✨ Проверить решение и практиковаться

Согласование валидации типов (Type Validation) с переменными класса (Class Variables)

В настоящее время наш класс Stock использует как переменную класса _types, так и сеттеры свойств (property setters) для обработки типов. Чтобы повысить согласованность и удобство сопровождения, мы согласуем эти механизмы, чтобы они использовали одну и ту же информацию о типах.

Инструкции:

  1. Откройте файл stock.py в редакторе.

  2. Измените сеттеры свойств (property setters), чтобы использовать типы, определенные в переменной класса _types:

    @property
    def shares(self):
        return self._shares
    
    @shares.setter
    def shares(self, value):
        if not isinstance(value, self._types[1]):
            raise TypeError(f"Expected {self._types[1].__name__}")
        if value < 0:
            raise ValueError("shares must be >= 0")
        self._shares = value
    
    @property
    def price(self):
        return self._price
    
    @price.setter
    def price(self, value):
        if not isinstance(value, self._types[2]):
            raise TypeError(f"Expected {self._types[2].__name__}")
        if value < 0:
            raise ValueError("price must be >= 0")
        self._price = value
  3. Сохраните файл stock.py.

  4. Создайте тестовый скрипт с именем test_subclass.py:

    touch /home/labex/project/test_subclass.py
  5. Добавьте следующий код в файл test_subclass.py:

    from stock import Stock
    from decimal import Decimal
    
    ## Create a subclass with different types
    class DStock(Stock):
        _types = (str, int, Decimal)
    
    ## Test the base class
    s = Stock('GOOG', 100, 490.10)
    print(f"Stock: {s.name}, Shares: {s.shares}, Price: {s.price}, Cost: {s.cost}")
    
    ## Test valid update with float
    try:
        s.price = 500.25
        print(f"Updated Stock price: {s.price}, Cost: {s.cost}")
    except Exception as e:
        print(f"Error updating Stock price: {e}")
    
    ## Test the subclass with Decimal
    ds = DStock('AAPL', 50, Decimal('142.50'))
    print(f"DStock: {ds.name}, Shares: {ds.shares}, Price: {ds.price}, Cost: {ds.cost}")
    
    ## Test invalid update with float (should require Decimal)
    try:
        ds.price = 150.75
        print(f"Updated DStock price: {ds.price}")
    except Exception as e:
        print(f"Error updating DStock price: {e}")
    
    ## Test valid update with Decimal
    try:
        ds.price = Decimal('155.25')
        print(f"Updated DStock price: {ds.price}, Cost: {ds.cost}")
    except Exception as e:
        print(f"Error updating DStock price: {e}")
  6. Запустите тестовый скрипт:

    python /home/labex/project/test_subclass.py

    Вы должны увидеть, что базовый класс Stock принимает значения float для цены, в то время как подкласс DStock требует значения Decimal.

    Stock: GOOG, Shares: 100, Price: 490.1, Cost: 49010.0
    Updated Stock price: 500.25, Cost: 50025.0
    DStock: AAPL, Shares: 50, Price: 142.50, Cost: 7125.00
    Error updating DStock price: Expected Decimal
    Updated DStock price: 155.25, Cost: 7762.50

Итог (Summary)

В этой лабораторной работе вы узнали, как использовать приватные атрибуты, преобразовывать методы в свойства (properties), реализовывать валидацию свойств (property validation), использовать __slots__ для оптимизации памяти (memory optimization) и согласовывать валидацию типов (type validation) с переменными класса (class variables). Эти методы повышают надежность, эффективность и удобство сопровождения ваших классов, обеспечивая инкапсуляцию и предоставляя четкие интерфейсы.