воскресенье, 20 января 2013 г.

Основы ООП в Python



Классы в Python это полноценные объекты,  даже если нет ни одного экземпляра.
Самый простой класс создается так:

>>> class database :
    pass

>>>

Это пустой класс с именем database без атрибутов и без методов.

Создадим класс с одним атрибутом:

>>> class database :
    name = 'oracle'
   
>>>

Теперь мы можем обращаться к этому атрибуту класса за пределами самого класса:

>>> print(database.name)
oracle
>>>

и даже изменять его значение:

>>> database.name = "ora11gR2"
>>> print(database.name)
ora11gR2
>>>

Тоже самое можно сделать и так:

- сначала создаем пустой класс

>>> class database :
    pass

>>>

После создания класса мы можем присоединить к нему атрибуты,
выполняя операции присваивания за пределами класса:

>>> database.name = "oracle"
>>> database.version = "11.2.0.2"
>>>
>>> print(database.name)
oracle
>>> print(database.version)
11.2.0.2
>>>
>>> database.version = "11.2.0.3"
>>> print(database.version)
11.2.0.3
>>>

Пока у нас имеется ссылка на класс, мы можем в любое время
добавлять или изменять его атрибуты по своему усмотрению.

Пусть у нас имеется простой класс с одним атрибутом:

>>> class test:
    name = 'Larry'

   
>>>

Создадим два экземпляра этого класса:

>>> x = test()
>>> y = test()
>>>

так как они помнят класс из которого были созданы,
они по наследству получат атрибуты класса:

>>> x.name, y.name
('Larry', 'Larry')
>>>

Если выполнить присваивание атрибуту экземпляра,
то будет создан (или изменен) атрибут этого конкретного объекта, а не другого.

Атрибуты обнаруживаются в результате поиска по дереву наследования,
но операция присваивания значения атрибуту воздействует только на тот объект,
к которому эта операция применяется.

>>>
>>> x.name = "Tom"
>>>
>>> test.name, x.name, y.name
('Larry', 'Tom', 'Larry')
>>>

Экземпляр x получил свой собственный атрибут name,
а экземпляр y по прежнему наследует атрибут name, присоединенный к классу test.

Добавим в наш простой класс метод setname с помощью которого мы получим возможность
изменять значение атрибута.


>>> class test:
    name = 'Larry'
    def setname(self, value):
        self.name = value

>>>

Создадим экземпляр класса test.

>>> x = test()
>>>

Он также по наследству получит атрибут name.

>>> x.name
'Larry'
>>>

Но теперь мы можем вызвать метод класса setname который изменяет значение атрибута name.
В качестве параметра укажем, атрибут какого экземпляра мы хотим изменить а также новое значение атрибута name.

>>> test.setname(x, "Tom")
>>>

Мы вызвали данный метод, как метод класса test.

test.setname(...)

Но так как наш экземпляр x, также по наследству от класса test получит и метод setname:

def setname(self, value):
    self.name = value

но уже в таком виде:

def setname(x, value):
    x.name = value

тут уже первый параметр - это имя созданного экземпляра.

То теперь проще вызвать данный метод не из класса test, а из созданного экземпляра x:

>>> x.setname("Tom")
>>>

Имя self внутри метода автоматически ссылается на обрабатываемый экземпляр x,
поэтому операция присваивания сохраняет значение в пространстве имен экземпляра а не класса.


В классе test атрибуты можно и не объявлять, а оставить только метод setname.

>>> class test:
    def setname(self, value):
            self.name = value

>>>

Тогда, после создания экземпляра

>>> x = test()
>>>

Никаких атрибутов он от класса не унаследует.
Унаследуется только метод setname.

И только после первого вызова метода setname

>>> x.setname("Larry")
>>>

У нас появится новый атрибут экземпляра с именем name

>>> x.name
'Larry'
>>>

