#!/usr/bin/env python # coding: utf-8 # # язык программирования Python # ## часть II - ООП # # автор: **Дьяконов Александр www.dyakonov.org** # # **для поддержки курсов автора, в частности https://github.com/Dyakonov/IML** # # # ##### материал частично основан на... # # * *Bruce Eckel* **Python 3 Patterns, Recipes and Idioms** # * *Никита Лесников* **Беглый обзор внутренностей Python** // slideshare # * *Сергей Лебедев* **Лекции по языку Питон** // youtube, канал "Computer Science Center" # * Learn X in Y minutes https://learnxinyminutes.com/docs/python/ # * *Дэн Бейдер* **Чистый Python. Тонкости программирования для профи** # - Всё - объекты # - Программа = объекты, посылающе друг другу сообщения. # - Объект имеет собственную часть памяти и может состоять из других объектов. # - Объект имеет тип (класс): формализует действия объекта / над объеком # In[8]: a = 1 b = 2.5 print (a + b) print (a.__add__(b)) # НЕ РАБОТАЕТ # In[34]: # в Python всё - объекты. Имеют id и значение a = 1 b = [1, 2] print (id(a), id(b)) a = a + 1 b.append(3) # id не изменится print (id(a), id(b)) del a # удаление объекта # a # будет ошибка # In[21]: # определение класса # первый аргумент всех методов - экземпляр класса class MyGraph: def __init__(self, V, E): # конструктор (деструктор - __del__) self.vertices = set(V) self.edges = set(E) def add_vertex(self, v): # метод - функция, объявленная в теле класса self.vertices.add(v) def add_edge(self, e): self.vertices.add(e[0]) self.vertices.add(e[1]) self.edges.add(e) def __str__(self): # представление в виде строки return ("%s; %s" % (self.vertices, self.edges)) g = MyGraph([1,2,3], [(1,2), (1,3)]) g.add_edge((3,4)) print (g) # In[22]: print (g.vertices) print (g.__getattribute__('vertices')) g.__setattr__('vertices', set([1,2,3,4,5])) print (g) # In[1]: class X: a = 0 # обычный атрибут _b = 1 # не желательно пользоваться, но доступен __c = 2 # доступен под другим именем ('_X__c') print (dir(X)) print (X.__dict__) # все атрибуты в виде словаря # In[1]: # множественное наследование class A: def make(self, x): print(x+1) class B: def make(self, x): print(x-1) class C(A, B): pass c = C() c.make(2) print (C.__mro__) # в таком порядке ищутся методы # In[6]: # класс class Human(object): species = "H. sapiens" # атрибут # инициализатор # когда класс инициализируется def __init__(self, name): self.name = name # инициализация атрибута self.age = 0 # инициализация свойства # метод класса, первый аргумент - self def say(self, msg): return "{0}: {1}".format(self.name, msg) # общий метод для всех экземпляров # первый аргумент - кто вызвал @classmethod def get_species(cls): return cls.species # вызывается без ссылки на вызвавшего @staticmethod def grunt(): return "статика..." # свойство - превращает метод в атрибут @property def age(self): return self._age # для присваивания свойству @age.setter def age(self, age): self._age = age # для удаления свойства @age.deleter def age(self): del self._age # In[7]: # инициализация i = Human(name="Иван") print (i.say("привет")) j = Human("Сергей") print (j.say("пока")) print (i.get_species()) # меняем атрибут общий - для всего класса Human.species = "H. neanderthalensis" print (i.get_species(), i.species) print (j.get_species(), j.species) print (Human.grunt()) # статический метод i.age = 42 # свойство print (i.age) del i.age # i.age # будет исключение # In[5]: class Myclass: __slots__ = ['name1', 'name2'] # указываем ВСЕ возможные атрибуты # -- занимает меньше памяти c = Myclass() c.name1 = 10 c.name2 = lambda x: x * x c.name2(2) #c.name3 = 20 # должна быть ошибка (в Python2 - нет) #c.name3 # In[9]: a = [1, 2] b = a print (b) a[0] = 3 print (b) # a и b идентичны del a print (b) # но b остаётся! # In[17]: # чтобы Unicode-литералы правильно воспринимались интерпретатором, в начале программы укажите # -*- coding: koi8 -r -*- # -*- coding: cp1251 -*- # 2я запись - в Win import encodings.aliases print (encodings.aliases.aliases.values()) # возможные кодировки # In[17]: # подсчёт числа объектов соответствующего класса class Counter: Count = 0 # счётчик def __init__(self, name): # внимание к отступам self.name = name # обращение через self Counter.Count += 1 print (name, ' создан, счётчик =', Counter.Count) # Counter.Count - у класса, а не объекта! def __del__(self): Counter.Count -= 1 print (self.name, 'удалён, счётчик = ', Counter.Count) if Counter.Count == 0: print ('Больше объектов нет...') x = Counter("First") y = Counter("Second") del x z = Counter("Third") del z # если запустить второй раз - какая-то ерунда... # In[16]: # del x # del y del Counter # ## Метаклассы # # Метакласс - класс для создания других классов (его экземпляры тоже классы) # # Пример - класс **type** (все классы это его экземпляры) # # **зачем нужны** (не будем) # - изменение поведения классов # - сохраняются при наследовании # - временно подменяют --dict-- (?) # In[14]: class Anyclass: x = 10 print (type(Anyclass)) class Nextclass(Anyclass): # Python 3 (metaclass = Anyclass): y = 20 print (type(Nextclass)) # In[8]: class C: pass # эквивалентная запись: С = type('C', (), {}) # type - дефолтный метакласс для создания классов # In[11]: def myprint(self): print("список: ", self) # такой способ создания класса MyList = type('MyList', (list,), dict(x=10, myprint=myprint)) ml = MyList() ml.append("one") ml.append("two") ml.myprint() # In[13]: class MyClass: pass # это класс! MyClass.field = 10 MyClass.method = lambda x: "Привет, мир!" x = MyClass() x.field2 = 5 x.method2 = lambda x: "Пока, мир!" print (x.field, x.method(), x.field2, x.method2(None)) # x.method2 - нужен аргумент # In[10]: # есть атрибуты экземпляра # а есть атрибуты класса class MyClass: pass MyClass.field = 10 x = MyClass() y = MyClass() MyClass.field = 5 # модификация произойдёт во всех объектах print (x.field, y.field) # In[26]: # много ещё чего про метаклассы - не стал писать # In[2]: # ???? - см. Python 3 class Path: def __init__(self, directory): self.directory = directory def __repr__(self): return "Path({})".format(self.directory) @property def parent(self): return Path(dirname(self.directory)) p = Path("./main/file.txt") p # In[17]: class Myclass: val = 0 def __getattr__(self, name): # __getattr__ - при вызове несуществующего атрибута return name m = Myclass() print (m.my_attr) # In[31]: class Myclass: val = 0 m = Myclass() setattr(m, "val", 2) # getattr - безопасное добавление атрибута # setattr(m, "val_other", 3.0) # m.val_other = 3.0 создастся новый атрибут print (getattr(m, "val")) # getattr - безопасный вызов атрибута print (getattr(m, "val_some_other", 1.0)) m.__dict__ # In[5]: class Vec: def __init__(self, x=0.0, y=0.0): self.x = x self.y = y def __str__(self): # представление в виде строки return ("(%s, %s)" % (self.x, self.y)) x = Vec(1, 2) print (x) # ## Дескриптор # # -- атрибут объекта со скрытым поведеним, которое задают методы в протоколе дескриптора: __get__(), __set__(), and __delete__() # In[5]: class RevealAccess(object): """Пример дескриптора данных: устанавливает и возвращает значения, а также пишет сообщения """ def __init__(self, initval=None, name='var'): self.val = initval self.name = name def __get__(self, obj, objtype): print('Выдаём', self.name) return (self.val + 1) # выдаёт следующее число def __set__(self, obj, val): print('Получаем', self.name) if val<0: print('Отрицательное значение - будет обнулено') self.val = 0 else: self.val = val class MyClass(object): x = RevealAccess(10, 'var "x"') # у переменное скрытое поведение y = 5 m = MyClass() print (m.x) m.x = 20 print (m.x) m.x = -20 print (m.x) print(m.y) # In[6]: # свойства в Питоне - "быстрый" дескриптор # "безопасный класс" # контролирует значения атрибутов class SafeClass: def _get_attr(self): return self._x def _set_attr(self, x): assert x > 0, "необходимо положительное значение" self._x = x def _del_attr(self): del self._x x = property(_get_attr, _set_attr, _del_attr) safe = SafeClass() safe.x = 1 # safe.x = -2 # будет исключение # In[13]: # ??? пока не понял - МОЖНО УДАЛИТЬ class NonNegative: def _get_attr(self): return self._x def _set_attr(self, x): assert x > 0, "необходимо положительное значение" self._x = x def _del_attr(self): del self._x class SafeClass2: x = NonNegative() y = NonNegative() # In[17]: safe = SafeClass2() # In[18]: safe.x = -2 # In[21]: safe.y = -2 # In[23]: class SafeClass2: x = SafeClass() y = SafeClass()