__slots__ Python
Введение | |
Пример | |
Разница в поведении | |
Проверка экономии памяти | |
Проверка скорости | |
__slots__ при наследовании | |
Далее по теме |
Введение
Официальная документация:
docs
,
wiki
Статья на сайте SQLAlchemy -
Significant Improvements in Structural Memory Use
Пример
Без использования __slots__ у каждого экземпляра класса есть свой собственный словарь атрибутов
class Developer: def __init__(self, name, age, salary, framework): self.name = name self.age = age self.salary = salary self.framework = framework employee1 = Developer("Andrei", 36, 5000, "FastAPI") print(employee1.__dict__) print(hasattr(employee1, "__slots__"))
{'name': 'Andrei', 'age': 36, 'salary': 5000, 'framework': 'FastAPI'} False
При использовании __slots__ отдельные словари не создаются
class Developer: __slots__ = ("name", "age", "salary", "framework") def __init__(self, name, age, salary, framework): self.name = name self.age = age self.salary = salary self.framework = framework employee1 = Developer("Andrei", 36, 5000, "FastAPI") print(employee1.__dict__)
Traceback (most recent call last): File "C:\Users\Andrei\slots_demo.py", line 13, in <module> print(employee1.__dict__) AttributeError: 'Developer' object has no attribute '__dict__'
Можно обратиться к атрибуту __slots__
… print(employee1.__slots__) print(hasattr(employee1, "__slots__"))
('name', 'age', 'salary', 'framework') True
Разница в поведении
Попробуем добавить аттрибут к обычному классу
class Regular: def __init__(self, name, age): self.name = name self.age = age regular = Regular("Eugene", 59) regular.city = "Benalmadena" print(regular.city)
Если тоже самое проделать с классом, где используются __slots__ появится AttributeError
class Slotted: __slots__ = ("name", "age") def __init__(self, name, age): self.name = name self.age = age slotted = Slotted("Andrei", 36) slotted.city = "Malaga" print(slotted.city)
Traceback (most recent call last): File "C:\Users\Andrei\slots_demo.py", line 10, in <module> slotted.city = "Malaga" ^^^^^^^^^^^^ AttributeError: 'Slotted' object has no attribute 'city'
Проверка экономии памяти
Если теперь в оба примера (без __slots__ и со __slots__) добавить проверку размера объекта метдом asizeof из pympler, окажется, что объект, где применялся __slots__ больше
from pympler import asizeof … print(f"asizeof() slotted size: {asizeof.asizeof(slotted_emp)}") … print(f"asizeof() not slotted size: {asizeof.asizeof(not_slotted_emp)}")
asizeof() slotted size: 240 asizeof() not slotted size: 560
Экономия приличная, но если воспользоваться другим измерителем размера, результат сильно исказится, подробнее в статье «Размер объекта в Python»
Проверка скорости
Выясним есть ли ускорение при обращении к атрибутам
import timeit from sys import getsizeof class Developer: __slots__ = ("name", "age", "salary", "framework") def __init__(self, name, age, salary, framework): self.name = name self.age = age self.salary = salary self.framework = framework class Programmer: def __init__(self, name, age, salary, framework): self.name = name self.age = age self.salary = salary self.framework = framework slotted_emp = Developer("Andrei", 36, 5000, "FastAPI") not_slotted_emp = Programmer("Eugene", 59, 95000, "Spring") # print(employee1.__dict__) # print(employee1.__slots__) def get_set_delete_fn(obj): def get_set_del(): obj.name = "foo" obj.name del obj.name obj.age = "foo" obj.age del obj.age obj.salary = "foo" obj.salary del obj.salary return get_set_del print(f"min slotted: {min(timeit.repeat(get_set_delete_fn(slotted_emp)))}") print(f"max slotted: {max(timeit.repeat(get_set_delete_fn(slotted_emp)))}") print(f"min not slotted: {min(timeit.repeat(get_set_delete_fn(not_slotted_emp)))}") print(f"max not slotted: {max(timeit.repeat(get_set_delete_fn(not_slotted_emp)))}")
min slotted: 0.10821229999999998 max slotted: 0.11044899999999991 min not slotted: 0.14354210000000012 max not slotted: 0.14311980000000002
Ускорение от __slotted__ в нашем примере очевидно.
__slots__ при наследовании
В родительском классе в __slots__ указываются его атрибуты. В производном классе в __slots__ нужно перечислять только новые для этого класса атрибуты. Перечислять заново родительские не нужно.
class Employee: __slots__ = ("name", "age", "salary") def __init__(self, name, age, salary): self.name = name self.age = age self.salary = salary def increase_salary(self, percent): self.salary += self.salary * (percent/100) class Developer(Employee): __slots__ = ("prog_language", "framework",) def __init__(self, name, age, salary, prog_language, framework): super().__init__(name, age, salary) self.prog_language = prog_language self.framework = framework class Tester(Employee): __slots__ = ("test_framework",) def __init__(self, name, age, salary, test_framework): super().__init__(name, age, salary) self.test_framework = test_framework def run_tests(self): print(f"Testing is started by {self.name}...") print("Tests are done.") employee1 = Developer("Andrei", 36, 5000, "python", "FastAPI") employee2 = Tester("Cem", 36, 5000, "PyTest") print(employee1.salary) employee1.increase_salary(10) print(employee1.salary) employee2.run_tests()
5000 5500.0 Testing is started by Cem... Tests are done.
Недостатки
При использовании __slots__ пропадает возможность динамически добавлять атрибуты. Можно пользоваться только тем, что определено в __slots__
При использовании множественного наследования __slots__ нужно указывать во всех родителях, хотя бы пустым.
ООП в Python | |
Классы | |
Методы | |
class variables | |
class methods | |
Статические методы | |
Наследование | |
Специальные методы | |
dataclass | |
__slots__ | |
Декоратор property | |
super() |