Author: Стефан Кънев, Николай Бачийски, Димитър Димитров

# Класове и обекти, част 2

### „ Програмиране с Python“, ФМИ

#### map(lambda _: ' '.join(_), [('Стефан', 'Кънев'),
('Николай', 'Бачийски'), ('Димитър', 'Димитров')])

#### 09.04.2008г.

# Lecture('09.04.2008').previous().overview()

Обектно-ориентирано програмиране...

- ...в Python
- ...като начин на мислене

# ...в Python

    class Vector(object):
        def __init__(self, x, y, z): self._coords = map(float, [x, y, z])
        def length(self): return sum([_ ** 2 for _ in self._coords]) ** 0.5
        def dot(self, other):
            return Vector(self.y() * other.z() - self.z() * other.y(),
                          self.z() * other.x() - self.x() * other.z(),
                          self.x() * other.y() - self.y() * other.x())
        def normalize(self):
            self._coords = [_ / self.length() for _ in self._coords]

        def x(self): return self._coords[0]
        def y(self): return self._coords[1]
        def z(self): return self._coords[2]

    x, y = Vector(0, 2.0, 0), Vector(3.0, 0, 0)
    z = x.dot(y)
    z.normalize()
    print z._coords

# ...като начин на мислене

- Квинтесенция: концепции, които си предават съобщения
- Не е "просто синтаксис"
- Позволява ви по-естествени програми

# Класове в нов стил

- Въведени в Python 2.2 за да добавят нови възможности в езика, без да чупят стар код
- Наследяват `object` или клас, който наследява `object` - `list`, `dict`, `tuple`, `str` и т.н.
- Другите класове се наричат "класически"
- Променят малко множественото наследяване.
- Дават ви нов начин за конструкция - `__new__`.
- Можете да ограничавате полетата им със `__slots__`.

# Класове в нов стил — __slots__

    class Something(object):
        __slots__ = ['spam', 'eggs']

    >>> smth = Something()
    >>> smth.spam = 1
    >>> smth.eggs = 2
    >>> smth.foo = 3

    Traceback (most recent call last):
      File "<stdin>", line 5, in <module>
    AttributeError: 'Something' object has no attribute 'foo'

# Класове в нов стил — __new__

Обикновенно се налага, когато наследявате immutable стандартни класове

      class SpamTuple(tuple):
          def __new__(cls, number = 1):
              result = ("Spam",) * number
              return tuple.__new__(cls, result)

          def countSpam(self):
              return len(self)

      spams = SpamTuple(3)
      print spams             # ('Spam', 'Spam', 'Spam')
      print spams.countSpam() # 3

# Множествено наследяване (1)

При класове от стар стил, атрибутите се търсят в родителите по дълбочина

    class A:
        def spam(self): print "A's spam"
    class B(A): pass
    class C(A):
        def spam(self): print "C's spam"
    class D(B, C): pass

    d = D()
    d.spam() # A's spam

# Множествено наследяване (2)

При класове от нов стил, атрибутите се търсят в родителите по широчина

    class A(object):
        def spam(self): print "A's spam"
    class B(A): pass
    class C(A):
        def spam(self): print "C's spam"
    class D(B, C): pass

    d = D()
    d.spam() # C's spam

# Атрибути

- `getattr(обект, име_на_атрубит)` връща `обект.атрибут`, където атрибут.
- `setattr(обект, име_на_атрибут, стойност)` присвоява стойност на `обект.атрибут`.
- `delattr(обект, име_на_атрибут)` работи като `del обект.атрибут`.

    class Spam: pass

    >>> spam = Spam()
    >>> spam.eggs = "Eggs"
    >>> getattr(spam, 'eggs')
    Eggs
    >>> setattr(spam, 'bacon', 'Spam, eggs and bacon')
    >>> spam.bacon
    Spam, eggs and bacon
    >>> delattr(spam, 'bacon')

# Сравняване на обекти

- Можете да проверите дали два обекта са равни по стойност с оператора `==`
- Можете да проверите дали две имена сочат към един и същи обект с оператора `is`

    >>> a = ['spam', 'eggs', 42]
    >>> b = ['spam', 'eggs', 42]

    >>> a is b
    False
    >>> a == b
    True

    >>> c = a
    >>> a == c
    True
    >>> a is c
    True

