Объект в духе Python
Сегодня поговорим об этом:
Продолжим говорить о классах в Python
Повторим, чем отличаются атрибуты классов и экземпляров
Декораторы классов
Декораторы @property, @classmethod, @staticmethod
Объект в духе Python на примере вектора
Как происходит доступ к атрибутам
Опционально: Решение задач из ДЗ и как делать exe файлы из .py
В этом коде есть 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)
Исполнение
Проблема:
>>> 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']
Решим проблему дефолтного значения
Теперь избавимся от мутабельного дефолтного значения
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)
Но теперь
Решили одну проблему, теперь у нас не один и тот же список для всех экземпляров, но наш класс влияет на данные извне экземпляра.
>>> basketball_team = ['Sue', 'Tina', 'Maya', 'Diana', 'Pat']
>>> bus = TwilightBus(basketball_team)
>>> bus.drop('Tina')
>>> bus.drop('Pat')
>>> basketball_team
['Sue', 'Maya', 'Diana']
Правильная реализация класса
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)
Посмотрим на копии
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)
Атрибуты классов и экземпляров
Рассмотрим такой простой класс:
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)
Как работает
Сначала ищет атрибут в экземпляре, если не нашел - в классе, потом в предках
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
Где хранятся атрибуты
В атрибуте __dict__:
class Noop:
"""Doc-str"""
print(*Noop.__dict__) # __module__ __doc__ __dict__ __weakref__
print(*Noop().__dict__) #
@staticmethod
Декоратор staticmethod позволяет объявить статический
метод, то есть просто функцию, внутри класса:
>>> class SomeClass:
... @staticmethod
... def do_something():
... pass
...
>>> SomeClass.do_something()
@classmethod
Для объявления методов класса используется похожий
декоратор classmethod. Первый аргумент метода класса —
непосредственно сам класс, а не его экземпляр.
>>> class Settings:
... @classmethod
... def read_from(cls, path):
... return cls() # your code
...
>>> Settings.read_from("./settings.ini")
<__main__.Settings at 0x10f558208>
Декораторы классов
Работают абсолютно также, как и для функций.
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
Расширим 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
Теперь мы можем делать так:
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
Свойства - @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')
Можно также переопределить логику изменения и удаления таких атрибутов.
Геттеры и сеттеры
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]
__getattr__(self, name)
Вы можете определить поведение для случая, когда пользователь пытается обратиться к атрибуту, который не существует (совсем или пока ещё).
Это может быть полезным для:
__setattr__(self, name, value)
Этот метод позволяет вам определить поведение для присвоения значения атрибуту, независимо от того существует атрибут или нет.
То есть, вы можете определить любые правила для любых изменений значения атрибутов. Впрочем, вы должны быть осторожны с тем, как использовать __setattr__
def __setattr__(self, name, value):
self.name = value
# это рекурсия, так как всякий раз, когда любому атрибуту присваивается значение,вызывается __setattr__()
def __setattr__(self, name, value):
self.__dict__[name] = value # присваивание в словарь переменных класса
# дальше определение произвольного поведения
__getattribute__(self, name)
Этот метод позволяет вам определить поведение для каждого случая доступа к атрибутам (а не только к несуществующим, как __getattr__(self, name)).
Он страдает от таких же проблем с бесконечной рекурсией, как и его коллеги (на этот раз вы можете вызывать __getattribute__ у базового класса, чтобы их предотвратить).
Напишем класс Vector2d
С такими методами:
x, y
__iter__
__repr__
__str__
__bytes__
__eq__
__hash__
__abs__
__bool__
angle
может быть frombytes
__format__
Полезные ссылки
Руководство по магическим методам в Питоне
How can I create a directly-executable cross-platform GUI app using Python?
И все остальные из прошлой презентации