Методы, которые обычно создаются инструкциями def, вложенными в инструкцию class,
могут создаваться совершенно независимо от объекта класса.

Пусть у нас имеется класс:

>>> class test:
    name = 'Larry'
    def setname(self, value):
        self.name = value

>>>

Мы хотим добавить в этот класс еще один метод getname,
который выводит текущее значение атрибута name.

Определим некую функцию вне класа:

def xyz(self):
    print(self.name)

Это обычная функция, она ничего незнает о классе test.
Она может вызываться как обычная функция.

Есть только единственное ограничение:
Объект, который она получает в качестве параметра, должен иметь атрибут name.

(Имя аргумента "self" не имеет никакого особого смысла и может называться как угодно)

Вызовем эту функцию, передав ей в качестве параметра наш ранее созданный объект x.

>>> xyz(x)
Larry
>>>

Однако, если эту функцию присвоить атрибуту нашего класса, она станет методом, вызываемым из любого экземпляра.
(А также через имя самого класса при условии, что функции вручную будет передан экземпляр)

>>> test.getname = xyz

>>> x.getname()
Larry

Вызвать через имя класса можно так:

>>> test.getname(x)
Larry
>>>

В итоге у нас получился такой класс:

class test:

    name = 'Larry'

    def setname(self, value):
        self.name = value

    def getname(self):
        print(self.name)


Определим новый класс testnew, который наследует все имена из класса test и добавляет свои собственные.


>>> class testnew(test):
    def dispname(self):
        print('Current value = "%s"' % self.name)

        # и такой же метод как и в test
    def getname(self):
        print('Value = "%s"' % self.name)
       
>>>


>>> x = test()

>>> z = testnew()

>>> z.setname("Tom")

>>> z.getname()     # вызовется метод из класса testnew
Value = "Tom"

>>> x.getname()    # вызовется метод из класса test
Larry
>>>

Допустим что класс test находится в модуле modtest.py

Мы хотим создать класс testnew который бы унаследовал все имена класса test.

Тогда нужно импортировать этот модуль

from modtest import test
class testnew(test):
...

Или эквивалентный вариант, импортируем весь модуль целиком:

import modtest
class testnew(modtest.test):
...
тут в скобках мы указываем полное имя.


Пусть имеется файл modtest.py и в нем определен класс:

class test:


Чтобы получить доступ к этому классу, нам необходимо обратиться к модулю, как обычно:

import modtest

x = modtest.test()

обращаемся к модулю и классу внутри модуля.

Можно также использовать инструкцию from

from modtest import test

x = test()

тут обращаемся только к классу.

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

import  modtest
x = modtest.Test()


Пусть имеется класс:

class MyClass:
    def display(self):
    print('Current value = "%s"' % self.data)

Создадим такой класс:

>>> class MyNewClass(MyClass):
    def __init__(self, value):
        self.data = value
    def __add__(self, other):
        return MyNewClass(self.data + other)
    def __str__(self):
        return 'MyNewClass: ' + self.data
    def mul(self, other):
        self.data *= other

       
>>> a = MyNewClass("abc")

>>> a.display()
Current value = "abc"

>>> print(a)
MyNewClass: abc

>>> b = a + 'xyz'

>>> b.display()
Current value = "abcxyz"

>>> print(b)
MyNewClass: abcxyz

>>> a.mul(3)

>>> print(a)
MyNewClass: abcabcabc

Обратите внимание, что метод __add__ создает и возвращает новый объект экземпляра
этого класса (вызывая MyNewClass, которому передается значение результата)

А метод mul изменяет текущий объект экземпляра выполняя присваивание атрибуту аргумента self.

Обычно встроенные типы, такие как числа и строки, всегда создают новые объекты при выполнении оператора *.


Атрибуты пространства имен.

>>> class test:
    pass

