1 of 23

Объект в духе Python

2 of 23

Сегодня поговорим об этом:

Продолжим говорить о классах в Python

Повторим, чем отличаются атрибуты классов и экземпляров

Декораторы классов

Декораторы @property, @classmethod, @staticmethod

Объект в духе Python на примере вектора

Как происходит доступ к атрибутам

Опционально: Решение задач из ДЗ и как делать exe файлы из .py

3 of 23

В этом коде есть 2 проблемы

class HauntedBus:

"""A bus model haunted by ghost passengers"""

def __init__(self, passengers=[]): # <1>

self.passengers = passengers # <2>

def pick(self, name):

self.passengers.append(name) # <3>

def drop(self, name):

self.passengers.remove(name)

4 of 23

Исполнение

Проблема:

>>> bus2 = HauntedBus()

>>> bus2.pick('Carrie')

>>> bus2.passengers

['Carrie']

>>> bus3 = HauntedBus()

>>> bus3.passengers

['Carrie']

>>> bus3.pick('Dave')

>>> bus2.passengers

['Carrie', 'Dave']

>>> bus2.passengers is bus3.passengers

True

Нормально работает, если делать так:

>>> bus1 = HauntedBus(['Alice', 'Bill'])

>>> bus1.passengers

['Alice', 'Bill']

>>> bus1.pick('Charlie')

>>> bus1.drop('Alice')

>>> bus1.passengers

['Bill', 'Charlie']

>>> bus1.passengers

['Bill', 'Charlie']

5 of 23

Решим проблему дефолтного значения

Теперь избавимся от мутабельного дефолтного значения

class TwilightBus:

"""A bus model that makes passengers vanish"""

def __init__(self, passengers=None):

if passengers is None:

self.passengers = []

else:

self.passengers = passengers

def pick(self, name):

self.passengers.append(name)

def drop(self, name):

self.passengers.remove(name)

6 of 23

Но теперь

Решили одну проблему, теперь у нас не один и тот же список для всех экземпляров, но наш класс влияет на данные извне экземпляра.

>>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat']

>>> bus = TwilightBus(basketball_team)

>>> bus.drop('Tina')

>>> bus.drop('Pat')

>>> basketball_team

['Sue', 'Maya', 'Diana']

7 of 23

Правильная реализация класса

class Bus:

def __init__(self, passengers=None):

if passengers is None:

self.passengers = []

else:

self.passengers = list(passengers)

def pick(self, name):

self.passengers.append(name)

def drop(self, name):

self.passengers.remove(name)

8 of 23

Посмотрим на копии

import copy

bus1 = Bus(['Alice', 'Bill', 'Claire', 'David'])

bus2 = copy.copy(bus1)

bus3 = copy.deepcopy(bus1)

bus1.drop('Bill')

print(bus2.passengers)

print(bus3.passengers)

9 of 23

Атрибуты классов и экземпляров

Рассмотрим такой простой класс:

class Employee:

raise_amt = 1.04

def __init__(self, first, last, pay):

self.first = first

self.last = last

self.pay = pay

def apply_raise(self):

self.pay = int(self.pay * self.raise_amt)

10 of 23

Как работает

Сначала ищет атрибут в экземпляре, если не нашел - в классе, потом в предках

emp_1 = Employee('Corey', 'Schafer', 50000)

emp_2 = Employee('Test', 'Employee', 60000)

emp_1.raise_amt = 1.06

print(Employee.raise_amt) # 1.04

print(emp_1.raise_amt) # 1.06

print(emp_2.raise_amt) # 1.04

Employee.raise_amt = 1.05

print(Employee.raise_amt) # 1.05

print(emp_1.raise_amt) # 1.06

print(emp_2.raise_amt) # 1.05

11 of 23

Где хранятся атрибуты

В атрибуте __dict__:

class Noop:

"""Doc-str"""

print(*Noop.__dict__) # __module__ __doc__ __dict__ __weakref__

