Создание классов и объектов

self и this

В Java класс ссылается сам на себя, используя ключевое слово this:

this подразумевается в Java-коде. Его в принципе даже необязательно писать, кроме случаев, когда имена переменных совпадают.

Сеттер можно написать и так:

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

В Python ключевое слово self служит аналогичной цели: обращение к членам-атрибутам, но в отличие от Java, оно обязательно:

Python требует написания self в обязательном порядке. Каждый self либо создает, либо обращается к атрибуту. Если мы пропустим его, то Python просто создаст локальную переменную вместо атрибута.

Отличие в том, как мы используем self и this в Python и Java, происходит из-за основных различий между двумя языками и от того, как они именуют переменные и атрибуты.

Определение конструктора для класса

Если вы заметили реализацию класса 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'

«Приватные» поля класса

Вы обратили внимание, что в нашем классе некоторые атрибуты начинаются с нижнего подчеркивания? Это одно из множества соглашений принятых в сообществе разработчиков на языке Python, согласно которому «приватные» атрибуты должны начинаться с одного символа нижнего подчеркивания. Давайте создадим нового пользователя:. Допустим, что мы хотим изменить пароль (или адрес электронной почты) и делаем это через прямое обращение к атрибуту:

Допустим, что мы хотим изменить пароль (или адрес электронной почты) и делаем это через прямое обращение к атрибуту:

Почему пароль не прошел проверку? Мы изменили значение атрибута напрямую, не используя функцию , таким образом, мы сохранили пароль в открытом виде. В свою очередь функция хеширует переданный ей пароль в качестве аргумента и затем сравнивает его с паролем, который хранился в атрибуте .

Note

Больше про нижние подчеркивания можно узнать тут.

Support for automatically setting __slots__?

At least for the initial release, __slots__ will not be supported.
__slots__ needs to be added at class creation time. The Data
Class decorator is called after the class is created, so in order to
add __slots__ the decorator would have to create a new class, set
__slots__, and return it. Because this behavior is somewhat
surprising, the initial version of Data Classes will not support
automatically setting __slots__. There are a number of
workarounds:

  • Manually add __slots__ in the class definition.
  • Write a function (which could be used as a decorator) that inspects
    the class using fields() and creates a new class with
    __slots__ set.

С множественным наследованием

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

class A1:
    def __init__(self, a1):
        print('A1 Constructor')
        self.var_a1 = a1


class B1:
    def __init__(self, b1):
        print('B1 Constructor')
        self.var_b1 = b1


class C1(A1, B1):
    def __init__(self, a1, b1, c1):
        print('C1 Constructor')
        A1.__init__(self, a1)
        B1.__init__(self, b1)
        self.var_c1 = c1


c_obj = C1(1, 2, 3)
print(f'c_obj var_a={c_obj.var_a1}, var_b={c_obj.var_b1}, var_c={c_obj.var_c1}')

Вывод:

C1 Constructor
A1 Constructor
B1 Constructor
c_obj var_a=1, var_b=2, var_c=3

Python не поддерживает несколько конструкторов

Python не поддерживает несколько конструкторов, в отличие от других популярных объектно-ориентированных языков программирования, таких как Java.

Мы можем определить несколько методов __init __(), но последний из них переопределит предыдущие определения.

class D:

    def __init__(self, x):
        print(f'Constructor 1 with argument {x}')

    # this will overwrite the above constructor definition
    def __init__(self, x, y):
        print(f'Constructor 1 with arguments {x}, {y}')


d1 = D(10, 20) # Constructor 1 with arguments 10, 20

Может ли функция Python __init __() что-то вернуть?

Если мы попытаемся вернуть значение, отличное от None, из функции __init __(), это вызовет ошибку TypeError.

class Data:

    def __init__(self, i):
        self.id = i
        return True

d = Data(10)

Вывод:

TypeError: __init__() should return None, not 'bool'

Если мы изменим оператор return на тогда код будет работать без каких-либо исключений.

Основное наследование

Наследование в Python основано на сходных идеях, используемых в других объектно-ориентированных языках, таких как Java, C ++ и т. Д. Новый класс может быть получен из существующего класса следующим образом.

является уже существующий (родительский) класс, а это новый (дочерний) класс , который наследует (или подклассов) атрибуты . Примечание: По состоянию на Python 2.2, все классы неявно наследуются от класса , который является базовым классом для всех встроенных типов.

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

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

класс автоматически наследует все атрибуты класса, а также класса объектов. используется для вызова метод класса, по существу , вызовом любой перекрытый метод базового класса. Примечание: в Python 3, не требует аргументов.

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

Примеры классов в Python и Java

