Классы в python
Содержание:
- Inheritance
- Основные операторы
- Constructors in Python
- Логистическая регрессия
- Как оценить производительность модели
- Квадратичный Дискриминантный Анализ (QDA)
- проект
- A complicated example
- Порядок поиска атрибутов
- Наследование и MRO
- Что не так с
- Строки
- Операции со строками
- Методы работы сос строками
- Создание собственных метаклассов
- Настройка отдельных полей
- Динамическое создание классов
- Deleting Attributes and Objects
- Определение конструктора для класса
- __dict__
- Как работает доступ к атрибутам
- Создание логируемого декоратора
- Способ 1 – Использование функции DIR () для перечисления методов в классе
- Зачем использовать классы метаклассов вместо функций?
Inheritance
Inheritance is a key concept in object oriented programming. Classes can inherit from other classes. This basically means that you can create a class based on another class.
So we could create a class and base it on the class. We can then add attributes to that only customers will need, such as how much they’ve spent:
# Create the ‘Person’ class
class Person:
def __init__(self, id, firstname, lastname):
self.id = id
self.firstname = firstname
self.lastname = lastname
# Create the ‘Customer’ class (inherited from the ‘Person’ class)
class Customer(Person):
def __init__(self, id, firstname, lastname, totalspend):
Person.__init__(self, id, firstname, lastname)
self.totalspend = totalspend
def getDetails(self):
return «%s %s (customer %i) has spent %s in total.» % (self.firstname, self.lastname, self.id, self.totalspend)
# Instantiate the ‘Customer’ class
customer = Customer(
12,
«Peter»,
«Griffin»,
13000
)
# Print details
print(customer.getDetails())Result
Peter Griffin (customer 12) has spent 13000 in total.
Here we use a (slightly) stripped down version of our class (from the previous example), then we create a class based on it. We know it’s inherited from the class because we’ve put inside the parentheses (like this .
The class has a function called that returns a string of text about how much the customer has spent. This function uses the string formatting tokens and to insert the customer’s details into the right place. The details are provided after the symbol after the end of the string.
Основные операторы
Оператор
Краткое описание
+
Сложение (сумма x и y)
—
Вычитание (разность x и y)
*
Умножение (произведение x и y)
Деление
Внимание! Если x и y целые, то результат всегда будет целым числом! Для получения вещественного результата хотя бы одно из чисел должно быть вещественным. Пример: 40/5 → 8, а вот 40/5.0 → 8.0
=
Присвоение
+=
y+=x; эквивалентно y = y + x;
-=
y-=x; эквивалентно y = y — x;
*=
y*=x; эквивалентно y = y * x;
/=
y/=x; эквивалентно y = y / x;
%=
y%=x; эквивалентно y = y % x;
==
Равно
!=
не равно
>
Больше
=
Часть после запятой отбрасывается
4 // 3 в результате будет 125 // 6 в результате будет 4
**
Возведение в степень
5 ** 2 в результате будет 25
and
логическое И
or
логическое ИЛИ
not
логическое отрицание НЕ
Constructors in Python
Class functions that begin with double underscore are called special functions as they have special meaning.
Of one particular interest is the function. This special function gets called whenever a new object of that class is instantiated.
This type of function is also called constructors in Object Oriented Programming (OOP). We normally use it to initialize all the variables.
Output
2+3j (5, 0, 10) Traceback (most recent call last): File "<string>", line 27, in <module> print(num1.attr) AttributeError: 'ComplexNumber' object has no attribute 'attr'
In the above example, we defined a new class to represent complex numbers. It has two functions, to initialize the variables (defaults to zero) and to display the number properly.
An interesting thing to note in the above step is that attributes of an object can be created on the fly. We created a new attribute attr for object num2 and read it as well. But this does not create that attribute for object num1.
Логистическая регрессия
Сначала мы будем использовать логистическую регрессию. На следующих этапах мы будем использовать область под кривой ROC и матрицу путаницы в качестве метрик ошибок.
Давайте сначала импортируем все, что нам нужно:
Затем мы делаем примерЛогистическая регрессияОбъект и пример модели для учебного набора:
Затем мы предсказываем вероятность того, что гриб ядовит. Помните, мы относимся к грибам как к ядовитым или неядовитым.
Также вам нужно напомнить, что логистическая регрессия возвращает вероятность. А пока давайте установим порог на 0,5. Таким образом, если вероятность больше 0,5, гриб будет классифицирован как ядовитый. Конечно, если вероятность меньше порога, гриб классифицируется как съедобный.
Это именно то, что происходит в ячейке кода ниже:
Обратите внимание, что мы рассчитали вероятности на тестовом наборе. Теперь давайте посмотримМатрица путаницы.Это покажет нам истинно положительные, истинно отрицательные, ложноположительные и ложно отрицательные показатели
Теперь давайте посмотримМатрица путаницы.Это покажет нам истинно положительные, истинно отрицательные, ложноположительные и ложно отрицательные показатели.
Пример путаницы
Мы выводим нашу матрицу путаницы следующим образом:
И вы должны получить:
Удивительно! Наш классификатор идеален! Из приведенной выше матрицы путаницы вы видите, что наши ложноположительные и ложноотрицательные показатели равны 0, что означает, что все грибы были правильно классифицированы как ядовитые или съедобные!
Давайте напечатаем область под кривой ROC. Как известно, для идеального классификатора он должен быть равен 1.
Действительно, кодовый блок выше выводит 1! Мы можем сделать нашу собственную функцию для визуализации кривой ROC:
И вы должны увидеть:
Кривая ROC
Поздравляем! Вы создали идеальный классификатор с базовой моделью логистической регрессии.
Тем не менее, чтобы получить больше опыта, давайте создадим классификатор, используя LDA и QDA, и посмотрим, получим ли мы аналогичные результаты.
Как оценить производительность модели
При классификации иногда нет необходимости использовать точность для оценки производительности модели.
Подумайте об анализе сильно несбалансированного набора данных. Например, вы пытаетесь определить, является ли транзакция мошеннической или нет, но только 0,5% вашего набора данных содержит мошенническую транзакцию. Тогда вы можете предсказать, что ни одна из транзакций не будет мошеннической и будет иметь показатель точности 99,5%! Конечно, это очень наивный подход, который не помогает обнаруживать мошеннические транзакции.
Так что мы используем?
Обычно мы используемчувствительностьа такжеспецифичность,
чувствительностьистинный положительный показатель: пропорции фактических положительных значений правильно определены.
специфичностьявляется истинным отрицательным показателем: доля фактических отрицательных значений правильно определена.
Давайте дадим некоторый контекст, чтобы лучше понять. Используя проблему обнаружения мошенничества,чувствительностьдоля мошеннических операций, определенных как мошенническиеспецифичностьэто доля не мошеннических операций, определенных как не мошеннические.
Поэтому в идеальной ситуации нам нужна высокая чувствительность и специфичность, хотя это может измениться в зависимости от контекста. Например, банк может пожелать установить более высокую чувствительность, а не специфичность, чтобы убедиться, что он идентифицирует мошеннические транзакции.
Кривая ROC(рабочая характеристика приемника) подходит для отображения двух типов метрик ошибок, описанных выше. Общая производительность классификатора определяется областью под кривой ROC (ППК). В идеале он должен охватывать верхний левый угол графика и иметь область, близкую к 1.
Пример кривой ROC. Прямая линия является базовой моделью
Квадратичный Дискриминантный Анализ (QDA)
Здесь мы придерживаемся тех же предположений, что и для LDA, но теперь каждое наблюдениеКТИкласс имеет свою собственную ковариационную матрицу
Для QDA дискриминант выражается как:
Дискриминантное уравнение для QDA
Без каких-либо сюрпризов вы заметите, что уравнение теперь квадратичное.
Но почему стоит выбирать QDA вместо LDA?
QDA — лучший вариант для больших наборов данных, так как он имеет тенденцию к меньшему смещению и большей дисперсии.
С другой стороны, LDA больше подходит для небольших наборов данных и имеет более высокое смещение и более низкую дисперсию.
проект
Большой! Теперь, когда мы глубоко понимаем, как работают логистическая регрессия, LDA и QDA, давайте применим каждый алгоритм для решения проблемы классификации.
A complicated example
This code exists in a closed source project:
class Application: def __init__(self, name, requirements, constraints=None, path='', executable_links=None, executables_dir=()): self.name = name self.requirements = requirements self.constraints = {} if constraints is None else constraints self.path = path self.executable_links = [] if executable_links is None else executable_links self.executables_dir = executables_dir self.additional_items = [] def __repr__(self): return f'Application({self.name!r},{self.requirements!r},{self.constraints!r},{self.path!r},{self.executable_links!r},{self.executables_dir!r},{self.additional_items!r})'
This can be replaced by:
@dataclass class Application: name: str requirements: List constraints: Dict = field(default_factory=dict) path: str = '' executable_links: List = field(default_factory=list) executable_dir: Tuple = () additional_items: List = field(init=False, default_factory=list)
The Data Class version is more declarative, has less code, supports
typing, and includes the other generated functions.
Порядок поиска атрибутов
Из одного из предыдущих примеров мы узнали, что при доступе к атрибуту инстанса, сначала происходит его поиск в инстанса, а потом — в класса. Но это с переменными. Теперь давайте разбираться, что происходит с методами. Начнём с примера:
Ага, метод лежит в класса. Получается, что когда мы говорим , происходит ? Давайте проверим:
Получается, что в лежит простая функция, а когда мы делаем — это уже атрибут. Что превращает одно в другое?
Как раз это и делается дескриптором: каждый метод оборачивается в него. Таким образом, — дескриптор, а – это уже результат его работы. Проверим нашу догадку:
Получается, что под капотом превращается в .
А что будет, если мы будем искать метод класса? У него ведь нет инстанса, есть только класс. За это отвечает декоратор и это — дескриптор, который при доступе прокидывает в первый аргумент функции класс, а не объект. Похожим образом действуют и .
— это тоже дескриптор. При своём вызове он отправляется в типа объекта и вызывает дескриптор нужного метода у подходящего класса. Если нужны детали реализации, ихможно посмотреть .
Превращение в происходит внутри метода . Если его неправильно перегрузить — всё может сломаться наихудшим образом. Трогать — почти всегда плохая идея.
Наследование и MRO
До этого у нас был только один класс. А что, если их несколько и какие-то методы перегружены? Кто, когда и как догадается, метод какого именно класса нужно вызывать?
Структура наследования может быть сложной и запутанной. Её линеаризацией занимается алгоритм C3 внутри Python. В результате работы у классов появляется — тупл из самого классаи всех его родителей в том порядке, в котором нужно искать в них методы.
остаётся только воспользоваться этим туплом и отыскать первый класс, в котором найдётся нужный метод. Этот код реализован на C, но на Python он бы выглядел примерно так:
Что не так с
Напоследок давайте поговорим о , с ним тоже всё не так просто.
Правильное название для — финализатор, а не деструктор.Деструктор уничтожает объект, а этим занимается не , а сборщик мусора. вызывается перед этим. Иногда.
вызывает непосредственно сборщик мусора перед тем, как удалить объект. Из этого следует, что этот метод может быть вызван в любой момент выполнения программы.
не вызывается при наличии циклических зависимостей. Поиском и удалением таких объектов занимается циклический сборщик мусора, он не вызывает . А вот начиная с Python 3.4 вызывается даже для циклических зависимостей, подробнее можно почитать в PEP-442. Можно избегать циклических зависимостей с помощью weakref, но чем больше проект — тем сложнее это делать.
Если внутри происходит получение эксклюзивного доступа к ресурсам, можно получить дедлок, если был вызван внутри кода, которому тоже эксклюзивно требуется тот же ресурс. Этим можно управлять, отключив автоматическое срабатывание сборщика мусора и вызывая его вручную.
Если внутри произошло исключение, то оно не будет прокинуто наружу. Вместо этого трейсбек отправится в , но само исключение будет проигнорировано. Если вы хотите, например, залогировать такое исключение в Sentry, это придётся делать вручную внутри метода с помощью .
Проблемы выше позволяют сформировать правило: не используйте . Если вам нужен код, который в любом случае будет вызван, используйте . Другой вариант — явно его вызвать с помощью . Тогда поведение финализатора будет предсказуемым, а код — более поддерживаемым.
Строки
Строка – это последовательность символов. Чаще всего строки – это просто некоторые наборы слов. Слова могут быть как на английском языке, так и почти на любом языке мира.
Операции со строками
string извлекает символ в позиции i
string извлекает последний символ
string извлекает символы в диапазоне от i до j
Методы работы сос строками
string.upper() преобразует строку в верхний регистр
String.lower() преобразует в строку в нижний регистр
string.count(x) подсчитывает, сколько раз появляется x
string.find(x) позиция первой строки вхождения x
string.replace(x, y) заменяет x на y
string.strip(x) удаляет как начальные, так и конечные символы x
string.join (List) объединяет список строк
Создание собственных метаклассов
Чтобы создать наш собственный метакласс, нам нужно унаследовать существующий метакласс `type` и переопределить некоторые специальные методы:
__new __() -> Это вызывается перед __init__() . Он отвечает за создание объекта и возвращает его.
__init __() -> Это для инициализации вновь созданного объекта, который передается как параметр (параметр self )
В следующем фрагменте показано, как можно создать метакласс:
class MyMetaclass(type): def __new__(cls, name, bases, dict): print('Creating a new object of', name) # Invoke __new__() method of the metaclass type return super(MyMetaclass, cls).__new__(cls, name, bases, dict) def __init__(cls, name, bases, dict): print('Initialising class', name) super(MyMetaclass, cls).__init__(name, bases, dict)
Теперь, когда мы создали наш собственный метакласс, нам нужно убедиться, что мы создаем другие классы, которые используют наш метакласс.
Для этого мы передаем параметр в определение нового класса, который сообщает классу использовать наш собственный метакласс в качестве собственного метакласса, а не .
class Student(metaclass=MyMetaclass): def __init__(self, name): self.name = name def get_name(self): return self.name
Здесь использует как свой метакласс. Следовательно, при создании экземпляра Student будут вызываться наши собственные методы метакласса вместо метакласса .
stud = Student('Amit') print(stud.get_name()) print('Type of Student object:', type(stud)) print('Type of Student Class:', type(Student))
Вывод
Creating a new object of Student Initialising class Student Amit Type of Student object: <class '__main__.Student'> Type of Student Class: <class '__main__.MyMetaclass'>
В старых версиях Python 2.7 или ниже используется ключевое слово для указания используемого метакласса. Python3 изменил это поведение, чтобы передать в качестве параметра.
Настройка отдельных полей
В большинстве стандартных ситуаций это не потребуется, однако есть возможность настроить поведение класса данных вплоть до отдельных полей с использованием функции field.
Изменяемые значения по умолчанию
Типичная ситуация, о которой говорилось выше — использование списков или других изменяемых значений по умолчанию. Мы можете захотеть класс «книжная полка», содержащий список книг. Если вы запустите следующий код:
интерпретатор сообщит об ошибке:
Однако для других изменяемых значений это предупреждение не сработает и приведет к некорректному поведению программы.
Чтобы избежать проблем, предлагается использовать параметр функции . В качестве его значения может быть любой вызываемый объект или функция без параметров.
Корректная версия класса выглядит так:
Другие параметры
Кроме указанного функция field имеет следующие параметры:
- : значение по умолчанию. Этот параметр необходим, так как вызов заменяет задание значения поля по умолчанию
- : включает (задан по умолчанию) использование поля в методе
- : включает (задан по умолчанию) использование поля в методе
- включает (задан по умолчанию) использование поля в методах сравнения (, и других)
-
: может быть булевое значение или . Если он равен , поле используется при вычислении хэша. Если указано (по умолчанию) — используется значение параметра .
Одной из причин указать при заданном может быть сложность вычисления хэша поля при том, что оно необходимо для сравнения. - : произвольный словарь или . Значение оборачивается в , чтобы оно стало неизменяемым. Этот параметр не используется самими классами данных и предназначено для работы сторонних расширений.
Динамическое создание классов
Поскольку классы являются объектами, вы можете создавать их на лету, как и любой объект.
Во-первых, вы можете создать класс в функции, используя ключевое слово class:
>>> def choose_class(name): ... if name == 'foo': ... class Foo(object): ... pass ... return Foo # возвращает class, а не его экземпляр ... else: ... class Bar(object): ... pass ... return Bar ... >>> MyClass = choose_class('foo') >>> print(MyClass) # функция вернула класс, а не его экземпляр <class '__main__.Foo'> >>> print(MyClass()) # you can create an object from this class <__main__.Foo object at 0x89c6d4c>
Но это не так динамично, так как вам все равно придется писать весь класс самостоятельно. Поскольку классы являются объектами, они должны быть чем-то порождены. Когда вы используете ключевое слово class, Python создает этот объект автоматически. Но, как и в большинстве случаев в Python, он позволяет делать это и вручную. Помните функции type? Старая добрая функция, которая позволяет узнать, к какому типу относится объект:
>>> print(type(1)) <type 'int'> >>> print(type("1")) <type 'str'> >>> print(type(ObjectCreator)) <type 'type'> >>> print(type(ObjectCreator())) <class '__main__.ObjectCreator'>
Что ж, у type есть и совершенно другие способности, он также может создавать классы на лету. type может принимать описание класса как параметры и возвращать класс. (Я знаю, это звучит глупо, что одна и та же функция может иметь два совершенно разных использования в зависимости от параметров, которые вы ей передаете. Это проблема из-за обратной совместимости в Python)
Итак работает следующим образом:
type(name, bases, attrs)
Где:
- : имя класса
- : кортеж родительского класса (для экземпляра, может быть пустым)
- : словарь, содержащий имена и значения атрибутов
Таким образом
>>> class MyShinyClass(object): ... pass
можно создать вручную следующим образом:
>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object >>> print(MyShinyClass) <class '__main__.MyShinyClass'> >>> print(MyShinyClass()) # создаем экземпляр класса <__main__.MyShinyClass object at 0x8997cec>
Вы заметите, что мы используем «MyShinyClass» как имя класса и как переменную для хранения ссылки на класс. Они могут быть разными, но нет причин усложнять ситуацию. type принимает словарь для определения атрибутов класса.
Так:
>>> class Foo(object): ... bar = True
Может быть переведен на:
>>> Foo = type('Foo', (), {'bar':True})
И используется как обычный класс:
>>> print(Foo) <class '__main__.Foo'> >>> print(Foo.bar) True >>> f = Foo() >>> print(f) <__main__.Foo object at 0x8a9b84c> >>> print(f.bar) True
И, конечно, вы можете унаследоваться от него. То есть такое:
>>> class FooChild(Foo): ... pass
можно переделать в такое:
>>> FooChild = type('FooChild', (Foo,), {}) >>> print(FooChild) <class '__main__.FooChild'> >>> print(FooChild.bar) # bar унаследован от Foo True
В конце концов, вы захотите добавить методы в свой класс. Просто определите функцию с соответствующей подписью и назначьте ее как атрибут.
>>> def echo_bar(self): ... print(self.bar) ... >>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) >>> hasattr(Foo, 'echo_bar') False >>> hasattr(FooChild, 'echo_bar') True >>> my_foo = FooChild() >>> my_foo.echo_bar() True
И вы можете добавить еще больше методов после динамического создания класса, точно так же, как можно добавить методы к обычно создаваемому объекту класса.
>>> def echo_bar_more(self): ... print('yet another method') ... >>> FooChild.echo_bar_more = echo_bar_more >>> hasattr(FooChild, 'echo_bar_more') True
Вы видите, к чему мы идем: в Python классы — это объекты, и вы можете создавать классы на лету, динамически. Это то, что Python делает, когда вы используете ключевое слово class, и делает это с помощью метакласса.
Deleting Attributes and Objects
Any attribute of an object can be deleted anytime, using the statement. Try the following on the Python shell to see the output.
We can even delete the object itself, using the del statement.
Actually, it is more complicated than that. When we do , a new instance object is created in memory and the name c1 binds with it.
On the command , this binding is removed and the name c1 is deleted from the corresponding namespace. The object however continues to exist in memory and if no other name is bound to it, it is later automatically destroyed.
This automatic destruction of unreferenced objects in Python is also called garbage collection.
Deleting objects in Python removes the name binding
Определение конструктора для класса
Если вы заметили реализацию класса Employee, невозможно установить значение employee_id. Мы можем определить отдельный метод для установки значения employee_id. Но это обязательное свойство объекта Employee. Лучшее место для установки этих свойств — через конструктор.
Давайте продолжим и создадим конструктор для класса Employee. Мы ожидаем, что вызывающая программа передаст значение employee_id в качестве аргумента.
class Employee: def __init__(self, i): self.employee_id = i def work(self): print(f'{self.employee_id} is working') emp = Employee(100) emp.work()
Выход:
Примечание: предыдущий код для создания объекта Employee теперь не будет работать, потому что конструктор Employee ожидает аргумент. Если мы вызовем , он вызовет ошибку TypeError: в init() отсутствует 1 обязательный позиционный аргумент: ‘id’.
Можем ли мы иметь несколько конструкторов?
В отличие от других популярных объектно-ориентированных языков программирования, Python не поддерживает перегрузку методов и конструкторов.
Однако, если мы определим несколько конструкторов в классе, это не вызовет никаких ошибок. Последний конструктор перезапишет ранее определенное определение конструктора. Давайте посмотрим на это на примере.
class Employee: def __init__(self, id): self.employee_id = id # this will overwrite earlier defined constructor def __init__(self, id, n): self.employee_id = id self.emp_name = n def work(self): print(f'{self.emp_name} is working') emp = Employee(100, 'Pankaj') emp.work() emp = Employee(100) # will raise Error emp.work()
Вывод:
Pankaj is working Traceback (most recent call last): File "/Users/pankaj/Documents/PycharmProjects/AskPython/hello-world/class_examples.py", line 19, in <module> emp = Employee(100) TypeError: __init__() missing 1 required positional argument: 'n'
__dict__
Переменные инстанса хранятся внутри инстанса. По сути инстанс и представляет из себя набор этих данных — всё остальное живёт в типе.
Самый распространённый метод хранения данных инстанса — переменная . Это словарь, который живёт под капотом инстанса класса: ключи — строки с названиями полей, значения — значения соответсвующих переменных. Пример:
Обратите внимание на поведение : она появилась в только после того, как была определена у инстанса класса. До этого её в не было, хотя она была доступна через (и вернула бы единицу)
— это не представление данных инстанса в формате словаря, они именно так и хранятся. Это полноценный словарь:
Это значит, что под капотом у него могут происходить хеширование, коллизии, увеличение адресного пространства и реаллокация — как у обычного словаря. Также это позволяет нам делать то же, что с обычным словарём, например удалять элементы. Вернёмся к предыдущему примеру:
У самого типа тоже есть — ведь он инстанс :
У классов — это не словарь, а . Он делает несколько полезных вещей: во-первых, убеждается, что ключи словаря — строки, а во-вторых делает его доступным только для чтения. Действительно, добавить элемент в такой словарь не выйдет:
А ещё это значит, что можно в инстанса добавить ключ не-строку:
Зачем нужна такая возможность — непонятно, но у классов её нет.Это сделано для большей стабильности работы интерпретатора и возможности использовать оптимизации — для них открывается больше простора, если у класса доступен только для чтения. Больше можно почитать в баге Bypassing __dict__ readonlyness на python.org (там 2007 год, но в общем всё актуально).
Переменной нет в двух случаях: если класс реализован на Cили если у класса объявлены слоты. К первому случаю относятся почти все встроенные типы, а о втором поговорим подробнее.
Если у класса определить , в котором перечислить все возможные поля инстанса, то пропадает смысл хранить их в словаре. Поэтому в таком случае под капотом у инстансов класса будет не словарь, а список.
Посмотреть, что происходит при использовании слотов можно в . Если коротко, то над слотами проводятся проверки на их вменяемость, правильно считаются все ссылки для сборщика мусора и, наконец, создаётся список со значениями для инстанса.
Для демонстрации посмотрим, сколько памяти экономит использование слотов в простом случае. Чтобы посчитать полный размер объекта со всеми вложенными в него объектами (например, вместе с и его ключами и значениями), используем модуль pympler:
Как работает доступ к атрибутам
Доступ к атрибутам инстанса организован через механизм дескрипторов. Давайте немного поговорим только о дескрипторах,чтобы разобраться, что это такое.
Создание логируемого декоратора
Возможно, вам потребуется логировать того, что делает ваша функция. Большую часть времени логинг будет встроен внутри вашей функции. Однако, бывают случаи, когда вам нужно сделать это на уровне функции, что бы получить представление о потоке программы или, возможно, для следования тем или иным условиям бизнеса, таким как аудит. Посмотрим на небольшой декоратор, который мы можем использовать для записи названия любой функции и того, что она делает:
Python
# -*- coding: utf-8 -*-
import logging
def log(func):
«»»
Логируем какая функция вызывается.
«»»
def wrap_log(*args, **kwargs):
name = func.__name__
logger = logging.getLogger(name)
logger.setLevel(logging.INFO)
# Открываем файл логов для записи.
fh = logging.FileHandler(«%s.log» % name)
fmt = ‘%(asctime)s — %(name)s — %(levelname)s — %(message)s’
formatter = logging.Formatter(fmt)
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.info(«Вызов функции: %s» % name)
result = func(*args, **kwargs)
logger.info(«Результат: %s» % result)
return func
return wrap_log
@log
def double_function(a):
«»»
Умножаем полученный параметр.
«»»
return a*2
if __name__ == «__main__»:
value = double_function(2)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# -*- coding: utf-8 -*- importlogging deflog(func) «»» Логируем какая функция вызывается. defwrap_log(*args,**kwargs) name=func.__name__ logger=logging.getLogger(name) logger.setLevel(logging.INFO) # Открываем файл логов для записи. fh=logging.FileHandler(«%s.log»%name) fmt=’%(asctime)s — %(name)s — %(levelname)s — %(message)s’ formatter=logging.Formatter(fmt) fh.setFormatter(formatter) logger.addHandler(fh) logger.info(«Вызов функции: %s»%name) result=func(*args,**kwargs) logger.info(«Результат: %s»%result) returnfunc returnwrap_log @log defdouble_function(a) «»» Умножаем полученный параметр. returna*2 if__name__==»__main__» value=double_function(2) |
Этот небольшой скрипт содержит функцию log, которая принимает функцию как единственный аргумент. Мы создаем объект логгер, а название лог файла такое же, как и у функции. После этого, функция log будет записывать, как наша функция была вызвана и что она возвращает, если возвращает.
Способ 1 – Использование функции DIR () для перечисления методов в классе
Чтобы перечислить методы этого класса, один подход – использовать функцию DIR () в Python.
Функция вернет все функции и свойства класса.
Посмотрим, что произойдет, если мы попробуем это для Отказ
print(dir(MyClass))
Выход
Хорошо, мы видим, что у нас есть наш , , и Методы перечислены! Однако как насчет всех других методов?
Ну, эти методы (те, которые начинаются с двойного поднятия), называются Методы гуляния Отказ
Они обычно называются функцией обертки. Например, Функция вызывает метод.
Фильтрация расположенных методов от выхода
Обычно нам не понадобится префиксные методы двойной подчеркивания, поэтому мы можем отфильтровать их с помощью приведенного ниже фрагмента:
method_list = print(method_list)
Выход
Ух ты! Теперь мы только получаем арифметические методы, которые мы хотели!
Однако наше настоящее решение имеет проблему.
Помните, что Вызывает как методы, так и свойства класса?
Обращение с свойствами класса
Если бы у нас была собственность внутри класса, он тоже будет перечислять. Рассмотрим ниже пример.
class MyClass(object): # MyClass property property1 = def __init__(self, a): assert isinstance(a, float) or isinstance(a, int) self.state = a def add(self, a): assert isinstance(a, float) or isinstance(a, int) self.state = self.state + a return self.state def subtract(self, a): assert isinstance(a, float) or isinstance(a, int) self.state = self.state - a return self.state def multiply(self, a): assert isinstance(a, float) or isinstance(a, int) self.state = self.state * a return self.state def divide(self, a): assert isinstance(a, float) or isinstance(a, int) self.state = self.state / a return self.state @staticmethod def global_method(a, b): return a + b @classmethod def myclass_method(cls): return cls method_list = print(method_list)
Теперь, что вы думаете, что вывод будет?
Выход
Это дает нам Также, что не то, что мы хотим.
Нам нужно сделать еще один фильтр для дифференцировки между методом и свойством.
Но это действительно просто. Основное отличие состоит в том, что любой объект недвижимости Не Callable, в то время как методы можно назвать!
В Python мы можем использовать булевую функцию Чтобы проверить, можно ли назвать атрибут.
Давайте теперь включаем это в наш старый код.
method_list = print(method_list)
Давайте сломаемся, написав его без понимания списка:
method_list = [] # attribute is a string representing the attribute name for attribute in dir(MyClass): # Get the attribute value attribute_value = getattr(MyClass, attribute) # Check that it is callable if callable(attribute_value): # Filter all dunder (__ prefix) methods if attribute.startswith('__') == False: method_list.append(attribute) print(method_list)
Мы также изменили к Так что это удаляет вводящее в заблуждение намерения!
Давайте проверим это сейчас.
Выход
Действительно, мы действительно получим свой список методов без свойств!
Зачем использовать классы метаклассов вместо функций?
Поскольку __metaclass__ может принимать любые вызываемые объекты, зачем использовать класс, если он явно более сложен?
Для этого есть несколько причин:
- Ваши намерения в этом случае будут более ясно. Когда вы читаете UpperAttrMetaclass (type), вам проще понять, что будет дальше
- Вы можете использовать ООП. Метакласс может наследоваться от метакласса, переопределять родительские методы. Метаклассы могут даже использовать другие метаклассы.
- Подклассы класса будут экземплярами его метакласса, если вы указали класс метакласса, не с помощью функции метакласса.
- Вы можете лучше структурировать свой код. Вы никогда не используете метаклассы для чего-то столь же тривиального, как приведенный выше пример. Обычно они используются для чего-то сложного. Возможность создавать несколько методов и группировать их в один класс очень полезна для облегчения чтения кода.
- Вы можете подключиться к __new__, __init__ и __call__. Это позволит вам делать разные вещи. Даже если обычно вы можете делать все это в __new__, некоторым людям просто удобнее использовать __init__.
- Они называются метаклассами, черт возьми! Это должно что-то значить!