print(*Noop().__dict__) #

12 of 23

@staticmethod

Декоратор staticmethod позволяет объявить статический

метод, то есть просто функцию, внутри класса:

>>> class SomeClass:

... @staticmethod

... def do_something():

... pass

...

>>> SomeClass.do_something()

13 of 23

@classmethod

Для объявления методов класса используется похожий

декоратор classmethod. Первый аргумент метода класса —

непосредственно сам класс, а не его экземпляр.

>>> class Settings:

... @classmethod

... def read_from(cls, path):

... return cls() # your code

...

>>> Settings.read_from("./settings.ini")

<__main__.Settings at 0x10f558208>

14 of 23

Декораторы классов

Работают абсолютно также, как и для функций.

def singleton(cls):

instance = None

@functools.wraps(cls)

def inner(*args, **kwargs):

nonlocal instance

if instance is None:

instance = cls(*args, **kwargs)

return instance

return inner

@singleton

class Noop:

"I do nothing at all."

print(id(Noop())) # 4383371952

print(id(Noop())) # 4383371952

15 of 23

Расширим class Employee

class Employee:

raise_amt = 1.04

...

@classmethod

def set_raise_amt(cls, amount):

cls.raise_amt = amount

@classmethod

def from_string(cls, emp_str):

first, last, pay = emp_str.split('-')

return cls(first, last, pay)

@staticmethod

def is_workday(day):

if day.weekday() == 5 or day.weekday() == 6:

return False

return True

16 of 23

Теперь мы можем делать так:

emp_str_1 = 'John-Doe-70000'

new_emp_1 = Employee.from_string(emp_str_1)

print(new_emp_1.pay) # 70000

import datetime

my_date = datetime.date(2016, 7, 11)

print(Employee.is_workday(my_date)) # True

17 of 23

Свойства - @property

Механизм свойств позволяет объявлять атрибуты, значение которых вычисляется в момент обращения:

class Path:

def __init__(self, current):

self.current = current

def __repr__(self):

return "Path({})".format(self.current)

@property

def parent(self):

return Path(dirname(self.current))

print(Path("./examples/some_file.txt").parent) # Path('./examples')

Можно также переопределить логику изменения и удаления таких атрибутов.

18 of 23

Геттеры и сеттеры

class PosDataModel:

def __init__(self):

self._params = []

@property

def params(self):

return self._params

@params.setter

def params(self, new_params):

assert all(map(lambda p: p > 0, new_params))

self._params = new_params

@params.deleter

def params(self):

del self._params

model = PosDataModel()

model.params = [0.1, 0.5, 0.4]

print(model.params) # [0.1, 0.5, 0.4]

19 of 23

__getattr__(self, name)

Вы можете определить поведение для случая, когда пользователь пытается обратиться к атрибуту, который не существует (совсем или пока ещё).

Это может быть полезным для:

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

20 of 23

__setattr__(self, name, value)

Этот метод позволяет вам определить поведение для присвоения значения атрибуту, независимо от того существует атрибут или нет.

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

def __setattr__(self, name, value):

self.name = value

# это рекурсия, так как всякий раз, когда любому атрибуту присваивается значение,вызывается __setattr__()

def __setattr__(self, name, value):

self.__dict__[name] = value # присваивание в словарь переменных класса

# дальше определение произвольного поведения

21 of 23

__getattribute__(self, name)

Этот метод позволяет вам определить поведение для каждого случая доступа к атрибутам (а не только к несуществующим, как __getattr__(self, name)).

Он страдает от таких же проблем с бесконечной рекурсией, как и его коллеги (на этот раз вы можете вызывать __getattribute__ у базового класса, чтобы их предотвратить).

22 of 23

Напишем класс Vector2d

С такими методами:

x, y

__iter__

__repr__

__str__

__bytes__

__eq__

__hash__

__abs__

__bool__

angle

может быть frombytes

__format__

23 of 23

Полезные ссылки