Для начала давайте реализуем простейший класс в Python и Java, чтобы проиллюстрировать некоторые отличия в этих языках, и будем постепенно вносить в этот класс изменения.
Представим, что у нас есть следующее определение класса Car в Java:

Имя исходного Java-файла должно соответствовать имени хранящегося в нем класса, поэтому мы обязаны назвать файл Car.java. Каждый Java-файл может содержать только один публичный класс.
Такой же класс в Python будет выглядеть так:

В Python вы можете объявить класс где угодно и когда угодно. Сохраним этот файл как car.py.
Используя эти классы как основу, продолжим исследование основных компонентов классов и объектов.

Управление доступом

В Java мы получаем доступ к приватным атрибутам при помощи сеттеров (setters) и геттеров (getters). Для того чтобы пользователь перекрасил-таки свою машину, добавим следующий кусок кода в Java-класс:

Поскольку методы getColor() и setColor() – публичные, то любой пользователь может вызвать их и получить / изменить цвет машины. Использование приватных атрибутов, к которым мы получаем доступ публичными геттерами и сеттерами, — одна из причин большей «многословности» Java в сравнении с Python.

Как было показано выше, в Python мы можем получить доступ к атрибутам напрямую. Поскольку всё – публичное, мы может достучаться к чему угодно, когда угодно и откуда угодно. Мы можем получать и устанавливать значения атрибутов напрямую, обращаясь по их имени. В Python мы можем даже удалять атрибуты, что немыслимо в Java:

Однако бывает и так, что мы хотим контролировать доступ к атрибутам. В таком случае нам на помощь приходят Python-свойства (properties).

В Python свойства обеспечивают управляемый доступ к атрибутам класса при помощи декораторов (decorators). Используя свойства, мы объявляем функции в питоновских классах подобно геттерам и сеттерам в Java (бонусом идет удаление атрибутов).

Работу свойств можно увидеть на следующем примере класса Car:

В данном примере мы расширяем понятие класса Car, включая электромобили. В строке 6 объявляется атрибут _voltage, чтобы хранить в нем напряжение батареи.

В строках 9 и 10 для контролируемого доступа мы создаем функцию voltage() и возвращаем значение приватной переменной. Используя декоратор @property, мы превращаем его в геттер, к которому теперь любой пользователь получает доступ.

В строках 13-15 мы определяем функцию, так же носящую название voltage(). Однако, мы ее декорируем по-другому: voltage.setter. Наконец, в строках 18-20 мы декорируем функцию voltage() при помощи voltage.deleter и можем при необходимости удалить атрибут _voltage.

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

Обратите внимание, что мы используем voltage, а не _ voltage. Так мы указываем Python-у на то, что следует применять свойства, которые только что определили:

  • Когда в 4-й строке выводим значение my_car.voltage, Python вызывает функцию voltage(), декорированную @property.
  • Когда в 7-й строке присваиваем значение my_car.voltage, Python вызывает функцию voltage(), декорированную voltage.setter.
  • Когда в 13-й строке удаляем my_car.voltage, Python вызывает функцию voltage(), декорированную voltage.deleter.

Вышеприведенные декораторы дают нам возможность контролировать доступ к атрибутам без использования различных методов. Можно даже сделать атрибут свойством только для чтения (read-only), убрав декорированные функции @.setter и @.deleter.

9.9. Generators¶

are a simple and powerful tool for creating iterators. They
are written like regular functions but use the statement
whenever they want to return data. Each time is called on it, the
generator resumes where it left off (it remembers all the data values and which
statement was last executed). An example shows that generators can be trivially
easy to create:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield dataindex
>>> for char in reverse('golf'):
...     print(char)
...
f
l
o
g

Anything that can be done with generators can also be done with class-based
iterators as described in the previous section. What makes generators so
compact is that the and methods
are created automatically.

Another key feature is that the local variables and execution state are
automatically saved between calls. This made the function easier to write and
much more clear than an approach using instance variables like
and .

9.4. Random Remarks¶

If the same attribute name occurs in both an instance and in a class,
then attribute lookup prioritizes the instance:

>>> class Warehouse
        purpose = 'storage'
        region = 'west'

>>> w1 = Warehouse()
>>> print(w1.purpose, w1.region)
storage west
>>> w2 = Warehouse()
>>> w2.region = 'east'
>>> print(w2.purpose, w2.region)
storage east

Data attributes may be referenced by methods as well as by ordinary users
(“clients”) of an object. In other words, classes are not usable to implement
pure abstract data types. In fact, nothing in Python makes it possible to
enforce data hiding — it is all based upon convention. (On the other hand,
the Python implementation, written in C, can completely hide implementation
details and control access to an object if necessary; this can be used by
extensions to Python written in C.)

