Абстрактные классы и интерфейсы в Питоне / Хабр
Абстрактные базовые классы и интерфейсы — близкие по назначению и смыслу сущности. Как первые, так и вторые представляют собой своеобразный способ документирования кода и помогают ограничить (decouple) взаимодействие отдельных абстракций в программе (классов).Питон — очень гибкий язык. Одна из граней этой гибкости — возможности, предоставляемые метапрограммированием. И хотя в ядре языка абстрактные классы и интерфейсы не представлены, первые были реализованы в стандартном модуле abc, вторые — в проекте Zope (модуль zope.interfaces).
Нет смысла одновременно использовать и то и другое, и поэтому каждый программист должен определить для себя, какой инструмент использовать при проектировании приложений.
2 Абстрактные базовые классы (abс)
Начиная с версии языка 2.6 в стандартную библиотеку включается модуль abc, добавляющий в язык абстрактные базовые классы (далее АБК).
АБК позволяют определить класс, указав при этом, какие методы или свойства обязательно переопределить в классах-наследниках:
from abc import ABCMeta, abstractmethod, abstractproperty class Movable(): __metaclass__=ABCMeta @abstractmethod def move(): """Переместить объект""" @abstractproperty def speed(): """Скорость объекта"""
Таким образом, если мы хотим использовать в коде объект, обладающий возможностью перемещения и определенной скоростью, то следует использовать класс Movable в качестве одного из базовых классов.
Наличие необходимых методов и атрибутов объекта теперь гарантируется наличием АБК среди предков класса:
class Car(Movable): def __init__: self.speed = 10 self.x = 0 def move(self): self.c += self.speed def speed(self): return self.speed assert issubclass(Car, Movable) assert ininstance(Car(), Movable)
Видно, что понятие АБК хорошо вписывается в иерархию наследования классов, использовать их легко, а реализация, если заглянуть в исходный код модуля abc, очень проста. Абстрактные классы используются в стандартных модулях collections и number, задавая необходимые для определения методы пользовательских
классов-наследников.
Подробности и соображения по поводу использования АБК можно найти в PEP 3119
(http://www.python.org/dev/peps/pep-3119/).
3 Интерфейсы (zope.interfaces)
Реализация проекта Zope в работе над Zope3 решила сделать акцент на компонентной архитектуре; фреймворк превратился в набор практически независимых компонент. Клей, соединяющий компоненты — интерфейсы и основывающиеся на них адаптеры.
Модуль zope.interfaces — результат этой работы.
В простейшем случае использвание интерфейсов напоминает примерение АБК:
import zope.interface class IVehicle(zope.interface.Interface): """Any moving thing""" speed = zope.interface.Attribute("""Movement speed""") def move(): """Make a single step""" class Car(object): zope.interface.implements(IVehicle) def __init__: self.speed = 1 self.location = 1 def move(self): self.location = self.speed*1 print "moved!" assert IVehicle.implementedBy(Car) assert IVehicle.providedBy(Car())
В интерфейсе декларативно показывается, какие атрибуты и методы должны быть у объекта. Причем класс реализует (implements) интерфейс, а объект класса — предоставляет (provides). Следует обратить внимание на разницу между этими понятиями!
«Реализация» чем-либо интерфейса означает, что только «производимая» сущность будет обладать необходимыми свойствами; а «предоставление» интерфейса говорит о конкретных возможностях оцениваемой сущности. Соответственно, в Питоне классы, кстати, могут как реализовывать, так и предоставлять интерфейс.
На самом деле декларация implement(IVehicle) — условность; просто обещание, что данный класс и его объекты ведут себя именно таким образом. Никаких реальных проверок проводиться
class IVehicle(zope.interface.Interface): """Any moving thing""" speed = zope.interface.Attribute("""Movement speed""") def move(): """Make a single step""" class Car(object): zope.interface.implements(IVehicle) assert IVehicle.implementedBy(Car) assert IVehicle.providedBy(Car())
Видно, что в простейших случаях интерфейсы только усложняют код, как, впрочем, и АБК
Компонентная архитектура Zope включает еще одно важное понятие — адаптеры. Вообще говоря, это простой шаблон проектирования, корректирующий один класс для использования где-то, где требуется иной комплект методов и атрибутов. Итак,
4 Адаптеры
Рассмотрим, сильно упростив, пример из Comprehensive Guide to Zope Component Architecture.
Предположим, что имеется пара классов, Guest и Desk. Определим интерфейсы к ним, плюс класс, реализующий интерфейс Guest:
import zope.interface from zope.interface import implements from zope.component import adapts, getGlobalSiteManager class IDesk(zope.interface.Interface): def register(): "Register a person" class IGuest(zope.interface.Interface): name = zope.interface.Attribute("""Person`s name""") class Guest(object): implements(IGuest) def __init__(self, name): self.name=name
Адаптер должен учесть анонимного гостя, зарегистрировав в списке имен:
class GuestToDeskAdapter(object): adapts(IGuest) implements(IDesk) def __init__(self, guest): self.guest=guest def register(self): guest_name_db.append(self.guest.name)
Существует реестр, который ведет учет адаптеров по интерфейсам. Благодаря ему можно получить адаптер, передав в вызов класса-интерфейса адаптируемый объект. Если адаптер не зарегистрирован, то вернется второй аргумент интерфейса:
guest = Guest("Ivan") adapter = IDesk(guest, alternate=None) print adapter >>>>None found gsm = getGlobalSiteManager() gsm.registerAdapter(GuestToDeskAdapter) adapter = IDesk(guest, alternate="None found") print adapter >>>>__main__.GuestToDeskAdapter object at 0xb7beb64c>
Такую инфраструктуру удобно использовать для разделения кода на компоненты и их связывания.
Один из ярчайших примеров использования такого подхода помимо самого Zope — сетевой фреймворк Twisted, где изрядная часть архитектуры опирается на интерфейсы из zope.interfaces.
5 Вывод
При ближайшем рассмотрении оказывается, что интерфейсы и абстрактные базовые классы — разные вещи.
Абстрактные классы в основном жестко задают обязательную интерфейсную часть. Проверка объекта на соответствие интерфейсу абстрактного класса проверяется при помощи встроенной функции isinstance; класса — issubclass. Абстрактный базовый класс должен включаться в иерархию в виде базового класса либо mixin`а.
Минусом можно считать семантику проверок issubclass, isinstance, которые пересекаются с обычными классами (их иерархией наследования). На АБК не выстраивается никаких допонительных абстракций.
Интерфейсы — сущность декларативная, они не ставят никаких рамок; просто утверждается, что класс реализует, а его объект предоставляет интерфейс. Семантически утверждения implementedBy, providedBy являются более корректными. На такой простой базе удобно выстраивать компонентную архитектуру при помощи адапетров и других производных сущностей, что и делают крупные фреймворки Zope и Twisted.
Надо понимать, что использование обоих инструментов имеет смысл только при построении и использовании сравнительно крупных ООП-систем — фреймворков и библиотек, в малых программах они могут только запутать и усложнить код код лишними абстракциями.
Метаклассы в Python / Хабр
Как сказал один из пользователей StackOverflow, «using SO is like doing lookups with a hashtable instead of a linked list». Мы снова обращаемся к этому замечательному ресурсу, на котором попадаются чрезвычайно подробные и понятные ответы на самые различные вопросы.В этот раз мы обсудим, что такое метаклассы, как, где и зачем их использовать, а также почему обычно этого делать не стоит.
Перед тем, как изучать метаклассы, надо хорошо разобраться с классами, а классы в Питоне — вещь весьма специфическая (основаны на идеях из языка Smalltalk).
В большинстве языков класс это просто кусок кода, описывающий, как создать объект. В целом это верно и для Питона:
>>> class ObjectCreator(object):
... pass
...
>>> my_object = ObjectCreator()
>>> print my_object
<__main__.ObjectCreator object at 0x8974f2c>
Но в Питоне класс это нечто большее — классы также являются объектами.
Как только используется ключевое слово class
, Питон исполняет команду и создаёт
>>> class ObjectCreator(object):
... pass
...
создаст в памяти объект с именем
ObjectCreator
.Этот объект (класс) сам может создавать объекты (экземпляры), поэтому он и является классом.
Тем не менее, это объект, а потому:
- его можно присвоить переменной,
- его можно скопировать,
- можно добавить к нему атрибут,
- его можно передать функции в качестве аргумента,
Так как классы являются объектами, их можно создавать на ходу, как и любой объект.
Например, можно создать класс в функции, используя ключевое слово
:
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # возвращает класс, а не экземпляр
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print MyClass # функция возвращает класс, а не экземпляр
<class '__main__.Foo'>
>>> print MyClass() # можно создать экземпляр этого класса
<__main__.Foo object at 0x89c6d4c>
Однако это не очень-то динамично, поскольку по-прежнему нужно самому писать весь класс целиком.
Когда используется ключевое слово class
, Питон создаёт этот объект автоматически. Но как и большинство вещей в Питоне, есть способ сделать это вручную.
Помните функцию type
? Старая-добрая функция, которая позволяет определить тип объекта:
>>> print type(1)
<type 'int'>
>>> print type("1")
<type 'str'>
>>> print type(ObjectCreator)
<type 'type'>
>>> print type(ObjectCreator())
<class '__main__.ObjectCreator'>
На самом деле, у функции
type
есть совершенно иное применение: она также может создавать классы на ходу. type
принимает на вход описание класса и созвращает класс.(Я знаю, это по-дурацки, что одна и та же функция может использоваться для двух совершенно разных вещей в зависимости от передаваемых аргументов. Так сделано для обратной совместимости)
type
работает следующим образом:
type(<имя класса>,
<кортеж родительских классов>, # для наследования, может быть пустым
<словарь, содержащий атрибуты и их значения>)
Например,
>>> class MyShinyClass(object):
... pass
может быть создан вручную следующим образом:
>>> MyShinyClass = type('MyShinyClass', (), {}) # возвращает объект-класс >>> 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 is inherited from Foo
True
В какой-то момент вам захочется добавить методов вашему классу. Для этого просто определите функцию с нужной сигнатурой и присвойте её в качестве атрибута:
>>> def echo_bar(self):
... print self.bar
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True
Уже понятно, к чему я клоню: в Питоне классы являются объектами и можно создавать классы на ходу.
Это именно то, что Питон делает, когда используется ключевое слово class
, и делает он это с помощью метаклассов.
Метакласс это «штука», которая создаёт классы.
Мы создаём класс для того, чтобы создавать объекты, так? А классы являются объектами. Метакласс это то, что создаёт эти самые объекты. Они являются классами классов, можно представить это себе следующим образом:
MyClass = MetaClass()
MyObject = MyClass()
Мы уже видели, что
type
позволяет делать что-то в таком духе: MyClass = type('MyClass', (), {})
Это потому что функция
type
на самом деле является метаклассом. type
это метакласс, который Питон внутренне использует для создания всех классов.Естественный вопрос: с чего это он его имя пишется в нижнем регистре, а не Type
?
Я полагаю, это просто для соответствия str
, классу для создания объектов-строк, и int
, классу для создания объектов-целых чисел. type
это просто класс для создания объектов-классов.
Это легко проверить с помощью атрибута __class__
:
В питоне всё (вообще всё!) является объектами. В том числе числа, строки, функции и классы — они все являются объектами и все были созданы из класса:
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>
А какой же
__class__
у каждого __class__
? >>> a.__class__.__class__
<type 'type'>
>>> age.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>
Итак, метакласс это просто штука, создающая объекты-классы.
Если хотите, можно называть его «фабрикой классов»
type
это встроенный метакласс, который использует Питон, но вы, конечно, можете создать свой.
При написании класса можно добавить атрибут
__metaclass__
:class Foo(object):
__metaclass__ = something...
[...]
В таком случае Питон будет использовать указанный метакласс при создании класса
Foo
.Осторожно, тут есть тонкость!
Хоть вы и пишете class Foo(object)
, объект-класс пока ещё не создаётся в памяти.
Питон будет искать __metaclass__
в определении класса. Если он его найдёт, то использует для создания класса Foo
. Если же нет, то будет использовать type
.
То есть когда вы пишете
class Foo(Bar):
pass
Питон делает следующее:
Есть ли у класса Foo
атрибут __metaclass__
?
Если да, создаёт в памяти объект-класс с именем Foo
, используя то, что указано в __metaclass__
.
Если Питон не находит __metaclass__
, он ищет __metaclass__
в родительском классе Bar
и попробует сделать то же самое.
Если же __metaclass__
не находится ни в одном из родителей, Питон будет искать __metaclass__
на уровне модуля.
И если он не может найти вообще ни одного __metaclass__
, он использует type
для создания объекта-класса.
Теперь важный вопрос: что можно положить в __metaclass__
?
Ответ: что-нибудь, что может создавать классы.
А что создаёт классы? type
или любой его подкласс, а также всё, что использует их.
Основная цель метаклассов — автоматически изменять класс в момент создания.
Обычно это делает для API, когда хочется создавать классы в соответсвии с текущим контекстом.
Представим глупый пример: вы решили, что у всех классов в вашем модуле имена атрибутов должны быть записать в верхнем регистре. Есть несколько способов это сделать, но один из них — задать __metaclass__
на уровне модуля.
В таком случае все классы этого модуля будут создаваться с использованием указанного меакласса, а нам остаётся только заставить метакласса переводить имена всех атрибутов в верхний регистр.
К счастью, __metaclass__
может быть любым вызываемым объектом, не обязательно формальным классом (я знаю, что-то со словом «класс» в названии не обязано быть классом, что за ерунда? Однако это полезно).
Так что мы начнём с простого примера, используя функцию.
# метаклассу автоматически придёт на вход те же аргументы,
# которые обычно используются в `type`
def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""
Возвращает объект-класс, имена атрибутов которого
переведены в верхний регистр
"""
# берём любой атрибут, не начинающийся с '__'
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
# переводим их в верхний регистр
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# создаём класс с помощью `type`
return type(future_class_name, future_class_parents, uppercase_attr)
__metaclass__ = upper_attr # это сработает для всех классов в модуле
class Foo(object):
# или можно определить __metaclass__ здесь, чтобы сработало только для этого класса
bar = 'bip'
print hasattr(Foo, 'bar')
# Out: False
print hasattr(Foo, 'BAR')
# Out: True
f = Foo()
print f.BAR
# Out: 'bip'
А теперь то же самое, только используя настояший класс:
# помним, что `type` это на само деле класс, как `str` и `int`,
# так что от него можно наследовать
class UpperAttrMetaclass(type):
# Метод __new__ вызывается перед __init__
# Этот метод создаёт обхект и возвращает его,
# в то время как __init__ просто инициализирует объект, переданный в качестве аргумента.
# Обычно вы не используете __new__, если только не хотите проконтролировать,
# как объект создаётся
# В данном случае созданный объект это класс, и мы хотим его настроить,
# поэтому мы перегружаем __new__.
# Можно также сделать что-нибудь в __init__, если хочется.
# В некоторых более продвинутых случаях также перегружается __call__,
# но этого мы сейчас не увидим.
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return type(future_class_name, future_class_parents, uppercase_attr)
Но это не совсем ООП. Мы напрямую вызываем
type
и не перегружаем вызов __new__
родителя. Давайте сделаем это:class UpperAttrMetaclass(type):
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):
attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
# используем метод type.__new__
# базовое ООП, никакой магии
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)
Вы, возможно, заметили дополнительный аргумент
upperattr_metaclass
. Ничего особого в нём нет: метод всегда получает первым аргументом текущий экземпляр. Точно так же, как вы используете self
в обычным методах.Конечно, имена, которые я тут использовал, такие длинные для ясности, но как и self
, есть соглашение об именовании всех этих аргументов. Так что реальный метакласс выгляит как-нибудь так:
class UpperAttrMetaclass(type):
def __new__(cls, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return type.__new__(cls, name, bases, uppercase_attr)
Можно сделать даже лучше, использовав
super
, который вызовет наследование (поскольку, конечно, можно создать метакласс, унаследованный от метакласса, унаследованного от type
):class UpperAttrMetaclass(type):
def __new__(cls, name, bases, dct):
attrs = ((name, value) for name, value in dct.items() if not name.startswith('__'))
uppercase_attr = dict((name.upper(), value) for name, value in attrs)
return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
Вот и всё. О метаклассах больше ничего и не сказать.
Причина сложности кода, использующего метаклассы, не в самих метаклассах. Она в том, что обычно метаклассы используются для всяких изощрённых вещей, основанных на интроспекции, манипуляцией наследованием, переменными вроде __dict__
и тому подобном.
Действительно, метаклассы особенно полезны для всякой «чёрной магии», а, следовательно, сложных штук. Но сами по себе они просты:
- перехватить создание класса
- изменить класс
- вернуть модифицированный
Поскольку
__metaclass__
принимает любой вызываемый объект, с чего бы вдруг использовать класс, если это очевидно сложнее?Тому есть несколько причин:
- Назначение яснее. Когда вы видите
UpperAttrMetaclass(type)
, вы сразу знаете, что дальше будет. - Можно использовать ООП. Метаклассы могту наследоваться от метаклассов, перегружая родитальские методы.
- Лучше структурированный код. Вы не будете использовать метаклассы для таких простых вещей, как в примере выше. Обычно это что-то сложное. Возможность создать несколько методов и сгруппировать их в одном классе очень полезна, чтобы сделать код более удобным для чтения.
- Можно использовать
__new__
,__init__
и__call__
. Конечно, обычно можно всё сделать в__new__
, но некоторым комфортнее использовать__init__
- Они называются метаклассами, чёрт возьми! Это должно что-то значить!
Наконец, главный вопрос. С чего кому-то использовать какую-то непонятную (и способствующую ошибкам) фичу?
Ну, обычно и не надо использовать:
Метаклассы это глубокая магия, о которой 99% пользователей даже не нужно задумываться. Если вы думаете, нужно ли вам их использовать — вам не нужно (люди, которым они реально нужны, точно знают, зачем они им, и не нуждаются в объяснениях, почему).
~ Гуру Питона Тим Питерс
Основное применение метаклассов это создание API. Типичный пример — Django ORM.
Она позволяет написать что-то в таком духе:
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()
Однако если вы выполните следующий код:
guy = Person(name='bob', age='35')
print guy.age
вы получите не
IntegerField
, а int
, причём значение может быть получено прямо из базы данных.Это возможно, потому что models.Model
определяет __metaclass__
, который сотворит некую магию и превратит класс Person
, который мы только что определили простым выражением в сложную привязку к базе данных.
Django делает что-то сложное выглядящим простым, выставляя наружу простое API и используя метаклассы, воссоздающие код из API и незаметно делающие всю работу.
ВО-первых, вы узнали, что классы это объекты, которые могут создавать экземпляры.
На самом деле, классы это тоже экземпляры. Экземпляры метаклассов.
>>> class Foo(object): pass
>>> id(Foo)
142630324
Всё что угодно является объектом в Питоне: экземпляром класса или экземпляром метакласса.
Кроме type
.
type
является собственным метаклассом. Это нельзя воспроизвести на чистом Питоне и делается небольшим читерством на уровне реализации.
Во-вторых, метаклассы сложны. Вам не нужно использовать их для простого изменения классов. Это можно делать двумя разными способами:
- руками
- декораторы классов
В 99% случаев, когда вам нужно изменить класс, лучше использовать эти два.
Но в 99% случаев вам вообще не нужно изменять классы 🙂
Сегодня мы поговорим об объектно-ориентированном программировании и о его применении в python.
Объектно-ориентированное программирование (ООП) — парадигма программирования, в которой основными концепциями являются понятия объектов и классов.
Класс — тип, описывающий устройство объектов. Объект — это экземпляр класса. Класс можно сравнить с чертежом, по которому создаются объекты.
Python соответствует принципам объектно-ориентированного программирования. В python всё является объектами — и строки, и списки, и словари, и всё остальное.
Но возможности ООП в python этим не ограничены. Программист может написать свой тип данных (класс), определить в нём свои методы.
Это не является обязательным — мы можем пользоваться только встроенными объектами. Однако ООП полезно при долгосрочной разработке программы несколькими людьми, так как упрощает понимание кода.
Приступим теперь собственно к написанию своих классов на python. Попробуем определить собственный класс:
>>> # Пример простейшего класса, который ничего не делает ... class A: ... pass
Теперь мы можем создать несколько экземпляров этого класса:
>>> a = A() >>> b = A() >>> a.arg = 1 # у экземпляра a появился атрибут arg, равный 1 >>> b.arg = 2 # а у экземпляра b - атрибут arg, равный 2 >>> print(a.arg) 1 >>> print(b.arg) 2 >>> c = A() >>> print(c.arg) # а у этого экземпляра нет arg Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'A' object has no attribute 'arg'
Классу возможно задать собственные методы:
>>> class A: ... def g(self): # self - обязательный аргумент, содержащий в себе экземпляр ... # класса, передающийся при вызове метода, ... # поэтому этот аргумент должен присутствовать ... # во всех методах класса. ... return 'hello world' ... >>> a = A() >>> a.g() 'hello world'
И напоследок еще один пример:
>>> class B: ... arg = 'Python' # Все экземпляры этого класса будут иметь атрибут arg, ... # равный "Python" ... # Но впоследствии мы его можем изменить ... def g(self): ... return self.arg ... >>> b = B() >>> b.g() 'Python' >>> B.g(b) 'Python' >>> b.arg = 'spam' >>> b.g() 'spam'
В языке программирования Python классы создаются с помощью инструкции class, за которой следует произвольное имя класса, после которого ставится двоеточие, далее с новой строки и с отступом реализуется тело класса:
class ИмяКласса: код_тела_класса
Если класс является дочерним, то родительские классы перечисляются в круглых скобках после имени класса.
Объект создается путем вызова класса по его имени. При этом после имени класса обязательно ставятся скобки:
ИмяКласса()
То есть класс вызывается подобно функции. Однако при этом происходит не выполнение его тела, а создается объект. Поскольку в программном коде важно не потерять ссылку на только что созданный объект, то обычно его связывают с переменной. Поэтому создание объекта чаще всего выглядит так:
имя_переменной = ИмяКласса()
В последствии к объекту обращаются через связанную с ним переменную.
Пример «пустого» класса и двух созданных на его основе объектов:
>>> class A: ... pass ... >>> a = A() >>> b = A()
Класс как модуль
В языке программирования Python класс можно представить подобным модулю. Также как в модуле в нем могут быть свои переменные со значениями и функции. Также как в модуле у класса есть собственное пространство имен, доступ к которым возможен через имя класса:
>>> class B: ... n = 5 ... def adder(v): ... return v + B.n ... >>> B.n 5 >>> B.adder(4) 9
Однако в случае классов используется немного иная терминология. Пусть имена, определенные в классе, называются атрибутами этого класса. В примере имена n
и adder
– это атрибуты класса B
. Атрибуты-переменные часто называют полями или свойствами. Свойством является n
. Атрибуты-функции называются методами. Методом в классе B
является adder
. Количество свойств и методов в классе может быть любым.
Класс как создатель объектов
Приведенный выше класс позволяет создавать объекты, но мы не можем применить к объекту метод adder():
>>> l = B() >>> l.n 5 >>> l.adder(100) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: adder() takes 1 positional argument but 2 were given
В сообщении об ошибке говорится, что adder() принимает только один аргумент, а было передано два. Откуда взялся второй аргумент, и кто он такой, если в скобках было указано только одно число 100?
На самом деле классы – это далеко не модули. Они идут дальше модулей и обладают своими особенностями. Класс создает объекты, которые в определенном смысле являются его наследниками. Это значит, что если у объекта нет собственного поля n
, то интерпретатор ищет его уровнем выше, то есть в классе. Таким образом, если мы присваиваем объекту поле с таким же именем как в классе, то оно перекрывает, т. е. переопределяет, поле класса:
>>> l.n = 10 >>> l.n 10 >>> B.n 5
Здесь l.n
и B.n
– это разные переменные. Первая находится в пространстве имен объекта l
. Вторая – в пространстве класса B
. Если бы мы не добавили поле n
к объекту l
, то интерпретатор бы поднялся выше по дереву наследования и пришел бы в класс, где бы и нашел это поле.
Что касается методов, то они также наследуются объектами класса. В данном случае у объекта l
нет своего собственного метода adder, значит, он ищется в классе B
. Однако от класса B
может быть порождено множество объектов. Методы же чаще всего предназначаются для обработки объектов. Таким образом, когда вызывается метод, в него надо передать конкретный объект, который он будет обрабатывать.
Понятно, что передаваемый экземпляр, это объект, к которому применяется метод. Выражение l.adder(100) выполняется интерпретатором следующим образом:
Ищу атрибут adder() у объекта
l
. Не нахожу.Тогда иду искать в класс
B
, так как он создал объектl
.Здесь нахожу искомый метод. Передаю ему объект, к которому этот метод надо применить, и аргумент, указанный в скобках.
Другими словами, выражение l.adder(100)
преобразуется в выражение B.adder(l, 100)
.
Таким образом, интерпретатор попытался передать в метод adder() класса B
два параметра – объект l
и число 100. Но мы запрограммировали метод adder() так, что он принимает только один параметр. В Python, да и многих других языках, определения методов не предполагают принятие объекта как само собой подразумеваемое. Принимаемый объект надо указывать явно.
По соглашению в Python для ссылки на объект используется имя self. Вот так должен выглядеть метод adder(), если мы планируем вызывать его через объекты:
>>> class B: ... n = 5 ... def adder(self, v): ... return v + self.n ...
Переменная self связывается с объектом, к которому был применен данный метод, и через эту переменную мы получаем доступ к атрибутам объекта. Когда этот же метод применяется к другому объекту, то self свяжется уже с этим другим объектом, и через эту переменную будут извлекаться только его свойства.
Протестируем обновленный метод:
>>> l = B() >>> m = B() >>> l.n = 10 >>> l.adder(3) 13 >>> m.adder(4) 9
Здесь от класса B
создаются два объекта – l
и m
. Для объекта l
заводится собственное поле n
. Объект m
, за неимением собственного, наследует n
от класса B
. Можно в этом убедиться, проверив соответствие:
>>> m.n is B.n True >>> l.n is B.n False
В методе adder() выражение self.n
– это обращение к свойству n
, переданного объекта, и не важно, на каком уровне наследования оно будет найдено.
Если метод не принимает объект, к которому применяется, в качестве первого параметра, то такие методы в других языках программирования называются статическими. Они имеют особый синтаксис и могут вызываться как через класс, так и через объект этого класса. В Python все немного по-другому. Для имитации статических методов используется специальный декоратор, после чего метод можно вызывать не только через класс, но и через объект, не передавая сам объект.
Изменение полей объекта
В Python объекту можно не только переопределять поля и методы, унаследованные от класса, также можно добавить новые, которых нет в классе:
>>> l.test = "hi" >>> B.test Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: type object 'B' has no attribute 'test' >>> l.test 'hi'
Однако в программировании так делать не принято, потому что тогда объекты одного класса будут отличаться между собой по набору атрибутов. Это затруднит автоматизацию их обработки, внесет в программу хаос.
Поэтому принято присваивать полям, а также получать их значения, путем вызова методов:
>>> class User: ... def setName(self, n): ... self.name = n ... def getName(self): ... try: ... return self.name ... except: ... print("No name") ... >>> first = User() >>> second = User() >>> first.setName("Bob") >>> first.getName() 'Bob' >>> second.getName() No name
Подобные методы в простонародье называют сеттерами (set – установить) и геттерами (get – получить).
Практическая работа
Напишите программу по следующему описанию. Есть класс «Воин». От него создаются два экземпляра-юнита. Каждому устанавливается здоровье в 100 очков. В случайном порядке они бьют друг друга. Тот, кто бьет, здоровья не теряет. У того, кого бьют, оно уменьшается на 20 очков от одного удара. После каждого удара надо выводить сообщение, какой юнит атаковал, и сколько у противника осталось здоровья. Как только у кого-то заканчивается ресурс здоровья, программа завершается сообщением о том, кто одержал победу.
Курс с примерами решений практических работ и всеми уроками: android-приложение, pdf-версия.
Перестаньте писать классы / Хабр
Признак того, что объект не должен быть классом — если в нём всего 2 метода, и один из них — инициализация, __init__. Каждый раз видя это, подумайте: «наверное, мне нужна просто одна функция».Каждый раз когда из написанного класса вы создаёте всего один экземпляр, используете только раз и тут же выбрасываете, следует думать: «ой, надо бы это отрефакторить! Можно сделать проще, намного проще!»
Перевод доклада Джэка Дидриха, одного из ключевых разработчиков языка Питон. Доклад прозвучал 9 марта 2012 на конференции PyCon US.
Все из вас читали Дзэн Питона, наверное много раз. Вот несколько пунктов из него:
- Простое лучше сложного
- Плоское лучше вложенного
- Важна читаемость
- Если программу трудно объяснить, она плохая
- Если программу легко объяснить, возможно, она хорошá
Написал этот текст Тим Питерс. Он умнее и вас, и меня. Сколько вы знаете людей, в честь которых назвали алгоритм сортировки? Вот такой человек написал Дзэн Питона. И все пункты гласят: «Не делай сложно. Делай просто.» Именно об этом и пойдёт речь.
Итак, в первую очередь, не делайте сложно, там, где можно сделать проще. Классы очень сложны или могут быть очень сложны. Но мы всё равно делаем сложно, даже стараясь делать проще. Поэтому в этом докладе мы прочитаем немного кода и узнаем, как заметить, что идём неверным путём, и как выбраться обратно.
На своей работе я говорю коллегам: «Я ненавижу код, и хочу чтобы его было как можно меньше в нашем продукте.» Мы продаём функционал, не код. Покупатели у нас не из-за кода, а из-за широкого функционала. Каждый раз, когда код удаляется, это хорошо. Нас четверо, и в последний год мы перестали считать количество строк в продукте, но продолжаем вводить новый функционал.
Классы
Из этого доклада вам в первую очередь нужно запомнить вот этот код. Это крупнейшее злоупотребление классами, встречающееся в природе.
class Greeting(object):
def __init__(self, word='Hello'):
self.word = word
def greet(self, name):
return '%s, %s!' % (self.word, name)
>>> greeting = Greeting('Hola')
>>> greeting.greet('Jorge')
Hola, Jorge!
Это не класс, хотя он похож на класс. Имя — существительное, «приветствие». Он принимает аргументы и сохраняет их в __init__. Да, выглядит как класс. У него есть метод, читающий состояние объекта и делающий что-то ещё, как в классах. Внизу написано, как этим классом пользуются: создаём экземпляр Приветствия и затем используем это Приветствие чтобы сделать что-то ещё.
Но это не класс, или он не должен быть классом. Признак этого — в нём всего 2 метода, и один из них — инициализация, __init__. Каждый раз видя это, подумайте: «наверное, мне нужна просто одна функция».
Каждый раз когда из написанного класса вы создаёте всего один экземпляр, используете только раз и тут же выбрасываете, следует думать: «ой, надо бы это отрефакторить! Можно сделать проще, намного проще!»
def greet(name):
ob = Greeting('превед')
print ob.greet(name)
return
Эта функция состоит из 4 строк кода. А вот как можно сделать то же самое всего за 2 строки:
def greet(greeting, name):
return '%s, %s!' % (greeting, name)
import functools
greet = functools.partial(greet, 'превед')
greet('красавчик')
Если вы всё время вызываете функцию с тем же первым аргументом, стандартной библиотеке есть инструмент! functools.partial. Вот посмотрите в код выше: добавляете аргумент, и результат можно вызывать многократно.
Не знаю, у скольких из вас диплом в ИТ, у меня он есть. Я учил такие понятия как
— разделение полномочий
— уменьшение связанности кода
— инкапсуляция
— изоляция реализации
С тех пор как я закончил вуз, 15 лет я этих терминов не употреблял. Слыша эти слова, знайте, вас дурят. Эти термины сами по себе не требуются. Если их используют, люди имеют в виду совершенно разное, что только мешает разговору.
Пример: брюки превращаются…
Многие из вас пользуются в повседневной работе сторонними библиотеками. Каждый раз когда надо пользоваться чужим кодом, первое, что нужно сделать — прочитать его. Ведь неизвестно, что там, какого качества, есть ли у них тесты и так далее. Нужно проверить код прежде чем включать его. Иногда читать код бывает тяжко.
Сторонняя библиотека API, назовём её ShaurMail, включала 1 пакет, 22 модуля, 20 классов и 660 строк кода. Мне пришлось всё это прочитать прежде чем включить в продукт. Но это был их официальный API, поэтому мы пользовались им. Каждый раз когда приходили обновления API, приходилось просматривать диффы, потому что было неизвестно, что они поменяли. Вы посылали патчи — а в обновлении они появились?
660 строк кода, 20 классов — это многовато, если программе нужно только дать список адресов электронной почты, текст письма и узнать, какие письма не доставлены, и кто отписался.
Что такое злоупотребление классами? Часто люди думают, что им понадобится что-то в будущем.… Не понадобится. Напишите всё, когда потребуется. В библиотеке ШаурМаил есть модуль ШаурХэш, в котором 2 строки кода:
class ShaurHash(dict):
pass
Кто-то решил, что позже понадобится надстройка над словарём. Она не понадобилась, но везде в коде остались строки, как первая:
my_hash = ShaurMail.ShaurHash.ShaurHash(id='cat')
d = dict(id='cat')
d = {'id': 'cat'}
Вторая и третья строки кода — никому не нужно объяснять их. Но там везде повторялась эта мантра «ШаурМаил-ШаурХэш-ШаурХэш». Троекратное повторение слова «Шаур» — ещё один признак излишества. От повторений всем только вред. Вы раздражаете пользователя, заставляя его писать «Шаур» три раза. (Это не настоящее имя компании, а вымышленное.)
Потом они уволили этого парня и наняли того, кто знал, что делает. Вот вторая версия API:
class API:
def __init__(self, key):
self.header = dict(apikey=key)
def call(self, method, params):
request = urllib2.Request(
self.url + method[0] + '/' + method[1],
urllib.urlencode(params),
self.header
)
try:
response = json.loads(urllib2.urlopen(request).read())
return response
except urllib2.HTTPError as error:
return dict(Error=str(error))
В той было 660 строк, в этой — 15. Всё, что делает этот код — пользуется методами стандартной библиотеки. Он читается целиком, легко, за секунды, и можно сразу понять, что он делает. Кстати, в нём был ещё набор тестов из 20 строк. Вот как надо писать. Когда они обновляли API, я мог прочесть изменения буквально за пару секунд.
Но и здесь можно заметить проблему. В классе два метода, и один из них — __init__. Авторы этого не скрывали. Второй метод — call, «вызвать». Вот как этим API пользоваться:
ShaurMail.API(key='СЕКРЕТНЫЙ КЛЮЧ').call(('mailing', 'statistics'), {'id': 1})
Строка длинная, поэтому мы делаем алиас и вызываем его многократно:
ShaurMail.request = ShaurMail.API(key='СЕКРЕТНЫЙ КЛЮЧ').call
ShaurMail.request(('mailing', 'statistics'), {'id': 1})
Заметьте, мы пользуемся этим классом как функцией. Ею он и должен быть. Если видите подобное, знайте, класс тут не нужен. Поэтому я послал им третью версию API:
ShaurMail_API = url = 'https://api.shaurmail.com/%s/%s'
ShaurMail_API_KEY = 'СЕКРЕТНЫЙ КЛЮЧ'
def request(noun, verb, **params):
headers = {'apikey': ShaurMail_API_KEY}
request = urllib2.Request(ShaurMail_API % (noun, verb),
urllib.urlencode(params), headers)
return json.loads(urllib2.urlopen(request).read())
Он вообще не создаёт файлов в нашем проекте, потому что я вставил его в тот модуль, где он используется. Он делает всё, что делал 15-строковый API, и всё, что делал 660-строковый API.
Вот с чего мы начали и к чему пришли:
- 1 пакет + 20 модулей => 1 модуль
- 20 классов => 1 класс => 0 классов
- 130 методов => 2 метода => 1 функция
- 660 строк кода => 15 строк => 5 строк
Легче пользоваться, легче писать, никому не надо выяснять, что происходит.
Стандартная библиотека
Кто пришёл из языка Java, возможно, считает, что пространства имён нужны для таксономии. Это неверно. Они нужны чтобы предотвратить совпадения имён. Если у вас глубокие иерархии пространств, это никому ничего не даёт. ShaurMail.ShaurHash.ShaurHash — всего лишь лишние слова, которые людям надо помнить и писать.
В стандартной библиотеке Питона пространство имён очень неглубокое, потому что вы либо помните, как называется модуль, либо надо смотреть в документации. Ничего хорошего если надо выяснять цепочку, в каком пакете искать, в каком пакете в нём, в каком пакете дальше, и как называется модуль в нём. Нужно просто знать имя модуля.
К нашему стыду, вот пример из нашего же кода, и те же грехи видно и здесь:
services.crawler.crawlerexceptions.ArticleNotFoundException
Пакет, в котором модуль из 2 строк, класс исключения и «pass». Чтобы использовать это исключение, надо дважды написать «crawler», дважды слово «exception». Имя ArticleNotFoundException само себя повторяет. Так не надо. Если вы называете исключения, пусть это будет EmptyBeer, BeerError, BeerNotFound, но BeerNotFoundError — это уже много.
Можно просто пользоваться исключениями из стандартной библиотеки. Они понятны всем. Если только вам не нужно выловить какое-то специфическое состояние, LookupError вполне подойдёт. Если вы получили отлуп по почте, всё равно придётся его читать, поэтому неважно, как называется исключение.
Кроме того, исключения в коде обычно идут после raise и except, и всем сразу понятно, что это исключения. Поэтому не нужно добавлять «Exception» в название.
В стандартной библиотеке Питона есть и ржавые детали, но она — очень хороший пример организации кода:
- 200 000 строк кода
- 200 модулей верхнего уровня
- в среднем по 10 файлов в пакете
- 165 исключений
10 файлов в пакете — это много, но только из-за некоторых сторонних проектов, добавленных в библиотеку, где были пакеты из всего 2 файлов. Если вам вздумается создать новое исключение, подумайте лучше, ведь в стандартной библиотеке обошлись 1 исключением на 1200 строк кода.
Я не против классов в принципе. Классы бывают нужны — когда много меняющихся данных и связанных с ними функций. Однако в каждодневной работе такое бывает нечасто. Регулярно приходится работать со стандартной библиотекой, а там уже есть подходящие классы. За вас их уже написали.
Единственное исключение в библиотеке Питона — модуль heapq. Heap queue, «очередь в куче» — это массив, который всегда отсортирован. В модуле heapq десяток методов, и они все работают с той же «кучей». Первый аргумент всегда остаётся тем же, что значит, здесь действительно напрашивается класс.
heapify(data)
pushleft(data, item)
popleft(data)
pushright(data, item)
popright(data)
и т.д.
Каждый раз, когда нужно пользоваться heapq, я беру реализацию этого класса из своего инструментария.
class Heap(object):
def __init__(self, data=None, key=lambda x: None):
self.heap = data or []
heapq.heapify(self.heap)
self.key = key
def pushleft(self, item):
if self.key:
item = (self.key(item), item)
heapq.pushleft(self.heap, item)
def popleft(self):
return heapq.popleft(self.heap)[1]
Классы разрастаются как сорняки
Состояние OAuth в Питоне — неважное. Опять же, есть сторонние библиотеки, и прежде чем использовать в своём проекте, их нужно прочесть.
Я пытался использовать сокращатель урлов от Гугла: мне нужно было взять урлы и просто сократить их. У Гугла есть проект, в котором 10 000 строк кода. 115 модулей и 207 классов. Я написал отповедь об этом в Гугле+, но мало кто её видел, а Гвидо (Ван Россум — прим. пер.) прокомментировал: «Я снимаю с себя ответственность за гугловский код API.» 10 000 строк кода — там же обязательно найдётся какая-нибудь дрянь вроде ШаурМэйла. Вот, например, класс Flow («поток»), от которого наследуют другие.
class Flow(object):
pass
class Storage(object):
def put(self, data): _abstract()
def get(self): _abstract()
def _abstract(): raise NotImplementedError
Он пустой. Но у него есть свой модуль, и каждый раз читая наследующий его класс, надо сходить, проверить тот файл и снова убедиться, что тот класс пуст. Кто-то глядел в будущее и решил: «Напишу-ка я 3 строчки кода сейчас, чтобы в будущем эти 3 строчки не менять.» И отнял время у всех, кто читает его библиотеку. Есть ещё класс Хранилище, (Storage) который почти ничего не делает. В нём правильно обрабатываются ошибки, используя стандартные исключения, но им делают алиасы, и опять же нужно ходить читать их код, чтобы выяснить, как это работает.
Чтобы внедрить OAuth3 мне понадобилась неделя. Пару дней заняло чтение десяти тысяч строк кода, после чего я стал искать другие библиотеки. Нашёл python-oauth3. Это вторая версия python-oauth, но она на самом деле не умеет работать с OAuth3, что не сразу удалось выяснить. Впрочем, эта библиотека немного лучше гугловской: только 540 строк и 15 классов.
Я переписал её ещё проще и назвал python-foauth3. 135 строк кода и 3 класса, и то всё равно много, я не достаточно её отрефакторил. Вот один из этих трёх классов:
class Error(Exception):
pass
Срамота!
Жизнь
Последний пример. Все вы видели игру «Жизнь» Конвэя, даже если не знаете её имени. Есть клетчатое поле, каждый ход вы считаете для каждой клетки соседние, и в зависимости от них она будет либо живой, либо мёртвой. И получаются такие красивые устойчивые узоры, как планер: клетки впереди оживают, а сзади умирают, и планер как будто летит по полю.
Игра «жизнь» очень проста: поле и пара правил. Мы задаём эту задачу на собеседовании, потому что если вы не умеете такого — нам не о чем разговаривать. Многие сразу же говорят «Клетка — существительное. Класс надо.» Какие свойства в этом классе? Место, живая или нет, состояние в следующий ход, всё? Ещё есть соседи. Потом начинают описывать поле. Поле — это множество клеток, поэтому это сетка, у неё метод «подсчитать», который обсчитывает клетки внутри.
class Cell(object):
def __init__(self, x, y, alive=True):
self.x = x
self.y = y
self.alive = alive
self.next = None
def neigbors(self):
for i, j in itertools.product(range(-1, 2), repeat=2):
if (i, j) != (0, 0):
yield (self.x + i, self.y + j)
class Board(object):
def __init__(self):
self.cells = {} # { (x, y): Cell() }
def advance(self):
for (x, y), cell in self.cells.items():
alive_neighbors = len(cell.neighbors)
cell.next = (alive_neighbors == 3 or (alive_neighbors == 2 and cell.alive))
На этом месте надо сказать «стоп»: у нас есть класс Поле, в котором 2 метода: __init__ и «сделать ход». В нём одно свойство — словарь, значит со словарём и надо работать. Заметьте, что не надо хранить соседей точки, они уже и так есть в словаре. Живая точка или нет — это просто булево значение, поэтому будем хранить координаты только живых клеток. А раз в словаре хранятся только True, нужен не словарь а просто множество (set) координат. Наконец, новое состояние не нужно, можно просто заново создать список живых клеток.
def neigbors(point):
x, y = point
for i, j in itertools.product(range(-1, 2), repeat=2):
if any((i, j)):
yield (x + i, y + j)
def advance(board):
newstate = set()
recalc = board | set(itertools.chain(*map(neighbors, board)))
for point in recalc:
count = sum((neigh in board)
for neigh in neighbors(point))
if count == 3 or (count == 2 and point in board):
newstate.add(point)
return newstate
glider = set([(0, 0), (1, 0), (2, 0), (0, 1), (1, 2)])
for i in range(1000):
glider = advance(glider)
print glider
Получается очень простая, сжатая реализация игры. Двух классов тут не надо. Внизу — координаты планера, их вставляют в поле, и планер летит. Всё. Это полная реализация игры «жизнь».
Резюме
1. Если вы видите класс с двумя методами, включая __init__, это не класс.
2. Не создавайте новых исключений, если они не нужны (а они не нужны).
3. Упрощайте жёстче.
От переводчика: в комментариях я вижу, что многие восприняли доклад как полное отрицание ООП. Это ошибка. Пункт 1 из итогов чётко говорит, что такое не класс. Классы нужны, но суть доклада — в том, что не нужно ими злоупотреблять.
Классы в Python
Вы здесь: Главная — Python — Основы Python — Классы в Python
Python — это объектно-ориентированный язык программирования. В Python почти все является объектами со свойствами и методами. Классы в языке Python похожи на классы в таких языках как Java, Python, но со своими особенностями. Так же как и в них, в Python, класс - это чертеж, на основе которого создается физический объект.
Далее в примере кода мы посмотрим как создаются и инициализируются классы:
# создаем класс
class Site: # со свойством
name = 'Myrusakov.Ru'
# создаем объект
site = Site()
# выводим его свойство
print(site.name)
Класс — это новый тип данных, который надо инициализировать перед использованием. Инициализация класса в Python выполняется в конструкторе. Код ниже:
class Site: # конструктор класса вызывается автоматически при создании объекта
# первым аргументом передаем ссылку (параметр self) на сам класс (в Python это надо делать явно)
# аргументы передаем далее
def __init__(self,name,url):
self.name = name
self.url = url
# создаем и инициализируем объект
site = Site('Myrusakov.Ru','https://myrusakov.ru')
# выводим свойства
print(site.name)
print(site.url)
Как видно создание класса и объекта в Python ничем принципиально не отличаются от таких же операций в Java и PHP.
Классы не были бы классами, если бы для них нельзя было определять методы. Методы классов в Python также определяются достаточно просто.
class Site: # конструктор класса вызывается автоматически при создании объекта
# первым аргументом передаем ссылку на сам класс (в Python это надо делать явно)
# аргументы передаем далее
def __init__(self,name,url):
self.name = name
self.url = url
# устанавливаем возвраст сайта
# первым аргументом снова передаем ссылку на объект, иначе у нас не будет доступа
# к свойствам класса
def setAge(self, age):
self.age = age
def show(self):
print('Название: {name}; URL: {url}; Возвраст: {age}'.format(name=self.name,url=self.url,age=self.age))
# создаем и инициализируем объект
site = Site('Myrusakov.Ru','https://myrusakov.ru')
# устанавливаем свойство объекта
site.setAge(12)
# печатаем объект
site.show()
Таким образом, создание и использование объектов в Python в своей основе достаточно простая операция.
- Создано 23.04.2020 11:03:52
- Михаил Русаков
Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (http://myrusakov.ru)!
Добавляйтесь ко мне в друзья ВКонтакте: http://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: http://vk.com/rusakovmy.
Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления
Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.
Порекомендуйте эту статью друзьям:
Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):
-
Кнопка:
<a href=»https://myrusakov.ru» target=»_blank»><img src=»https://myrusakov.ru/images/button.gif» alt=»Как создать свой сайт» /></a>Она выглядит вот так:
-
Текстовая ссылка:
<a href=»https://myrusakov.ru» target=»_blank»>Как создать свой сайт</a>Она выглядит вот так: Как создать свой сайт
- BB-код ссылки для форумов (например, можете поставить её в подписи):
[URL=»https://myrusakov.ru»]Как создать свой сайт[/URL]
Заметки об объектной системе языка Python ч.1 / Хабр
Несколько заметок об объектной системе python’a. Рассчитаны на тех, кто уже умеет программировать на python. Речь идет только о новых классах (new-style classes) в python 2.3 и выше. В этой статье рассказывается, что такое объекты и как происходит поиск атрибутов.Объекты
Все данные в питоне — это объекты. Каждый объект имеет 2 специальных атрибута __class__ и __dict__.
- __class__ — определяет класс или тип, экзмепляром которого является объект. Тип (или класс объекта) определяет его поведение; он есть у всех объектов, в том числе и встроенных. Тип и класс — это разные названия одного и того же. x.__class__ <==> type(x).
- __dict__ словарь, дающий доступ к внутреннему пространству имен, он есть почти у всех объектов, у многих встроенных типов его нет.
>>> def foo(): pass
...
>>> foo.__class__
<type 'function'>
>>> foo.__dict__
{}
>>> (42).__dict__
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute '__dict__'
>>> (42).__class__
<type 'int'>
>>> class A(object):
... qux = 'A'
... def __init__(self, name):
... self.name=name
... def foo(self):
... print 'foo'
...
>>> a = A('a')
У a тоже есть __dict__ и __class__:
>>> a.__dict__ {'name': 'a'}
>>> a.__class__
<class '__main__.A'>
>>> type(a)
<class '__main__.A'>
>>> a.__class__ is type(a)
True
Класс и тип — это одно и то же.
>>> a.__class__ is type(a) is A
True
a.__dict__ — это словарь, в котором находятся внутренние (или специфичные для объекта) атрибуты, в данном случае ‘name’. А в a.__class__ класс (тип).
И, например, в методах класса присваивание self.foo = bar практически идентично self.__dict__[‘foo’] = bar или сводится к аналогичному вызову.
В __dict__ объекта нет методов класса, дескрипторов, классовых переменных, свойств, статических методов класса, все они определяются динамически с помощью класса из __class__ атрибута, и являются специфичными именно для класса (типа) объекта, а не для самого объекта.
Пример. Переопределим класс объекта a:
>>> class B(object):
... qux = 'B'
... def __init__(self):
... self.name = 'B object'
... def bar(self):
... print 'bar'
...
>>> a.__dict__
{'name': 'a'}
>>> a.foo()
foo
>>> a.__class__
<class '__main__.A'>
>>> a.__class__ = B
>>> a.__class__
<class '__main__.B'>
Смотрим, что поменялось.
Значение a.name осталось прежним, т.е. __init__ не вызывался при смене класса.
>>> a.__dict__
{'name': 'a'}
Доступ к классовым переменным и методам «прошлого» класса A пропал:
>>> a.foo()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'B' object has no attribute 'foo'
А вот классовые переменные и методы класса B доступы:
>>> a.bar()
bar
>>> a.qux
'B'
Работа с атрибутам объекта: установка, удаление и поиск, равносильна вызову встроенных функций settattr, delattr, getattr:
a.x = 1 <==> setattr(a, ‘x’, 1)
del a.x <==> delattr(a, ‘x’)
a.x <==> getattr(a, ‘x’)
При этом стоит стоит понимать, что setattr и delattr влияют и изменяют только сам объект (точнее a.__dict__), и не изменяют класс объекта.
qux — является классовой переменной, т.е. она «принадлежит» классу B, а не объекту a:
>>> a.qux
'B'
>>> a.__dict__
{'name': 'a'}
Если мы попытаемся удалить этот атрибут, то получим ошибку, т.к. delattr будет пытаться удалить атрибут из a.__dict__
>>> delattr(a, 'qux')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: qux
>>> del a.qux
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: qux
>>> a.qux
'B'
>>>
Далее, если мы попытаемся изменить (установить) атрибут, setattr поместит его в __dict__, специфичный для данного, конкретного объекта.
>>> b = B()
>>> b.qux
'B'
>>> a.qux = 'myB'
>>> a.qux
'myB'
>>> a.__dict__
{'qux': 'myB', 'name': 'a'}
>>> b.qux
'B'
>>>
Ну и раз есть ‘qux’ в __dict__ объекта, его можно удалить с помощью delattr:
>>> del a.qux
После удаления, a.qux будет возвращать значение классовой переменной:
>>> a.qux
'B'
>>> a.__dict__
{'name': 'a'}
Итак:
- класс для объекта — это значение специального атрибута __class__ и его можно менять. (Хотя в официальной документации говорится, что никаких гарантий нет, но на самом деле можно)
- почти каждый объект имеет свое пространство имен (атрибутов), доступ (не всегда полный), к которому осуществляется с помощью специального атрибута __dict__
- класс фактичеки влияет только на поиск атрибутов, которых нет в __dict__, как-то: методы класса, дескрипторы, магические методы, классовые переменные и прочее.
Объекты и классы
Классы — это объекты, и у них тоже есть специальные атрибуты __class__ и __dict__.
>>> class A(object):
... pass
...
У класса тип type.
>>> A.__class__
<type 'type'>
Правда __dict__ у классов не совсем словарь
>>> A.__dict__
<dictproxy object at 0x1111e88>
Но __dict__ ответственен за доступ к внутреннему пространству имен, в котором хранятся методы, дескрипторы, переменные, свойства и прочее:
>>> dict(A.__dict__)
{'__module__': '__main__', 'qux': 'A', '__dict__': <attribute '__dict__' of 'A' objects>, 'foo': <function foo at 0x7f7797a25c08>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
>>> A.__dict__.keys()
['__module__', 'qux', '__dict__', 'foo', '__weakref__', '__doc__']<
В классах помимо __class__ и __dict__, имеется еще несколько специальных атрибутов: __bases__ — список прямых родителей, __name__ — имя класса. [1]
Классы можно считать эдакими расширениями обычных объектов, которые реализуют интерфейс типа. Множество всех классов (или типов) принадлежат множеству всех объектов, а точнее является его подмножеством. Иначе говоря, любой класс является объектом, но не всякий объект является классом. Договоримся называть обычными объектами(regular objects) те объекты, которые классами не являются.
Небольшая демонстрация, которая станет лучше понятна чуть позже.
Класс является объектом.
>>> class A(object):
... pass
...
>>> isinstance(A, object)
True
Число — это тоже объект.
>>> isinstance(42, object)
True
Класс — это класс (т.е. тип).
>>> isinstance(A, type)
True
А вот число классом (типом) не является. (Что такое type будет пояснено позже)
>>> isinstance(42, type)
False
>>>
Ну и a — тоже обычный объект.
>>> a = A()
>>> isinstance(a, A)
True
>>> isinstance(a, object)
True
>>> isinstance(a, type)
False
И у A всего один прямой родительский класс — object.
>>> A.__bases__
(<type 'object'>,)
Часть специальных параметров можно даже менять:
>>> A.__name__
'A'
>>> A.__name__ = 'B'
>>> A
<class '__main__.B'>
С помощью getattr получаем доступ к атрибутам класса:
>>> A.qux
'A'
>>> A.foo
<unbound method A.foo>
>>>
Поиск атрибутов в обычном объекте
В первом приближении алгоритм поиска выглядит так: сначала ищется в __dict__ объекта, потом идет поиск по __dict__ словарям класса объекта (который определяется с помощью __class__) и __dict__ его базовых классов в рекурсивном порядке.
Пример.
>>> class A(object):
... qux = 'A'
... def __init__(self, name):
... self.name=name
... def foo(self):
... print 'foo'
...
>>> a = A()
>>> b = A()
Т.к. в обычных объектах a и b нет в __dict__ атрибута ‘qux’, то поиск продолжается во внутреннем словаре __dict__ их типа (класса), а потом по __dict__ словарям родителей в определенном порядке:
>>> b.qux
'A'
>>> A.qux
'A'
Меняем атрибут qux у класса A. И соответственно должны поменяться значения, которые возвращают экземпляры класса A — a и b:
>>> A.qux='B'
>>> a.qux
'B'
>>> b.qux
'B'
>>>
Точно так же в рантайме к классу можно добавить метод:
>>> A.quux = lambda self: 'i have quux method'
>>> A.__dict__['quux']
<function <lambda> at 0x7f7797a25b90>
>>> A.quux
<unbound method A.<lambda>>
И доступ к нему появится у экземпляров:
>>> a.quux()
'i have quux method'
Точно так же как и с любыми другими объектами, можно удалить атрибут класса, например, классовую переменную qux:
>>> del A.qux
Она удалиться из __dict__
>>> A.__dict__['qux']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'qux'
И доступ у экземляров пропадет.
>>> a.qux
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'A' object has no attribute 'qux'
>>>
У классов почти такой же поиск атрибутов, как и у обычных объектов, но есть отличия: поиск начинается с собственного __dict__ словаря, а потом идет поиск по __dict__ словарям суперклассов (которые хранятся в __bases__) по опредленному алгоритму, а затем по классу в __class__ и его суперклассах. (Подробнее об этом позже).
Cсылки
- Unifying types and classes in Python — главный документ, объясняющий что, как и зачем в новых классах.
- Making Types Look More Like Classes — PEP 252, описывающий отличие старых классов от новых.
- Built-in functions — детальное описание работы всех встроенных функций.
- Data model — детальное описание модели данных python’а.
- Python types and objects — объяснение объектной модели python на простых примерах с картинками.
Примечания
[1] О __module__ и __doc__ для простоты изложения пока забудем. Полный список атрибутов класса можно посмотреть в документации
Читать дальше
Заметки об объектной системе языка Python ч.2
Заметки об объектной системе языка Python ч.3
Python Classes
Python Классы / Объекты
Python — это объектно-ориентированный язык программирования.
Почти все в Python является объектом, со своими свойствами и методами.
Класс подобен конструктору объектов или «чертежу» для создания объектов.
Создать класс
Чтобы создать класс, используйте ключевое слово class
:
Пример
Создайте класс с именем MyClass со свойством с именем x:
класс MyClass:
x = 5
Создать объект
Теперь мы можем использовать класс с именем MyClass для создания объектов:
Пример
Создайте объект с именем p1 и напечатайте значение x:
p1 = MyClass ()
print (p1.х)
Функция __init __ ()
Приведенные выше примеры являются классами и объектами в их простейшей форме и не очень полезно в реальных приложениях.
Чтобы понять значение классов, мы должны понимать встроенную функцию __init __ () функция.
Все классы имеют функцию с именем __init __ (), которая всегда выполняется, когда класс инициируется.
Используйте функцию __init __ (), чтобы назначать значения свойствам объекта, или другие операции, которые необходимо выполнить, когда объект создается:
Пример
Создайте класс с именем Person, используйте функцию __init __ () для присвоения значений для имени и возраста:
Класс Лицо:def __init __ (личность, имя, возраст):
самостоятельно.имя = имя
self.age = возраст
p1 = человек («Джон», 36)
Печать (p1.name) Печать
(p1.age)
Примечание: Функция __init __ ()
вызывается автоматически каждый раз, когда класс используется для создания нового объекта.
Методы объекта
Объекты также могут содержать методы. Методы в объектах являются функциями, которые принадлежат объекту.
Давайте создадим метод в классе Person:
Пример
Вставьте функцию, которая печатает приветствие, и выполните ее на объекте p1:
Класс Лицо:def __init __ (личность, имя, возраст):
самостоятельно.имя = имя
self.age = возраст
def myfunc (self):
print («Здравствуйте, меня зовут» + self.name)
p1 = Person («Джон»,
36)
p1.myfunc ()
Примечание: Параметр self
является ссылкой на текущий экземпляр класса и используется для доступа к переменным, которые принадлежат классу.
Автопараметр
Параметр self
является ссылкой на
текущий экземпляр класса, и используется для доступа к переменным, принадлежащим классу.
Это не обязательно должно быть названо само
, вы можете
называйте это как хотите, но это должен быть первый параметр любой функции
в классе:
Пример
Используйте слова mysillyobject и abc вместо self :
класс Лицо:def __init __ (mysillyobject, имя, возраст):
mysillyobject.name = имя
mysillyobject.age = возраст
def myfunc (abc):
print («Здравствуйте, меня зовут» + abc.имя)
р1 = человек («Джон»,
36)
p1.myfunc ()
Изменить свойства объекта
Вы можете изменить свойства объектов следующим образом:
Удалить свойства объекта
Вы можете удалить свойства объектов, используя ключевое слово del
:
Удалить объекты
Вы можете удалять объекты, используя ключевое слово del
:
Пропускная ведомость
определения класса
не могут быть пустыми, но если
по какой-то причине у вас есть определение класса
без содержания, поместите в инструкцию pass
, чтобы избежать ошибки.
,
классов и объектов Python [с примерами]
Python Объекты и Классы
Python — это объектно-ориентированный язык программирования. В отличие от процедурно-ориентированного программирования, где основной упор делается на функции, объектно-ориентированное программирование делает упор на объекты.
Объект — это просто набор данных (переменных) и методов (функций), которые воздействуют на эти данные. Точно так же класс является планом для этого объекта.
Мы можем представить класс как набросок (прототип) дома.Он содержит все детали о полах, дверях, окнах и т. Д. На основании этих описаний мы строим дом. Дом это объект.
Так как много домов можно сделать из чертежа дома, мы можем создать много объектов из класса. Объект также называется экземпляром класса, и процесс создания этого объекта называется экземпляром .
Определение класса в Python
Как и определения функций начинаются с ключевого слова def в Python, определения классов начинаются с ключевого слова class.
Первая строка внутри класса называется docstring и имеет краткое описание класса. Хотя это и не обязательно, это настоятельно рекомендуется.
Вот простое определение класса.
класс MyNewClass:
'' 'Это строка документации. Я создал новый класс '' '
пройти
Класс создает новое локальное пространство имен, в котором определены все его атрибуты. Атрибуты могут быть данными или функциями.
Есть также специальные атрибуты, которые начинаются с двойного подчеркивания __
.Например, __doc__
дает нам строку документации этого класса.
Как только мы определяем класс, создается новый объект класса с тем же именем. Этот объект класса позволяет нам получать доступ к различным атрибутам, а также создавать новые объекты этого класса.
класс Человек:
«Это класс человека»
возраст = 10
def greet (self):
печать ( 'Hello')
# Выход: 10
печать (person.age)
# Вывод:
печать (Person.greet)
# Вывод: «Это мой второй класс»
печать (Person.__doc__)
Выход
10Это человек класса
Создание объекта в Python
Мы увидели, что объект класса можно использовать для доступа к различным атрибутам.
Он также может использоваться для создания новых экземпляров объектов (создания экземпляров) этого класса. Процедура создания объекта аналогична вызову функции.
>>> Гарри = Человек ()
Это создаст новый экземпляр объекта с именем Гарри .Мы можем получить доступ к атрибутам объектов, используя префикс имени объекта.
Атрибуты могут быть данными или методом. Методы объекта являются соответствующими функциями этого класса.
Это означает, что, поскольку Person.greet
является функциональным объектом (атрибут класса), Person.greet
будет объектом метода.
класс Человек:
«Это класс человека»
возраст = 10
def greet (self):
печать ( 'Hello')
# создать новый объект класса Person
Гарри = Человек ()
# Вывод: <функция Person.Приветствуйте>
печать (Person.greet)
# Вывод: <связанный метод Person.greet из <__ main __. Person object >>
печать (harry.greet)
# Вызов метода greet () объекта
# Вывод: Привет
harry.greet ()
Выход
<связанный метод Person.greet из <__ main __. Person объекта в 0x7fd288e9fa30 >> Привет
Возможно, вы заметили параметр self
в определении функции внутри класса, но мы назвали метод просто как Гарри.greet ()
без каких-либо аргументов. Это все еще работало.
Это потому, что всякий раз, когда объект вызывает свой метод, сам объект передается в качестве первого аргумента. Итак, harry.greet ()
переводится в Person.greet (harry)
.
Как правило, вызов метода со списком из n аргументов эквивалентен вызову соответствующей функции со списком аргументов, который создается путем вставки объекта метода перед первым аргументом.
По этим причинам первым аргументом функции в классе должен быть сам объект.Это условно называется само по себе . Это может быть названо иначе, но мы настоятельно рекомендуем следовать соглашению.
Теперь вы должны быть знакомы с объектом класса, объектом экземпляра, объектом функции, объектом метода и их различиями.
Конструкторы в Python
Функции класса, которые начинаются с двойного подчеркивания __
, называются специальными функциями, поскольку они имеют особое значение.
Особый интерес представляет функция __init __ ()
.Эта специальная функция вызывается всякий раз, когда создается новый объект этого класса.
Этот тип функции также называется конструкторами в объектно-ориентированном программировании (ООП). Обычно мы используем его для инициализации всех переменных.
класс ComplexNumber:
def __init __ (self, r = 0, i = 0):
self.real = r
self.imag = я
def get_data (self):
печать (F '{self.real} + {self.imag} J')
# Создать новый объект ComplexNumber
num1 = ComplexNumber (2, 3)
# Вызовите метод get_data ()
# Выход: 2 + 3j
num1.получить данные()
# Создайте еще один объект ComplexNumber
# и создайте новый атрибут 'attr'
num2 = ComplexNumber (5)
num2.attr = 10
# Вывод: (5, 0, 10)
печать ((num2.real, num2.imag, num2.attr))
# но объект c1 не имеет атрибута "attr"
# AttributeError: у объекта 'ComplexNumber' нет атрибута 'attr'
печать (num1.attr)
Выход
2 + 3j (5, 0, 10) Traceback (последний вызов был последним): Файл "<строка>", строка 27, в <модуле> печать (num1.атр) AttributeError: у объекта 'ComplexNumber' нет атрибута 'attr'
В приведенном выше примере мы определили новый класс для представления комплексных чисел. Он имеет две функции: __init __ ()
для инициализации переменных (по умолчанию ноль) и get_data ()
для правильного отображения числа.
Интересно отметить, что на предыдущем этапе атрибуты объекта могут создаваться на лету. Мы создали новый атрибут attr для объекта num2 и также прочитали его.Но это не создает этот атрибут для объекта num1 .
Удаление атрибутов и объектов
Любой атрибут объекта можно удалить в любое время, используя оператор del
. Попробуйте следующее в оболочке Python, чтобы увидеть результат.
>>> num1 = ComplexNumber (2,3)
>>> del num1.imag
>>> num1.get_data ()
Traceback (последний вызов был последним):
...
AttributeError: у объекта 'ComplexNumber' нет атрибута 'imag'
>>> del ComplexNumber.получить данные
>>> num1.get_data ()
Traceback (последний вызов был последним):
...
AttributeError: у объекта 'ComplexNumber' нет атрибута 'get_data'
Мы можем даже удалить сам объект, используя оператор del.
>>> c1 = ComplexNumber (1,3)
>>> дель с1
>>> с1
Traceback (последний вызов был последним):
...
NameError: имя 'c1' не определено
На самом деле, это сложнее, чем это. Когда мы делаем c1 = ComplexNumber (1,3)
, новый объект экземпляра создается в памяти, и имя c1 связывается с ним.
По команде del c1
эта привязка удаляется, а имя c1 удаляется из соответствующего пространства имен. Однако объект продолжает существовать в памяти, и если с ним не связано никакое другое имя, он позже автоматически уничтожается.
Это автоматическое уничтожение объектов без ссылок в Python также называется сборкой мусора.
Удаление объектов в Python удаляет привязку имени ,В этом руководстве мы объясним концепции объектно-ориентированного (ООП) языка Python. Во-первых, вы узнаете — что такое Python класс , как создавать и использовать его в программах.
Кроме того, мы расскажем вам, что такое ключевое слово «self», какие атрибуты может иметь класс и как определять конструкторы для целей инициализации.
Вы также узнаете, как наследование работает в Python, как оно работает с множественным наследованием и что такое перегрузка операторов?
Python класс и объекты — Введение
☛ Вернуться к учебникам по Python
Могу ли я использовать ООП с Python?
Да, Python поддерживает объектно-ориентированное программирование (ООП).ООП — это модель разработки, которая позволяет программисту сосредоточиться на создании повторно используемого кода. Это отличается от процедурной модели, которая следует последовательному подходу.
ООП полезно, когда у вас есть большой и сложный проект для работы. Будет много программистов, создающих повторно используемый код, делящихся и интегрирующих их исходный код. Повторное использование приводит к лучшей читаемости и сокращает обслуживание в долгосрочной перспективе.
Python Class & OOP ОсновыЧто такое класс Python?
Класс — это набор переменных и функций в единый логический объект.Он работает как шаблон для создания объектов. Каждый объект может использовать переменные и функции класса в качестве своих членов.
Python имеет зарезервированное ключевое слово, известное как «класс», которое вы можете использовать для определения нового класса.
Объект является рабочим экземпляром класса, созданного во время выполнения.
Как создать класс на Python?
Есть несколько терминов, которые вам необходимо знать при работе с классами в Python.
1. Ключевое слово «class»
2. Атрибуты экземпляра
3.Атрибуты класса
4. Ключевое слово «self»
5. Метод «__init_»
Давайте теперь иметь четкое понимание каждого из вышеперечисленных пунктов один за другим.
Ключевое слово «класс»
С помощью ключевого слова class мы можем создать класс Python, как показано в примере ниже.
Книжный магазин класса: пройти
Что такое я?
Python предоставляет ключевое слово «self» для представления экземпляра класса. Он работает как дескриптор для доступа к членам класса, таким как атрибуты из методов класса.
Также обратите внимание, что это неявно первый аргумент метода __init__ в каждом классе Python. Вы можете прочитать об этом ниже.
Что такое __init__ (конструктор) в Python?
«__init __ ()» — это уникальный метод, связанный с каждым классом Python.
Python вызывает его автоматически для каждого объекта, созданного из класса. Его целью является инициализация атрибутов класса с помощью пользовательских значений.
Он широко известен как конструктор в объектно-ориентированном программировании.Смотрите пример ниже.
Книжный магазин класса: def __init __ (self): print ("__ init __ () конструктор вызывается ...") B1 = BookStore ()
Выход
__init __ () конструктор вызывается ...
Атрибуты экземпляра
Это специфичные для объекта атрибуты, определенные как параметры для метода __init__. Каждый объект может иметь разные значения для себя.
В приведенном ниже примере «attrib1» и «attrib2» являются атрибутами экземпляра.
Книжный магазин класса: def __init __ (self, attrib1, attrib2): self.attrib1 = attrib1 self.attrib2 = attrib2
Атрибуты класса
В отличие от атрибутов экземпляра, которые видны на уровне объекта, атрибуты класса остаются одинаковыми для всех объектов.
Посмотрите на приведенный ниже пример, чтобы продемонстрировать использование атрибутов уровня класса.
Книжный магазин класса: экземпляры = 0 def __init __ (self, attrib1, attrib2): самостоятельно.attrib1 = attrib1 self.attrib2 = attrib2 BookStore.instances + = 1 b1 = BookStore ("", "") b2 = BookStore ("", "") печать ("BookStore.instances:", BookStore.instances)
В этом примере «instance» — это атрибут уровня класса. Вы можете получить к нему доступ, используя имя класса. Это держит общее количество. созданных экземпляров.
Мы создали два экземпляра класса <Книжный магазин>. Следовательно, выполнение примера должно вывести «2» в качестве вывода.
# выход BookStore.instances: 2
Демо-класс Python
Приведенный здесь пример, где мы создаем класс BookStore и создаем его экземпляр с различными значениями.
Создание класса BookStore в Python
Книжный магазин класса: noOfBooks = 0 def __init __ (self, title, author): self.title = название self.author = автор BookStore.noOfBooks + = 1 def bookInfo (self): печать («Название книги:», сам.заглавие) print ("Автор книги:", self.author, "\ n") # Создать виртуальный книжный магазин b1 = BookStore («Большие ожидания», «Чарльз Диккенс») b2 = Книжный магазин («Война и мир», «Лев Толстой») b3 = BookStore ("Middlemarch", "George Eliot") # вызывать функции-члены для каждого объекта b1.bookInfo () b2.bookInfo () b3.bookInfo () печать ("BookStore.noOfBooks:", BookStore.noOfBooks)
Вы можете открыть IDLE или любую другую Python IDE , сохранить приведенный выше код в каком-то файле и запустить программу.
В этом примере мы создали три объекта класса BookStore, то есть b1, b2 и b3. Каждый из объектов является экземпляром класса BookStore.
UML-схема класса BookStore
UML-схема вышеприведенного кода выглядит следующим образом.
Класс и объекты Python (диаграмма UML)После выполнения кода в примере вы должны увидеть следующий результат.
# выход Название книги: Большие ожидания Автор книги: Чарльз Диккенс Название книги: Война и мир Автор книги: Лев Толстой Название книги: Middlemarch Автор книги: Джордж Элиот Книжный магазин.noOfBooks: 3
Вы могли заметить из приведенного выше примера, что мы использовали несколько ключевых слов, таких как «self», и «__init__».
Резюме
Сегодня мы рассмотрели основы классов и объектов в Python. В следующих уроках вы увидите такие темы, как Python Multiple Inheritance и перегрузка методов.
Если наше объяснение класса Python помогло вам, не стесняйтесь поделиться им с коллегами. Кроме того, подключайтесь к нашим учетным записям в социальных сетях ( Facebook / Twitter ), чтобы получать своевременные обновления.
Best,
TechBeamers
,классов и объектов Python — GeeksforGeeks
Класс — это пользовательский проект или прототип, из которого создаются объекты. Классы предоставляют средства объединения данных и функциональности вместе. Создание нового класса создает новый тип объекта, позволяя создавать новые экземпляры этого типа. К каждому экземпляру класса могут быть прикреплены атрибуты для поддержания его состояния. Экземпляры класса также могут иметь методы (определяемые его классом) для изменения его состояния.
Чтобы понять необходимость создания класса, давайте рассмотрим пример. Допустим, вы хотели отслеживать количество собак, которые могут иметь различные атрибуты, такие как порода, возраст.Если используется список, первым элементом может быть порода собаки, а вторым элементом может быть ее возраст. Предположим, есть 100 разных собак, тогда как вы узнаете, какой элемент должен быть? Что если вы захотите добавить другие свойства этим собакам? Это не хватает организации, и это именно то, что нужно для занятий.
Class создает определяемую пользователем структуру данных, которая содержит свои собственные элементы данных и функции-члены, к которым можно обращаться и использовать, создавая экземпляр этого класса.Класс похож на план объекта.
Некоторые баллы на уроке Python:
- Классы создаются по ключевому слову
, класс
. - Атрибуты — это переменные, которые принадлежат классу.
- Атрибуты всегда общедоступны и могут быть доступны с помощью оператора точки (.). Например: Myclass.Myattribute
Синтаксис определения класса: Класс ClassName: № Заявление-1 , , ,№ Заявление-N
Определение класса —
|
В приведенном выше примере ключевое слово class
указывает, что вы создаете класс, за которым следует имя класса (в данном случае Dog).
объектов класса
Объект является экземпляром класса.Класс похож на план, а экземпляр является копией класса с фактическими значениями . Это уже не идея, это настоящая собака, как собака породы мопс, которой семь лет. У вас может быть много собак для создания множества разных экземпляров, но без класса в качестве руководства вы потерялись бы, не зная, какая информация требуется.
Объект состоит из:
- Состояние: Представлено атрибутами объекта. Это также отражает свойства объекта.
- Поведение: Представлено методами объекта. Он также отражает реакцию объекта на другие объекты.
- Идентификация: Он дает уникальное имя объекту и позволяет одному объекту взаимодействовать с другими объектами.
Объявление объектов (также называется создание класса)
Когда создается объект класса, говорят, что класс создан. Все экземпляры имеют общие атрибуты и поведение класса.Но значения этих атрибутов, то есть состояние, являются уникальными для каждого объекта. Один класс может иметь любое количество экземпляров.
Пример:
Объявление объекта —
|
Выход:
mamal Я мама Я собака
В приведенном выше примере создан объект, который в основном представляет собой собаку по имени Роджер.Этот класс имеет только два атрибута класса, которые говорят нам, что Роджер — собака и млекопитающее.
Self
- Методы класса должны иметь дополнительный первый параметр в определении метода. Мы не даем значение для этого параметра при вызове метода, Python предоставляет его.
- Если у нас есть метод, который не принимает аргументов, то у нас все равно должен быть один аргумент.
- Это похоже на этот указатель в C ++ и эту ссылку в Java.
Когда мы называем метод этого объекта как myobject.метод (arg1, arg2)
, он автоматически преобразуется Python в MyClass.method (myobject, arg1, arg2)
— это все, что представляет собой особая личность.
__init__ метод
Метод __init__
похож на конструкторы в C ++ и Java. Конструкторы используются для инициализации состояния объекта. Как и методы, конструктор также содержит набор операторов (т.е. инструкций), которые выполняются во время создания объекта. Он запускается, как только создается экземпляр объекта класса.Этот метод полезен для любой инициализации, которую вы хотите выполнить с вашим объектом.
|
Выход:
Здравствуйте, меня зовут Нихил
Переменные класса и экземпляра
Переменные экземпляра предназначены для данных, уникальных для каждого экземпляра, а переменные класса — для атрибутов и методов, общих для всех экземпляров класса.Переменные экземпляра — это переменные, значение которых присваивается внутри конструктора или метода с self
, тогда как переменные класса — это переменные, значение которых присваивается в классе.
Определение экземпляра varibale с помощью конструктора.
|