# Сравняване на обекти (2)

Можете да предефинирате равенството за обекти от даден клас с метода`__eq__`

    class Vector(object):
        def __init__(self, x, y, z): self._coords = map(float, [x, y, z])
        def __eq__(self, other):
            return all([a == b for a, b in zip(self._coords, other._coords)])

По подразбиране, `__eq__` е имплементирана с `is`

    class Food(object): pass
    spam = Food()
    eggs = Food()
    moreSpam = spam
    print spam == moreSpam, spam is moreSpam # True True
    print spam == eggs, spam is eggs # False False

# Сравняване на обекти (3)

    class Vector(object):
        def __init__(self, x, y, z): self._coords = map(float, [x, y, z])
        def __eq__(self, other):
            return all([a == b for a, b in zip(self._coords, other._coords)])

    >>> a, b = Vector(1.0, 1.0, 1.0), Vector(1.0, 1.0, 1.0)
    >>> print a == b
    True
    >>> print a != b
    True
    >>> print "WTF?!"

- `__eq__` не предефинира `!=`
- За целта трябва да дефинирате метода `__ne__`

# Сравняване на обекти (4)

Други методи за сравняване на обекти:

- `__lt__(self, other)` # self < other
- `__le__(self, other)` # self <= other
- `__gt__(self, other)` # self > other
- `__ge__(self, other)` # self >= other

# Сравняване на обекти (5)

Можете да предефинирате всичките шест оператора `__cmp__(self, other)`. Този метод трябва да сравнява `self` и `other` и да връща:

- Отрицателно число, ако `self < other`.
- Нула, ако `self == other`.
- Положително число, ако `self > other`.

    class Vector(object):
        ...
        def __cmp__(self, other):
            if self.length() < other.length(): return -1
            elif self.length() == other.length(): return 0
            else: return 1

# str и repr (1)

- В Python от всеки обект могат да се получат две важни неща - текстов низ (`str`) и "репрезентация" (`repr`)
- Текстовото представяне се използва при `str(обект)` или при `"%s" % обект`
- Репрезентацията представлява текст, който ако се изпълни като Python код ще създаде обекта на който е извикан - т.е. `eval(repr(обект)) == обект`
- Репрезентация се извежда с `repr(обект)`.

    >>> print "Spam\nand\neggs"
    Spam
    and
    eggs
    >>> print repr("Spam\nand\neggs")
    'Spam\nand\neggs'

# str и repr (2)

Можете да дефинирате текстово представяне и репрезентация със "служебните" методи `__str__` и `__repr__`.

    class Person(object):
        ...
        def __repr__(self):
            return "Person(%s, %s)" % (repr(self.name), repr(self.age))

        def __str__(self):
            return self.name

    >>> mityo = Person("Mityo the Python", 33)
    >>> str(mityo)
    Mityo the Python
    >>> repr(mityo)
    Person('Mityo the Python', 33)
    >>> mityo
    Person('Mityo the Python', 33)
    >>> eval(repr(mityo))
    Person('Mityo the Python', 33)

# Хеш функции

Можете да "задавате" хеш стойностите на вашите обекти дефинирайки метода `__hash__`.

    class Person(object):
        ...
        def __hash__(self):
            return len(self.name) + self.age

Можете да вземете хеша на даден обект с функцията `hash()`.

    >>> mityo = hash(Person("Mityo da Gun", 30))
    42

# Аритметични оператори (1)

Можете да предефинирате аритметичните оператори за вашите типове.

- `__add__(self, other)` за `self + other`
- `__sub__(self, other)` за `self - other`
- `__mul__(self, other)` за `self * other`
- `__div__(self, other)` за `self / other`
- `__floordiv__(self, other)` за `self // other`
- `__mod__(self, other)` за `self % other`
- `__lshift__(self, other)` за `self << other`
- `__rshift__(self, other)` за `self >> other`
- `__and__(self, other)` за `self & other`
- `__xor__(self, other)` за `self ^ other`
- `__or__(self, other)` за `self | other`

# Аритметични оператори (2)

- Всеки оператор има вариантен метод за прилагане на операцията "на място" - например `+=`, `/=` и т.н.
- Метода има същото име, но със `i` след двете подчертавки - `__add__` става `__iadd__`
- Хубаво е когато дефинирате такива методи, те да променят `self` и да връщат `self`

    a, b, c = MagicNumber(3), MagicNumber(5), MagicNumber(7)

    a = a + b # MagicNumber.__add__(a, b)
    a += c    # MagicNumber.__iadd__(a, c)

# Аритметични оператори (3)

- Аритметичните оператори имат още един вариант - когато обекта е от дясната страна на операцията
- Те започват с `r`, т.e. "десния" вариант на `__add__` е `__radd__`
- При `a + b`, ще се извика `b.__radd__` само ако `а` не дефинира `__add__`, а `b` дефинира `__radd__`.
- Ако `b` е от тип, наследник на `a`, то Python ще опита да извика `b.__radd__` преди да пробва с `a.__add__`. По този начин наследници могат да предефинират аритметични операции

# Преобразуване до стандартни типове

Има методи, които може да предефинирате, за преобразования от вашия клас към стандартен тип:

- `__int__(self)` за `int(обект)`.
- `__long__(self)` за `long(обект)`.
- `__float__(self)` за `float(обект)`.
- `__complex__(self)` за `complex(обект)`.
- `__nonzero__(self)` за `bool(обект)`.

# Колекции

Python ви предлага и оператори, с които можете да третирате вашия клас като колекция:

- `__len__(self)` за `len(обект)`.
- `__getitem__(self, key)` за `обект[key]`.
- `__setitem__(self, key, value)` за `обект[key] = value`
- `__delitem__(self, key)` за `del обект[key]`.
- `__contains__(self, item)` за `item in обект`.

# Обекти, които могат да бъдат извиквани като функции

Можете да предефинирате оператора две скоби `()`.

    class Stamp(object):
        def __init__(self, name): self.name = name
        def __call__(self, something):
            print "%s was stamped by %s" % (something, self.name)

    >>> stamp = Stamp("The goverment")
    >>> stamp("That thing there")
    That thing there was stamped by The goverment

# Още атрибути

Можете да предефинирате достъпа до атрибутите на вашите обекти с `__getattr__`, `__setattr__` и `__delattr__`. Сигнатурите са следните:

- `__getattr__(self, name)` за `something = object.name`
- `__setattr__(self, name, value)` за `object.name = "Foo"`
- `__delattr__(self, name)` за `del object.name`

# Още атрибути (2)

`__getattr__(self, name)` се извиква само, ако обекта няма атрибут с име `name`.

    class Spam(object):
        def __getattr__(self, name):
            print "Getting attribute: " + name
            return None

    spam = Spam()
    spam.eggs = "Eggs"
    print spam.eggs
    print spam.foo

    Eggs
    Getting attribute: foo
    None

# Още атрибути (3)

`__setattr__` се извиква, когато присвоявате стойност на атрибута на даден обект. За да не изпаднете в безкрайна рекурсия, ползвайте `object.__setattr__`

    class Turtle(object):
        def __setattr__(self, name, value):
            print "Setting attribute: " + name
            object.__setattr__(self, name, value)

    turtle = Turtle()
    turtle.spam = "Spam" # prints 'Setting attribute: spam'

# Още атрибути (4)

Класовете нов стил имат специален метод `__getattribute__`, който работи подобно на `__getattr__`, с тази разлика че се извиква винаги, без значение дали обекта има такъв атрибут или не. Отново, за да не изпаднете в бездънна рекурсия, ползвайте `object.__getattribute__`.

    class Crab(object):
        def __getattribute__(self, name):
            print "Getting attribute: " + name
            return object.__getattribute__(self, name)

    crab = Crab()
    crab.spam = "Spam"
    crab.eggs = "Eggs"
    print crab.spam
    print crab.eggs

    Getting attribute: spam
    Spam
    Getting attribute: eggs
    Eggs