Clients should use data attributes with care — clients may mess up invariants
maintained by the methods by stamping on their data attributes. Note that
clients may add data attributes of their own to an instance object without
affecting the validity of the methods, as long as name conflicts are avoided —
again, a naming convention can save a lot of headaches here.

There is no shorthand for referencing data attributes (or other methods!) from
within methods. I find that this actually increases the readability of methods:
there is no chance of confusing local variables and instance variables when
glancing through a method.

Often, the first argument of a method is called . This is nothing more
than a convention: the name has absolutely no special meaning to
Python. Note, however, that by not following the convention your code may be
less readable to other Python programmers, and it is also conceivable that a
class browser program might be written that relies upon such a convention.

Any function object that is a class attribute defines a method for instances of
that class. It is not necessary that the function definition is textually
enclosed in the class definition: assigning a function object to a local
variable in the class is also ok. For example:

# Function defined outside the class
def f1(self, x, y):
    return min(x, x+y)

class C
    f = f1

    def g(self):
        return 'hello world'

    h = g

Now , and are all attributes of class that refer to
function objects, and consequently they are all methods of instances of
— being exactly equivalent to . Note that this practice
usually only serves to confuse the reader of a program.

Methods may call other methods by using method attributes of the
argument:

class Bag
    def __init__(self):
        self.data = []

    def add(self, x):
        self.data.append(x)

    def addtwice(self, x):
        self.add(x)
        self.add(x)

Methods may reference global names in the same way as ordinary functions. The
global scope associated with a method is the module containing its
definition. (A class is never used as a global scope.) While one
rarely encounters a good reason for using global data in a method, there are
many legitimate uses of the global scope: for one thing, functions and modules
imported into the global scope can be used by methods, as well as functions and
classes defined in it. Usually, the class containing the method is itself
defined in this global scope, and in the next section we’ll find some good
reasons why a method would want to reference its own class.

Документирование классов

Весь код нужно комментировать и документировать. Классы – не исключение. Стоит помнить, что код вы пишите не для себя, и вполне вероятно, что написанное вами придётся поддерживать другим людям. Комментарии повышают читаемость и увеличивают легкость восприятие кода в разы, тем самым экономя время и деньги.

Поэтому вот наша статья о Python-документации.

ООП ещё долгое время будет оставаться передовой парадигмой программирования. Но учить её полезно и по другой причине. Прямая связь объектно-ориентированного программирования с реальным миром помогает глубже понимать устройство и принципы работы, как самого языка, так и написания кода в целом.

Переменные класса или переменные экземпляра?

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

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

Итак, очень важно понимать, когда использовать переменную класса, а когда — переменную экземпляра

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

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

class Employee:
    count = 0
    ids_list = []

    def __init__(self, i):
        self.id = i
        Employee.count += 1
        self.ids_list.append(i)


for x in range(0, 10):
    emp = Employee(x)

print(f'Number of employees created = {Employee.count}')
print(f'List of employee ids allocated = {Employee.ids_list}')

emp = Employee(1000)
print(f'List of employee ids allocated = {emp.ids_list}')

Выход:

Number of employees created = 10
List of employee ids allocated = 
List of employee ids allocated = 

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

9.6. Private Variables¶

“Private” instance variables that cannot be accessed except from inside an
object don’t exist in Python. However, there is a convention that is followed
by most Python code: a name prefixed with an underscore (e.g. ) should
be treated as a non-public part of the API (whether it is a function, a method
or a data member). It should be considered an implementation detail and subject
to change without notice.

Since there is a valid use-case for class-private members (namely to avoid name
clashes of names with names defined by subclasses), there is limited support for
such a mechanism, called name mangling. Any identifier of the form
(at least two leading underscores, at most one trailing underscore)
is textually replaced with , where is the
current class name with leading underscore(s) stripped. This mangling is done
without regard to the syntactic position of the identifier, as long as it
occurs within the definition of a class.

Name mangling is helpful for letting subclasses override methods without
breaking intraclass method calls. For example:

class Mapping
    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable
            self.items_list.append(item)

    __update = update   # private copy of original update() method

class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

The above example would work even if were to introduce a
identifier since it is replaced with in the
class and in the
class respectively.

Note that the mangling rules are designed mostly to avoid accidents; it still is
possible to access or modify a variable that is considered private. This can
even be useful in special circumstances, such as in the debugger.

Источники

Методы класса: альтернативные инициализаторы

Методы класса представляют альтернативные способы создания экземпляров классов. Чтобы проиллюстрировать это, давайте посмотрим на пример.

