Имена и области видимости

Дерево атрибутов

  • атрибуты экземпляра - создаются self.атрибут = в методах;
  • атрибуты класса - создаются атрибут = внутри инструкции class;
  • ссылки на суперклассы - перечисление при наследовании.

Поиск по дереву атрибутов - при любом обращении к атрибуту (даже при self.атрибут)

Функции при наследовании

Так как поиск атрибутов идет снизу вверх, то в подклассе можно:

  • заместить атрибут суперкласса;
  • предоставить атрибут суперкласса;
  • расширить методы суперкласса за счет их вызова из метода подкласса.

Расширение:

>>> class Super:
...     def method(self):
...         print('in Super.method')
...
>>> class Sub(Super):
...     def method(self):                   # Переопределить метод
...         print('starting Sub.method')    # Дополнительное действие
...         Super.method(self)              # Выполнить действие по умолчанию
...         print(ending Sub.method')

Взаимодействие классов при наследовании

Файл примера specialize.py

  • Super - имеет методы method и delegate (он хочет метод action в наследнике).
  • Inheritor - ничего своего, все только наследует от Super.
  • Replacer - переопределяет Super.method своей собственной версией.
  • Extender - переопределяет Super.method так, что вызывает и его, для выполнения действий по умолчанию.
  • Provider - реализует метод action, который ожидается метдом Super.delegate
class Super:
    def method(self):
        print('in Super.method')    # Поведение по умолчанию
    def delegate(self):
        self.action()               # Ожидаемый метод

class Inheritor(Super):             # Наследует методы, как они есть
    pass

class Replacer(Super):              # Полностью замещает method
    def method(self):
        print('in Replacer.method')

class Extender(Super):              # Расширяет поведение метода method
    def method(self):
        print('starting Extender.method')
        Super.method(self)
        print('ending Extender.method')

class Provider(Super):              # Определяет необходимый метод
    def action(self):
        print('in Provider.action')

if __name__ == '__main__':
    for myclass in (Inheritor, Replacer, Extender):
        print('\n' + myclass.__name__ + '...')
        myclass().method()
    print('\nProvider...')
    x = Provider()
    x.delegate()

Напечатает:

Inheritor...
in Super.method

Replacer...
in Replacer.method

Extender...
starting Extender.method
in Super.method
ending Extender.method

Provider...
in Provider.action

Абстрактные суперклассы

Когда класс Provider вызывается метод delegate, начинаются две независимых процедуры поиска.

  • первый вариант:

    • при вызове x.delegate() интерпретатор ищет метод, начиная от класса Provider вверх по дереву наследования.
    • Экземпляр x передается в виде аргумента self.
  • внутри метода Super.delegate выражение self.action запускает новый независимый поиск в дереве наследования, начиная от экземпляра self и далее вверх по дереву. Но self ссылается на экземпляр класса Provider, метод action будет найден в подклассе Provider.

Абстрактный суперкласс - класс, который ожидает, что часть его функционала будет реализована его детьми.

Хороший тон: сделать "абстрактность" неработоспособной, возбуждая NotImplementedError или написав assert (проверяет логическое выражение, если истина, идет дальше, если ложь, то останавливает выполнение с сообщением об ошибке):

class Super:
    def delegate(self):
        self.action()
    def action(self):
        assert False, 'action must be defined!' # При вызове этой версии

>>> X = Super()
>>> X.delegate()
AssertionError: action must be defined!

или через возбуждение исключения

class Super:
    def delegate(self):
        self.action()
    def action(self):
        raise NotImplementedError('action must be defined!')

>>> X = Super()
>>> X.delegate()
NotImplementedError: action must be defined!

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

>>> class Sub(Super): pass
...
>>> X = Sub()
>>> X.delegate()
NotImplementedError: action must be defined!

до тех пор, пока не реализуем:

>>> class Sub(Super):
...     def action(self): print('Реализация тут')
...
>>> X = Sub()
>>> X.delegate()
Реализация тут

Абстрактные суперклассы в питоне 2.6 и 3.0

Абстрактный суперкласс можно определить с помощью специальных синтаксических конструкций. Они разные в питоне 2.6 и 3.0

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

Python 3.0 (подробнее разберем позже):

from abc import ABCMeta, abstractmethod
class Super(metaclass=ABCMeta):
    @abstractmethod
    def method(self, ...):
        pass

Python 2.6:

class Super:
    __metaclass__ = ABCMeta
    @abstractmethod
    def method(self, ...):
        pass

Чтобы запретить это для методов delegate и action, напишем эту конструкцию для питона 3.0:

>>> from abc import ABCMeta, abstractmethod
>>>
>>> class Super(metaclass=ABCMeta):
...     def delegate(self):
...         self.action()
...     @abstractmethod
...     def action(self):
...         pass

>>> X = Super()
TypeError: Can’t instantiate abstract class Super with abstract methods action
>>> class Sub(Super): pass
...
>>> X = Sub()
TypeError: Can’t instantiate abstract class Sub with abstract methods action
>>> class Sub(Super):
... def action(self): print('Реализация тут')
...
>>> X = Sub()
>>> X.delegate()
Реализация тут

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

Минус: увеличение кода.

О декораторах функций (@abstractmethod) и объявлении метаклассов расскажем потом.

Пространство имен

  • Неквалифицированные имена (например, Х) располагаются в областях видимости (namespace).
  • Квалифицированные имена атрибутов (например, object.X) принадлежат простанствам имен объектов.
  • Некоторые области видимости инициализируют пространства имен объектов (в модулях и классах)

Простые имена - глобальные, пока нет =

Правило нахождения имени LEGB.

  • Присваивание (X = value) - делает имена локальными, создает или изменяет имя X в текущей локальной области видимости, если имя не объявлено глобальным

  • Ссылка (X) - пытается отыскать имя Х по правилу LEGB.

Имена атрибутов: пространства имен объектов

Квалифицированные имена атрибутов ссылаются на атрибуты конкретных объектов и к ним применяются правила, предназначенные для модулей и классов. Для объектов классов и экземпляров эти правила дополняются включением процедуры поиска в дереве наследования:

  • Присваивание (object.X = value) Создает или изменяет атрибут с именем X в пространстве имен объекта object, и ничего больше.

Восхождение по дереву наследования происходит только при попытке получить ссылку на атрибут, но не при выполнении операции присваивания.

  • Ссылка (object.X) Для объектов, созданных на основе классов, поиск атрибута X производится сначала в объекте object, затем во всех классах, расположенных выше в дереве наследования. В случае объектов, которые создаются не из классов, таких как модули, атрибут X извлекается непосредственно из объекта object.

Классификация имен происходит при =.

mynames.py:

X = 11 # Глобальное (в модуле) имя/атрибут (X, или manynames.X)

def f():
    print(X) # Обращение к глобальному имени X (11)

def g():
    X = 22 # Локальная (в функции) переменная (X, скрывает имя X в модуле)
    print(X)

class C:
    X = 33 # Атрибут класса (C.X)
    def m(self):
        X = 44 # Локальная переменная в методе (X)
        self.X = 55 # Атрибут экземпляра (instance.X)    

if __name__ == '__main__':
    print(X)        # 11: модуль (за пределами файла manynames.X)
    f()             # 11: глобальная
    g()             # 22: локальная
    print(X)        # 11: переменная модуля не изменилась
    obj = C()       # Создать экземпляр
    print(obj.X)    # 33: переменная класса, унаследованная экземпляром
    obj.m()         # Присоединить атрибут X к экземпляру
    print(obj.X)    # 55: экземпляр
    print(C.X)      # 33: класс (она же obj.X, если в экземпляре нет X)
    #print(C.m.X)   # ОШИБКА: видима только в методе
    #print(g.X)     # ОШИБКА: видима только в функции

Теперь в другом файле:

# otherfile.py
import manynames
X = 66
print(X)            # 66: здешняя глобальная переменная
print(manynames.X)  # 11: глобальная, ставшая атрибутом в результате импорта
manynames.f()       # 11: X в manynames, не здешняя глобальная!
manynames.g()       # 22: локальная в функции, в другом файле
print(manynames.C.X) # 33: атрибут класса в другом модуле
I = manynames.C()
print(I.X)          # 33: все еще атрибут класса
I.m()
print(I.X)          # 55: а теперь атрибут экземпляра!

Добавим global и nonlocal:

X = 11              # Глобальная в модуле
def g1():
    print(X)        # Ссылка на глобальную переменную в модуле
def g2():
    global X
    X = 22          # Изменит глобальную переменную в модуле
def h1():
    X = 33          # Локальная в функции
    def nested():
        print(X)    # Ссылка на локальную переменную в объемлющей функции
def h2():
    X = 33          # Локальная в функции
    def nested():
        nonlocal X  # Инструкция из Python 3.0
        X = 44      # Изменит локальную переменную в объемлющей функции

Старайтесь не писать одинаковые имена в разных областях (контекстах)

Словари пространств имен (исследуем какие есть имена)

Пространства имен реализованы как словари и доступны по встроенному атрибуту __dict__

Аналогично объекты классов и экземпляров: обращение к квалифицированному имени - это операция доступа к элементу словаря.

>>> class super:
...     def hello(self):
...         self.data1 = 'spam'
...
>>> class sub(super):
...     def hola(self):
...         self.data2 = 'eggs'
...
  • экземпляры по атрибуту __class__ получают ссылку на класс;
  • класс по атрибуту __bases__ получает кортеж со ссылками на суперклассы.
>>> X = sub()
>>> X.__dict__              # Словарь пространства имен экземпляра
{}
>>> X.__class__             # Класс экземпляра
<class '__main__.sub'>
>>> sub.__bases__           # Суперклассы данного класса
(<class '__main__.super'>,)
>>> super.__bases__         # В Python 2.6 возвращает пустой кортеж ()
(<class 'object'>,)

Исследуем экземпляр подкласса:

>>> Y = sub()
>>> X.hello()
>>> X.__dict__
{'data1': 'spam'}
>>> X.hola()
>>> X.__dict__
{'data1': 'spam', 'data2': 'eggs'}
>>> sub.__dict__.keys()
['__module__', '__doc__', 'hola']
>>> super.__dict__.keys()
['__dict__', '__module__', '__weakref__', 'hello', '__doc__'>]
>>> Y.__dict__              # до сих пор пустой!!!
{}

Имена с __ определяются автоматически.

Можно доступиться к атрибуту по квалифицированному имени или индексироваться по ключу:

>>> X.data1, X.__dict__['data1']
('spam', 'spam')
>>> X.data3 = 'toast'
>>> X.__dict__
{'data1': 'spam', 'data3': 'toast', 'data2': 'eggs'}
>>> X.__dict__['data3'] = 'ham'
>>> X.data3
'ham'

Заметим, что унаследованный атрибут X.hello недоступен через X.__dict__['hello']

Функция dir(object) напоминает вызов object.dict.keys(), но:

  • dir - сортирует свой список и включает в него системные атрибуты.
  • собирает унаследованные атрибуты (с версии 2.2)
  • вместе с атрибутами класса object
>>> X.__dict__, Y.__dict__
{ ({'data1': 'spam', 'data3': 'ham', 'data2': 'eggs'}, {})
>>> list(X.__dict__.keys())         # Необходимо в Python 3.0
['data1', 'data3', 'data2']
                                    # В Python 2.6
>>> dir(X)
['__doc__', '__module__', 'data1', 'data2', 'data3', 'hello', 'hola']
>>> dir(sub)
['__doc__', '__module__', 'hello', 'hola']
>>> dir(super)
['__doc__', '__module__', 'hello']
                                    # В Python 3.0:
>>> dir(X)
['__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__',
...часть строк опущена...
'data1', 'data2', 'data3', 'hello', 'hola']
>>> dir(sub)
['__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__',
...часть строк опущена...
'hello', 'hola']
>>> dir(super)
['__class__', '__delattr__', '__dict__', '__doc__', '__eq__', '__format__',
...часть строк опущена...
'hello'
]

Строки документации (docstring)

Написаны в тройных кавычках.

Автоматически сохраняются интерпретатором в их объектах.

Сделаем блоки документации:

"""I am: docstr.__doc__"""
def func(args):
    """I am: docstr.func.__doc__"""
    pass
class spam:
    """I am: spam.__doc__ or docstr.spam.__doc__"""
    def method(self, arg):
        """I am: spam.method.__doc__ or self.method.__doc__"""
        pass

Доступ к объектам docstr:

>>> import docstr
>>> docstr.__doc__
'I am: docstr.__doc__'
>>> docstr.func.__doc__
'I am: docstr.func.__doc__'
>>> docstr.spam.__doc__
'I am: spam.__doc__ or docstr.spam.__doc__'
>>> docstr.spam.method.__doc__
'I am: spam.method.__doc__ or self.method.__doc__'

Сделаем из строк документации help:

>>> help(docstr)
Help on module docstr:
NAME
    docstr - I am: docstr.__doc__
FILE
    c:\misc\docstr.py
CLASSES
    spam
    class spam
    | I am: spam.__doc__ or docstr.spam.__doc__
    |
    | Methods defined here:
    |
    | method(self, arg)
    |   I am: spam.method.__doc__ or self.method.__doc__
FUNCTIONS
    func(args)
        I am: docstr.func.__doc__

Классы и модули

  • Модули
    • Это пакеты данных и исполняемого кода.
    • Создаются как файлы с программным кодом на языке Python или как расширения на языке C.
    • Задействуются операцией import.
  • Классы
    • Реализуют новые объекты.
    • Создаются с помощью инструкции class.
    • Задействуются операцией вызова.
    • Всегда располагаются внутри модуля. Кроме того, классы поддерживают дополнительные возможности, недоступные в модулях, такие как перегрузка операторов, создание множества экземпляров и наследование. Несмотря на то что и классы, и модули являются пространствами имен, между ними есть различия.

Встроенные атрибуты класса

https://www.tutorialspoint.com/python/python_classes_objects.htm

  • __dict__ - Dictionary containing the class's namespace.
  • __doc__ - Class documentation string or none, if undefined.
  • __name__ - Class name.
  • __module__ - Module name in which the class is defined. This attribute is "__main__" in interactive mode.
  • __bases__ - A possibly empty tuple containing the base classes, in the order of their occurrence in the base class list.
#!/usr/bin/python

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1

   def displayCount(self):
     print "Total Employee %d" % Employee.empCount

   def displayEmployee(self):
      print "Name : ", self.name,  ", Salary: ", self.salary

print "Employee.__doc__:", Employee.__doc__
print "Employee.__name__:", Employee.__name__
print "Employee.__module__:", Employee.__module__
print "Employee.__bases__:", Employee.__bases__
print "Employee.__dict__:", Employee.__dict__

Получаем:

Employee.__doc__: Common base class for all employees
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: ()
Employee.__dict__: {'__module__': '__main__', 'displayCount':
<function displayCount at 0xb7c84994>, 'empCount': 2, 
'displayEmployee': <function displayEmployee at 0xb7c8441c>, 
'__doc__': 'Common base class for all employees', 
'__init__': <function __init__ at 0xb7c846bc>}

results matching ""

    No results matching ""