>>>
>>> test.name = "Tom"
>>> test.age = 40
>>>
>>> x = test()
>>> y = test()
>>>
>>> print(x.name)    # Унаследованные атрибуты
Tom
>>> print(y.name)    # Унаследованные атрибуты
Tom
>>>
>>> x.name = "Larry"
>>> print(x.name)     # Экземпляр x получил собственный атрибут
Larry
>>>

Атрибуты пространства имен обычно реализованы в виде словарей.
И деревья наследования классов тоже всего лишь словари со ссылками на другие словари.

Например в большинстве объектов, созданных на базе классов имеется атрибут :

__dict__

Который является словарем пространства имен.

>>> test.__dict__.keys()
dict_keys(['__module__', 'name', 'age', '__dict__', '__weakref__', '__doc__'])
>>>

>>> list(x.__dict__.keys())
['name']
>>>

>>> list(y.__dict__.keys())
[]
>>>

Как видим в словаре класса присутствуют атрибуты name и age, которые созданы ранее.

Объект x имеет свой собственный атрибут name, а объект y по прежнему пуст.

Каждый экземпляр имеет ссылку на свой наследуемый класс, она называется :

__class__

>>> x.__class__

>>>

Классы также имеют атрибут __bases__, который представляет собой картеж его суперклассов:

>>> test.__bases__
(,)
>>>

Эти два атрибута описывают, как деревья классов размещаются в памяти.

Классы и экземпляры - это всего лишь объекты пространства имен
с атрибутами создаваемыми на лету с помощью операции присваивания.

Обычно эти операции присваивания выполняются внутри инструкции class,
но они могут находиться в любом другом месте, где имеется ссылка на один из объектов в дереве.



#Модуль: test1.py

gl1 = 999              # Глобальная переменная модуля

def fn1():             # Имя функции глобально в пределах модуля
    lf1 = 111          # Локальная переменная в функции
    print("-fn1-")
    print("Видна только в функции", lf1)


class Cl1:

    ac1 = 888          # Атрибут класса

    def mt1(self):     # Метод экземпляра
        lm1 = 777      # Локальная переменная в методе
        self.ae1 = 555 # Атрибут экземпляра
        print("-mt1-")
        print("Видна только в методе", lm1)
        print("Видна только в экземпляре", self.ae1)


    def mt2():     # Статический метод
        lm2 = 333      # Локальная переменная в методе
        print("-mt2-")
        print("Видна только в методе", lm2)
    mt2 = staticmethod(mt2)


    def mt3(cls):     # Метод класса
        lm3 = 444      # Локальная переменная в методе
        print("-mt2-")
        print("Видна только в методе", lm3)
    mt3 = classmethod(mt3)


# Глобальная переменная модуля
print(gl1)

# Функция модуля
fn1()

# Атрибут класса
print(Cl1.ac1)

# Обращение к методам экземпляра

em1 = Cl1()      # Создаем ссылку экземпляр класса

Cl1.mt1(em1)     # Обязательно передаем имя экземпляра первым параметром
em1.mt1()        # Или так

Cl1().mt1()      # Ссылку на экземпляр можно и не создавать

# Обращение к статическим методам

Cl1.mt2()        # имя экземпляра первым параметром не передается
em1.mt2()        # имя экземпляра первым параметром не передается


# Обращение к методам класса

Cl1.mt3()        # автоматически передается имя класса первым параметром
em1.mt3()        # автоматически передается имя класса первым параметром




#Модуль: test2.py

Подсчет количества экземпляров класса:


class Cl1:

    ac1 = 0          # Атрибут класса

    def __init__(self):

        self.ae1 = 555 # Атрибут экземпляра
        print("-init-")
        Cl1.ac1 +=1

    def __del__(self):
        print("-del-")
        Cl1.ac1 -=1


    def mt1(self):     # Метод экземпляра
        print(Cl1.ac1)


ex1 = Cl1()
ex1.mt1()

ex2 = Cl1()
ex3 = Cl1()

ex1.mt1()

del ex2
del ex3

ex1.mt1()