Давайте предположим , что мы имеем относительно простой класс:

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

Однако с этим битом кода связаны две основные проблемы:

  1. Параметры и теперь вводит в заблуждение, так как вы можете ввести полное имя для . Кроме того, если есть больше падежей и / или больше параметров, которые обладают такой гибкостью, ветвление if / elif / else может быстро раздражать.

Введите методы класса. Вместо того , чтобы иметь один инициализатор, мы создадим отдельный инициализатору, называемый , и украсить его с (встроенный) декоратора.

Обратите внимание на вместо в качестве первого аргумента. Методы класса применяется к общему классу, не является экземпляром данного класса (что обычно обозначает)

Так что , если является наш класс, то возвращается значение из метода класса является , который использует «s создать экземпляр класса. В частности, если мы должны были сделать подкласс из , то будет работать в классе , а также.

Для того, чтобы показать , что это работает , как и ожидалось, давайте создавать экземпляры в более чем одним способом , без разветвлений в :

Другие ссылки:

  • https://codecamp.ru/questions/12179271/python-classmethod-and-staticmethod-for-beginner
  • https://docs.python.org/2/library/functions.html#classmethod
  • https://docs.python.org/3.5/library/functions.html#classmethod

GPON от МГТС версия 2016 — 2018 года: 16 комментариев

§2.2 Книги для контент-менеджера и копирайтера

9.5. Наследование

Несомненно свойство языка не имела бы право называться «классом» без поддержки наследования. Синтаксис для определения производного класса выглядит следующим образом:

Имя должны быть определено в пределах области видимости производного класса. На месте имени базового класса также допускается иные произвольные выражения. Это может быть использовано, например, когда базовый класс определен в другом модуле:

Выполнение определения производного класса протекает так же, как для базового класса. Когда объект класса создается, базовый класс запоминается. Это используется для выяснения ссылок на атрибуты: если запрошенный атрибут не найден в классе, поиск продолжается в базовом классе. Это правило применяется рекурсивно, если сам базовый класс является производным от другого класса.

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

Производные классы могут переопределить методы их базовых классов. Потому как методы не имеют особых привилегий при вызове других методов того же объекта, так метод базового класса, что вызывает другой метод, определенный в том же базовом классе, может в конечном итоге вызвать метод производного класса, который переопределяет его. (Для программистов на C++: все методы в Python фактически являются .)

Переопределенный метод в производном классе на самом деле может потребоваться расширить, а не просто заменить метод базового класса с тем же именем. Существует простой способ вызвать метод базового класса напрямую: просто напишите . Это также иногда полезно для клиентов. (Заметим, что это работает только если базовый класс доступен как в глобальной области видимости.)

У Python есть две встроенные функции, которые работают с наследованием:

  • Используйте isinstance(), чтобы проверить тип экземпляра: вернет , если только является int или некоторый класс, производный от int.
  • Используйте issubclass() для проверки наследования классов: есть , поскольку bool — это подкласс int. Однако, ложно, так как float не является подклассом int.

9.5.1. Множественное наследование

Python также поддерживает форму множественного наследования. Определение класса с несколькими базовыми классами выглядит следующим образом:

Для большинства целей, в простейших случаях, вы можете думать о поиске атрибутов, унаследованных от родительского класса, как сначала в глубину, затем слева-направо, не ищет дважды в том же классе, где есть совпадение в иерархии. Таким образом, если атрибут не найден в , он ищется в , затем (рекурсивно) в базовых классах , и если не был найден там, поиск будет продолжен в и так далее.

На самом деле все немного сложнее; порядок выбора метода динамически изменяется для поддержки совместных вызовов к . Такой подход известен в некоторых других языках с множественным наследованием как «вызов следующего метода» и является более мощным, чем вызов super, присутствующего в языках с одиночным наследованием.

Динамическое следование необходимо потому, что все случаи множественного наследования обладают одним или несколькими взаимосвязанными отношениями (где по крайней мере один из родительских классов может быть доступен через множество путей от самого нижнего класса). Например, все классы унаследованы от , так что любой случай множественного наследования обеспечивает более одного пути, чтобы добраться до . Чтобы сохранить базовые классы от доступа более одного раза, динамический алгоритм делает линейным порядок поиска таким образом, чтобы сохранить порядок слева-направо, указанный в каждом классе, который вызывает каждого родителя только один раз (это означает, что от класса можно создать подкласс не затрагивая порядок приоритетов его родителей). Взятые вместе эти свойства делают возможным создание надежных и расширяемых классов с множественным наследованием. Более подробно см. python.org/download/releases/2.3/mro/.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector