Обектно-ориентирано програмиране, част 2

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

29.03.2010 г

Lecture('29.03.2010').previous().overview()

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

Погледнато философски

В Python

Vector (1)

class Vector:
    pass

Vector (2)

class Vector:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z

spam = Vector(1.0, 2.0, 3.0)
print(spam.x)

Vector (3)

class Vector:
    def __init__(self, x, y, z): ...

    def length(self):
        return (self.x * self.x + self.y * self.y + self.z * self.z) ** 0.5

spam = Vector(1.0, 2.0, 3.0)
print(spam.length())

Vector (4)

class Vector:
    def __init__(self, x, y, z): ...

    def _coords(self):
        return (self.x, self.y, self.z)

    def length(self):
        return sum(_ ** 2 for _ in self._coords()) ** 0.5

Извикване през класа

v1 = Vector(1.0, 2.0, 3.0)
v2 = Vector(4.0, 5.0, 6.0)
v3 = Vector(7.0, 8.0, 9.0)

print(Vector.length(v1))
print(Vector.length(v2))
print(map(Vector.length, [v1, v2, v3]))

Vector (5)

class Vector:
    def __init__(self, x, y, z): ...
    def length(self): ...
    def normalize(self):
        length = self.length()
        self.x /= length
        self.y /= length
        self.z /= length

Vector (6)

class Vector:
    def __init__(self, x, y, z): ...
    def length(self): ...
    def normalized(self):
        return Vector(self.x / self.length(),
                      self.y / self.length(), self.z / self.length())

normalize vs normalized

class Vector:
    def normalize(self):
        length = self.length()
        self.x /= length
        self.y /= length
        self.z /= length

    def normalized(self):
        return Vector(self.x / self.length(), self.y / self.length(), self.z / self.length())

Ако имате само едно от двете, кое предпочитате?

(верен отговор по-късно)

mutable срещу immutable

Нещо подобно

Искаме да пишем на всички активни потребители.


emails = []
for user in User.all():
    if user.active():
        emails.append(user.email)
# или
emails = [user.email for user in User.all() if user.active()]

Кое и защо?

Fred Brooks

Vector (7)

class Vector:
    def __init__(self, x, y, z): ...
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y, self.z + other.z)

spam = Vector(1.0, 2.0, 3.0)
eggs = Vector(4.0, 5.0, 6.0)
breakfast = spam + eggs

Vector (8)

class Vector:
    def __init__(self, x, y, z): ...
    def _coords(self): ...
    def __add__(self, other):
        return Vector(*map(sum, zip(self._coords(), other._coords())))

spam = Vector(1.0, 2.0, 3.0)
eggs = Vector(4.0, 5.0, 6.0)
breakfast = spam + eggs

По-хакерско, но спорно дали по-четимо

Vector (9)

class Vector:
    def __init__(self, x, y, z): ...
    def _coords(self): ...

def addition(a, b):
    return Vector(a.x + b.x, a.y + b.y, a.z + b.z)

Vector.__add__ = addition

print(Vector(1.0, 2.0, 3.0) + Vector(4.0, 5.0, 6.0))

Vector (10)

Ако искате да достъпвате компонентите на вектора с v[0], v[1] и v[2]:

class Vector:
    def __init__(self, x, y, z): ...
    def __getitem__(self, i):
        return (self.x, self.y, self.z)[i]

Vector (11)

Можете да направите вектора да се държи като колекция:

class Vector:
    def __init__(self, x, y, z): ...
    def __getitem__(self, i):
        return (self.x, self.y, self.z)[i]

    def __len__(self):
        return 3

    def length(self):
        return sum(_ ** 2 for _ in self) ** 0.5

    def __add__(self, other):
        return Vector(*map(sum, zip(self, other)))

Vector (12)

Може и да имплементирате присвояване на индекс:

class Vector:
    def __init__(self, x, y, z): ...
    def __getitem__(self, i): ...
    def __setitem__(self, index, value):
        if   index == 0: self.x = value
        elif index == 1: self.y = value
        elif index == 2: self.z = value
        else: pass # Тук е добро място за изключение

v = Vector(1, 2, 3)
v[1] = 10
print(v.y) # 10

Разбира се, по-добре вектора да е immutable.

Атрибути

class Spam: pass

spam = Spam()

spam.eggs = "Eggs"
print(getattr(spam, 'eggs')) # Eggs

setattr(spam, 'bacon', 'Spam, eggs and bacon')
print(spam.bacon) # Spam, eggs and bacon

Атрибути (2)

Може да дефинирате __getitem__ и __setitem__ по-компактно:

class Vector:
    def __init__(self, x, y, z): ...

    def __getitem__(self, i):
        return getattr(self, ('x', 'y', 'z')[i])

    def __setitem__(self, index, value):
        return setattr(self, ('x', 'y', 'z')[i], value)

Атрибути (3)

Може да предефинирате „оператора точка“:

Атрибути (4)

__getattr__(self, name) се извиква само ако обекта няма атрибут name.
class Spam:
    def __init__(self):
        self.eggs = 'larodi'

    def __getattr__(self, name):
        return name.upper()

    def answer(self):
        return 42

spam = Spam()
print(spam.foo) # FOO
print(spam.bar) # BAR
print(spam.eggs) # larodi
print(spam.answer()) # 42

Атрибути (5)

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

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

class Spam:
    def __setattr__(self, name, value):
        print("Setting {0} to {1}".format(name, value))
        return object.__setattr__(self, name.upper(), value + 10)

spam = Spam()
spam.foo = 42
print(spam.FOO) # 52
print(spam.foo) # грешка!

Атрибути (6)

Обектите и питоните

Опростен модел: Всеки обект се състои от две неща:
class Spam: pass

spam = Spam()
spam.foo = 1
spam.bar = 2
print(spam.__dict__) # {'foo': 1, 'bar': 2}
print(spam.__class__) # <class '__main__.Spam'>
print(spam.__class__ is Spam) # True

Обектите и питоните (2)

Още по-опростено: Функциите и променливите дефинирани в тялото на класа са атрибути на класа.
class Spam:
    def foo(self): 
        return 1
    
    bar = 42

print(Spam.foo) # <function foo at 0x0c4f3b4b3>
print(Spam.bar) # 42

Обектите и питоните (3)

Когато извикате object.attr:

class Spam:
    answer = 42
    def __init__(self, x):
        self.x = x
    def add(self, y):
        return self.x * y

spam = Spam(6)
print(spam.add) # <bound method Spam.add of <__main__.Spam object at 0x0d34db33f>>
print(spam.add(9)) # 54
print(spam.answer) # 42

Обектите и питоните (4)

Наследяване

class Person:
    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name
    
    def name(self):
        return self.first_name + " " + self.last_name

class Star(Person):
    def greet_audience(self):
        print("Hello Sofia, I am {0}!".format(self.name()))

david = Star("David", "Gaham")
david.greet_audience()
# Hello Sofia, I am David Gaham!

Наследяване (2)

class Person:
    def __init__(self, first_name, last_name):
        self.first_name, self.last_name = first_name, last_name
    
    def name(self):
        return "{0} {1}".format(self.first_name, self.last_name)

class Japanese(Person):
    def name(self):
        return "{0} {1}".format(self.last_name, self.first_name)

print(Person("Edward", "Murdsone").name()) # Edward Murdstone
print(Japanese("Yukihiro", "Matsumoto").name()) # Matsumoto Yukihiro

Наследяване (3)

class Person:
    def __init__(self, first_name, last_name):
        self.first_name, self.last_name = first_name, last_name
    
    def name(self):
        return "{0} {1}".format(self.first_name, self.last_name)

class Doctor(Person):
    def name(self):
        return "{0}, M.D.".format(Person.name(self))

print(Doctor("Gregory", "House").name()) # Gregory House, M.D.

Множествено наследяване

class Spam:
    def spam(self): return "spam"

class Eggs:
    def eggs(self): return "eggs"

class CheeseShop(Spam, Eggs):
    def food(self):
        return self.spam() + " and " + self.eggs()

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

Методи се търсят в широчина (breath-first):

class A:
    def spam(self): return "A.spam"
class B(A):
    pass
class C(A):
    def spam(self): return "C.spam"
class D(B, C):
    pass

print(D().spam()) # C.spam

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

Да, в широчина:

class A:
    def spam(self): return "A.spam"
class B(A):
    def spam(self): return "B.spam"
class C(A):
    def spam(self): return "C.spam"
class D(B, C):
    pass

print(D().spam()) # B.spam

24 карата

Ако изпаднете в диамантено наследяване, имате проблем. Обикновено първопричината не е в кода.

(но има и изключения)

private и protected

class Spam:
    def __init__(self):
        self.__var = 42

print(dir(Spam())) # ['_Spam__var', '__class__', ...]

private и protected (2)

class Base:
    def __init__(self, name, age):
        self.__name = name
        self._age = age
    def report_base(self):
        print("Base:", self.__name, self._age)

class Derived(Base):
    def __init__(self, name, age, derived_name):
        Base.__init__(self, name, age)
        self.__name = derived_name
        self._age = 33
    def report_derived(self):
        print("Derived:", self.__name, self._age)

derived = Derived("John", 0, "Doe")
print(derived.report_base()) # Base: John 33
print(derived.report_derived()) # Derived: Doe 33
print(derived._Base__name, derived._Derived__name, sep=', ') # John, Doe

isinstance и issubclass

print(isinstance(3, int)) # True
print(isinstance(4.5, int)) # False

print(issubclass(int, object)) # True
print(issubclass(float, int)) # False