В чем отличие свойств и полей python
Перейти к содержимому

В чем отличие свойств и полей python

  • автор:

Свойства — Python: Введение в ООП

Что нам делать, если мы захотим иметь возможность получить еще и полное имя (имя+фамилию) объекта?

Мы могли бы в инициализаторе добавить строчку self.full_name = name + ‘ ‘ + surname . Но если впоследствии имя или фамилия поменяются, полное имя устареет, и его нужно будет не забыть поменять.

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

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

Добавим в класс свойство и посмотрим на его использование:

class Person: def __init__(self, name, surname): self.name = name self.surname = surname @property def full_name(self): return self.name + ' ' + self.surname tom = Person('Thomas', 'Smith') tom.full_name # 'Thomas Smith' 

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

tom.name = 'Alice' tom.full_name # 'Alice Smith' 

Как вы можете видеть, свойство объявляется как метод без параметров (кроме self , естественно), декорированный с помощью property . Такой метод, возвращающий динамически вычисляемое значение, называется геттером (getter).

Сеттер

Атрибутам пространства имен можно присваивать значения, но что делать, если атрибут — свойство? Если вы попытаетесь присвоить значение свойству, у которого один лишь getter, вы получите ошибку «AttributeError: can’t set attribute».

Чтобы иметь возможность присвоить значение свойству, нужно использовать сеттер (setter). Сеттер — это тоже метод, который принимает новое значение для атрибута и как-то его обрабатывает. Чтобы метод стал сеттером, его тоже нужно соответствующим образом декорировать. Если у вас уже есть геттер, вы можете сделать так:

class Person: def __init__(self, name, surname): self.name = name self.surname = surname @property def full_name(self): return self.name + ' ' + self.surname # сеттер для свойства full_name @full_name.setter def full_name(self, new): self.name, self.surname = new.split(' ') 

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

tom = Person('Thomas', 'Smith') tom.full_name # 'Thomas Smith' tom.full_name = 'Alice Cooper' tom.name # 'Alice' tom.surname # 'Cooper' 

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

Делитер

Кроме геттеров и сеттеров, в Python существует еще один важный метод для управления атрибутами классов — это делитеры (deleter). Этот метод предоставляет дополнительный уровень контроля над тем, как атрибуты класса удаляются.

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

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

class Person: def __init__(self, name, surname): self.name = name self.surname = surname @property def full_name(self): return self.name + ' ' + self.surname @full_name.setter def full_name(self, new): self.name, self.surname = new.split(' ') @full_name.deleter def full_name(self): print("Удаляем имя и фамилию!") self.name = None self.surname = None 

Теперь при удалении свойства full_name будет вызываться наш делитер, который устанавливает свойствам name и surname значение None , тем самым «удаляя» эти значения. Пример:

tom = Person('Thomas', 'Smith') tom.full_name # 'Thomas Smith' del tom.full_name # Удаляем имя и фамилию tom.name # None tom.surname # None 

Использование делитера особенно полезно в случаях, когда необходимо управлять удалением важных или связанных данных, или выполнять освобождение ресурсов (как, например, закрытие файлов или сетевых соединений) в момент уничтожения объекта или его свойств.

Декоратор property

Если посмотреть в документацию к декоратору property , то можно увидеть такую сигнатуру:

property(fget=None, fset=None, fdel=None, doc=None) 

Первые три аргумента позволяют задать getter, setter и deleter, а аргумент doc позволяет указать docstring. В такой форме property удобно использовать, когда вы уже имеете готовые функции, которые хотите просто «упаковать» в свойство:

def get_full_name(self): . def set_full_name(self, new): . class Person: . full_name = property( fget=get_full_name, fset=set_full_name, doc='A full name of person' ) 

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов

Наши выпускники работают в компаниях:

ООП: переменные класса, атрибуты и свойства

Class variables, attributes, and properties

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

Понимание Переменных, Атрибутов и Свойств

  • Переменные: Переменная — это имя, которое ссылается на значение. Переменные могут содержать объект любого типа.
  • Атрибуты: Атрибут — это значение, связанное с объектом. Доступ к атрибутам можно получить с помощью точечной нотации.
  • Свойства: Свойство — это способ определить метод как атрибут. Свойства создаются с использованием декоратора @property .

Переменные Класса в Python

В Python переменные класса — мощный способ делиться данными между всеми экземплярами класса. Давайте рассмотрим, как создавать, доступать и изменять переменные класса.

Создание Переменной Класса

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

class Team: # Creating a class variable team_name = "Python Developers" 

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

Доступ к Переменной Класса в Python

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

class Team: team_name = "Python Developers" # Accessing the class variable using the class name print("Team Name:", Team.team_name) # Output: Team Name: Python Developers 

В этом примере переменная класса the team_name доступна с использованием имени класса, Team.team_name .

Изменение Значения Переменной Класса

Изменить значение переменной класса довольно просто. Вы используете имя класса для ссылки на переменную и присваиваете ей новое значение.

class Team: team_name = "Python Developers" # Modifying the value of the class variable Team.team_name = "Advanced Python Developers" # Accessing the modified value print("Team Name:", Team.team_name) # Output: Team Name: Advanced Python Developers 

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

Переменные Экземпляра в Python

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

Создание Переменной Экземпляра

Чтобы создать переменную экземпляра в Python, вы можете определить ее внутри метода конструктора __init__ . Конструктор — это специальный метод, который вызывается при создании объекта класса. Внутри конструктора вы можете инициализировать переменные экземпляра для объекта.

class Person: def __init__(self, name, age): # Creating instance variables self.name = name self.age = age # Creating an instance of the Person class person = Person("Alice", 30) 

В этом примере мы определяем две переменные экземпляра, name и age , в методе __init__ класса Person . Эти переменные уникальны для каждого объекта person , созданного из класса.

Присваивание Значений Переменной Экземпляра

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

class Person: def __init__(self, name, age): self.name = name self.age = age # Creating an instance of the Person class person = Person("Alice", 30) # Modifying the values of instance variables person.name = "Bob" person.age = 35 

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

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

Атрибуты и Свойства Классов в Python

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

Определение Атрибутов Класса

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

class Animal: # Defining a class attribute kingdom = "Animalia" 

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

Использование Свойств

Свойства в Python — это специальные методы, которые действуют как атрибуты. Они создаются с использованием декоратора @property для определения метода как атрибута, обеспечивая дополнительный функционал, такой как геттеры, сеттеры и удалители.

class Circle: def __init__(self, radius): self._radius = radius @property def radius(self): return self._radius @radius.setter def radius(self, value): if value > 0: self._radius = value # Creating an instance of the Circle class circle = Circle(5) # Accessing and modifying attributes using properties print("Radius:", circle.radius) # Output: Radius: 5 circle.radius = 7 print("Modified Radius:", circle.radius) # Output: Modified Radius: 7 

В этом примере мы используем свойства для контроля доступа к атрибуту radius . Декоратор @property определяет метод getter, а @radius.setter определяет метод setter.

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

Приватные Переменные в Классах Python

В Python приватные переменные — это атрибуты или свойства, которые предназначены для использования только внутри класса и не должны быть доступны или модифицированы снаружи класса. Они обозначаются добавлением двойного подчеркивания __ перед именем переменной или свойства.

Определение Приватных Переменных

Чтобы определить приватную переменную в Python, вы можете использовать префикс с двойным подчеркиванием __ перед именем переменной. Это указывает на то, что переменная не должна быть доступна или модифицирована снаружи класса.

class MyClass: def __init__(self, public_value, private_value): self.public_value = public_value self.__private_value = private_value # Creating an instance of the MyClass class obj1 = MyClass(5, 10) 

В этом примере мы определяем приватную переменную __private_value внутри класса MyClass . Она предназначена для использования только внутри класса.

Доступ к Приватным Переменным

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

class MyClass: def __init__(self, public_value, private_value): self.public_value = public_value self.__private_value = private_value def get_private_value(self): return self.__private_value # Creating an instance of the MyClass class obj1 = MyClass(5, 10) # Accessing private variable using a class method private_value = obj1.get_private_value() 

В этом примере мы создаём метод get_private_value внутри класса для доступа к приватной переменной __private_value .

Приватные переменные ценны для инкапсуляции данных и обеспечения их защиты от непреднамеренного изменения из внешнего кода. Они помогают поддерживать целостность вашего класса и его данных.

Глобальные Переменные Python

В Python глобальные переменные — это переменные, которые могут быть доступны из любой части программы, будь то внутри функции, класса или любого другого контекста. Глобальные переменные определяются вне любой функции или класса, делая их доступными везде.

Определение Глобальных Переменных

Чтобы определить глобальную переменную в Python, вы просто объявляете её вне любой функции или класса. Это позволяет переменной быть доступной и изменяемой из любого места в коде.

global_var = 10 

В этом примере мы определяем глобальную переменную global_var со значением 10 . Эту переменную можно получить доступ и изменить из любого места программы.

Использование Глобальных Переменных

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

global_var = 10 class MyClass: def __init__(self): self.instance_var = global_var # Creating an instance of the MyClass class obj = MyClass() # Accessing the global variable from within the class print(obj.instance_var) # Output: 10 

В этом примере глобальная переменная global_var доступна и присваивается к переменной экземпляра внутри класса MyClass .

Изменение Глобальных Переменных

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

global_var = 10 def modify_global_variable(): global global_var global_var = 20 # Modifying the global variable modify_global_variable() # Accessing the modified global variable print(global_var) # Output: 20 

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

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

Проверка Наличия Атрибута у Класса

В Python вы можете проверить, имеет ли класс конкретный атрибут, используя встроенную функцию hasattr() . Эта функция позволяет вам определить, имеет ли класс конкретный атрибут, делая ее ценным инструментом для динамической проверки атрибутов.

Использование hasattr()

Чтобы использовать hasattr() , предоставьте объект (в данном случае класс или экземпляр класса) и имя атрибута в качестве аргументов. Функция возвращает булево значение, указывающее на существование атрибута в классе.

class MyClass: var = 10 obj = MyClass() # Checking if the class has an attribute if hasattr(obj, 'var'): print('Object has var attribute') else: print('Object does not have var attribute') 

В этом примере мы определяем класс MyClass с атрибутом var . Используя hasattr() , мы проверяем, имеет ли экземпляр obj атрибут var .

Обработка Существования Атрибута

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

class MyClass: var = 10 obj = MyClass() # Checking if the class has an attribute if hasattr(obj, 'var'): # Accessing the attribute value = getattr(obj, 'var') print(f'Attribute var exists with value: ') else: print('Object does not have var attribute') 

В этом расширенном примере, если hasattr() подтверждает, что атрибут var существует, мы используем getattr() для безопасного доступа к значению атрибута.

Использование hasattr() может повысить надежность вашего кода и улучшить обработку ошибок при работе с динамическими атрибутами в классах Python. Это полезная техника для проверки атрибутов.

Печать и Извлечение Атрибутов Класса в Python

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

Использование dir()

Функция dir() в Python возвращает список всех атрибутов объекта. Хотя это включает в себя внутренние атрибуты и методы, вы можете отфильтровать их, чтобы получить список атрибутов класса.

class MyClass: var1 = 10 var2 = 'hello' var3 = True # List of all attributes (including internal ones) attributes = dir(MyClass) # Filtering and printing only class attributes for attr in attributes: if not attr.startswith('__'): value = getattr(MyClass, attr) print(f': ') 

В этом примере мы используем dir(MyClass) , чтобы получить список всех атрибутов. Затем мы фильтруем внутренние атрибуты и выводим имена и значения атрибутов класса.

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

Использование vars()

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

class MyClass: var1 = 10 var2 = 'hello' var3 = True # Using vars() to retrieve class attributes class_attrs = vars(MyClass) for attr, value in class_attrs.items(): print(f': ') 

В этом примере мы используем vars(MyClass) , чтобы получить атрибуты класса и вывести их имена и значения.

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

Приватные Переменные Класса

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

Создание Приватных Переменных Класса

Чтобы создать приватную переменную класса в Python, добавьте двойное подчёркивание __ перед именем переменной внутри класса. Эта конвенция указывает на то, что к переменной не следует обращаться напрямую извне класса.

class MyClass: __private_var = 10 

В этом примере мы создаем закрытую переменную класса __private_var .

Доступ к Закрытым Переменным Класса

Попытка доступа к закрытой переменной класса извне класса приведет к AttributeError. Закрытые переменные предназначены для использования только внутри класса для инкапсуляции данных.

class MyClass: __private_var = 10 def get_private_var(self): return self.__private_var obj = MyClass() # Accessing the private variable using a class method print(obj.get_private_var()) # Output: 10 

В этом примере мы определяем метод get_private_var внутри класса для доступа к приватной переменной __private_var .

Роль Приватных Переменных Класса

Приватные переменные играют ключевую роль в инкапсуляции данных и поддержании целостности внутреннего состояния класса. Они предотвращают неумышленное изменение или доступ к чувствительным данным внутри класса со стороны внешнего кода.

class BankAccount: def __init__(self, account_number, balance): self.__account_number = account_number self.__balance = balance def withdraw(self, amount): if amount > 0 and amount  

В этом примере класс BankAccount определяет приватные переменные __account_number и __balance для инкапсуляции конфиденциальных данных счета. Эти приватные переменные доступны и изменяются с помощью методов класса, таких как withdraw .

Приватные переменные являются фундаментальным аспектом инкапсуляции данных и контроля доступа в объектно-ориентированном программировании, обеспечивая сохранение целостности данных класса.

Глобальные Переменные Класса в Python

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

Создание Глобальных Переменных Класса

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

global_var = 10 

В этом примере мы создаём глобальную переменную класса global_var .

Доступ к Глобальным Переменным Класса

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

global_var = 10 class MyClass: def __init__(self): self.instance_var = global_var obj = MyClass() # Accessing the class global variable from within a class print(obj.instance_var) # Output: 10 

В этом примере мы определяем глобальную переменную класса global_var и получаем к ней доступ изнутри класса MyClass .

Роль Глобальных Переменных Класса

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

user_name = "John" class User: def __init__(self, name): self.name = name # Accessing the class global variable from within a class new_user = User(user_name) print(new_user.name) # Output: John 

В этом примере глобальная переменная класса user_name хранит имя пользователя и доступна внутри класса User .

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

Публичные, Защищенные и Приватные Атрибуты и Свойства в Python

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

Публичные Атрибуты и Свойства

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

class Person: def __init__(self, name): self.name = name # Public attribute class Circle: def __init__(self, radius): self.radius = radius # Public attribute class MyClass: def __init__(self, value): self.value = value # Public attribute # Accessing public attributes and properties person = Person("Alice") print(person.name) # Output: Alice circle = Circle(5) print(circle.radius) # Output: 5 obj = MyClass(42) print(obj.value) # Output: 42 

В приведенных выше примерах атрибуты, такие как name , radius и value , являются публичными и могут быть доступны извне классов.

Защищённые Атрибуты и Свойства

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

class Employee: def __init__(self, name, _salary): self.name = name self._salary = _salary # Protected attribute def _get_salary(self): return self._salary # Protected method # Accessing protected attributes and methods employee = Employee("Bob", 50000) print(employee._salary) # Output: 50000 print(employee._get_salary()) # Output: 50000 

В классе Employee , _salary отмечено как защищенный атрибут. Хотя оно доступно, одиночное подчеркивание указывает на то, что к нему не следует обращаться напрямую.

Приватные Атрибуты и Свойства

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

class BankAccount: def __init__(self, account_number, __balance): self.account_number = account_number self.__balance = __balance # Private attribute def __get_balance(self): return self.__balance # Private method # Attempting to access private attributes and methods account = BankAccount("123456789", 1000) # Results in an AttributeError # print(account.__balance) # print(account.__get_balance()) 

В классе BankAccount , __balance является приватным атрибутом, а __get_balance - приватным методом. Попытка доступа к ним извне класса приводит к AttributeError.

Пишите вместе с нами!

Вы можете внести свой вклад в статьи о Python на GitHub: создайте Fork, внесите правки и отправьте нам Pull request.

В чем отличие свойств и полей python

Чем отличается свойство от поля? Вопрос по ООП. Нет, это не синонимы.

Разница в том, что свойство — это само поле плюс два метода (установить и взять значение в поле). Вы может сделать свойство, а потом откройте IL код и посмотрите, и увидите поле типа private и два метода get_ и set_. А разные варианты синтаксической записи — это просто синтаксический сахар самого языка.

Остальные ответы

Фактически одно и то же, но разный подтекст. Поле это глобальная переменная класса, а свойство это атрибут объекта (поле класса), к которому обращаешься через геттеры и сеттеры. В c# реализация свойств отличается от полей. Но в целом это одно и то же.

Zazi DooyiloУченик (95) 6 лет назад

То есть, свойство — это в описание класса, а поле это уже конкретное реализация свойства, описанного в классе?

Максим Искусственный Интеллект (195239) Я бы сказал, что что-то типа этого. То есть свойство это уже ближе к вопросу о том, для чего применяется поле. Свойство объекта реализуется полями (но в c# для полей есть свой механизм). Хотя, вероятно, не любое поле можно назвать свойством.

Настройка свойств поля

Aspose.Words предоставляет возможность программного взаимодействия с различными свойствами полей. В этой статье мы рассмотрим пару примеров, чтобы вы поняли основной принцип работы со свойствами полей. Полный список свойств для каждого типа поля вы можете увидеть в соответствующем классе в файле Модуль полей.

Обновление свойств поля

Иногда пользователям необходимо изменить значение свойства поля. Например, обновите свойство author_name поля AUTHOR или измените свойство field_name поля MERGEFIELD .

В следующем примере кода показано, как переименовать поля слияния в документе Word:

Результат отображения поля

Aspose.Words предоставляет свойство для получения результата поля для полей, у которых нет узла-разделителя полей. Мы называем это “фальшивым результатом” или отображаемым результатом; MS Word отображает его в документе, вычисляя значение поля на лету, но в модели документа такого значения нет.

В следующем примере кода показано использование свойства display_result:

В чем отличие в присвоении атрибута класса в конструкторе и нет

В первом случае переменная a является атрибутом класса, и доступна и как W.a и как:

w = W() w.a 

Во втором же случае переменная создаётся в момент инициализации объекта, а значит принадлежит объекту и доступна только как:

w = W() w.a 

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

Из фишек — можно например изменять значение сразу для всех объектов класса:

class A(): b = 1 a = A() b = A() a.b # результат 1 b.b # результат 1 # Когда меняем значение для члена класса A.b = 5 # Оно изменяется для всех объектов класса b.b # результат 5 a.b # результат 5 

Отслеживать
ответ дан 19 окт 2015 в 7:40
Алексей Стародубцев Алексей Стародубцев
649 1 1 золотой знак 4 4 серебряных знака 15 15 бронзовых знаков
Очень подробно! Все яснопонятно! Спасибо вам!
19 окт 2015 в 17:02

Немного расширю ваш пример:

class T: def __init__(self): self.a = 10 self.b = [1, 2, 3] c = 20 d = [15] 

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

Таким образом, мы имеем класс T , у которого есть три атрибута (не считая автоматические): c , d и __init__ (объявление функции тоже создаёт локальную переменную!):

>>> T.__dict__

Когда мы создаём экземпляр класса, выполняется метод __init__ класса, в который передаётся заготовка создаваемого экземпляра. Таким образом, в строке

t = T() 

Мы получаем экземпляр t класса T , который имеет два атрибута: a и b :

>>> t.__dict__

Что происходит дальше, когда мы пытаемся получить доступ к атрибутам?

Например, имеем три строки кода:

print(t.a) # 10 print(t.c) # 20 print(T.c) # 20 # print(T.a) # error: у класса нет такого атрибута 

В первой строке интерпретатор посмотрит в атрибуты переменной t , найдёт среди них имя a и вернёт его значение.

Во второй строке происходит следующее. Интерпретатор не может найти атрибут c среди атрибутов объекта t , поэтому обращается к объекту класса, экземпляром которого является этот объект. У класса уже имеется нужный атрибут, поэтому на этом поиск завершается, возвращается значение 20 .

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

Четвёртая строка выполнится с ошибкой, потому что атрибуты экземпляров не распространяются на классы.

Какие за счёт этого возникают эффекты?

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

t1 = T() t2 = T() print(t1.d, t2.d) # [15] [15] # изменим атрибут класса, пользуясь тем, что список является изменяемым типом t1.d.append(16) print(t1.d, t2.d) # [15, 16] [15, 16] # создадим новый атрибут уровня класса T.x = "Hello!" print(t1.x, t2.x) # Hello! Hello! 

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

t1.d = [-1, -2] print(t1.d, t2.d, T.d) # [-1, -2] [15, 16] [15, 16] 

Т.е. в строке t1.d = [-1, -2] не изменяется значение атрибута класса, а создаётся новый атрибут у объекта t1 . Поэтому изменить значение неизменяемого атрибута класса через экземпляр этого класса не получится. Если требуется подобная операция, стоит изменять атрибут класса.

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

С учётом этих замечаний обычно рекомендуется создавать все атрибуты в методе __init__ класса, а не в самом классе.

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

Чем отличаются классы Python от классов C++.

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

Очевидные различия

Некоторые отличия сразу бросаются в глаза. Нет, речь не о синтаксисе, очевидно, что по синтаксису Python сильно отличается от C++.

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

Одно из важнейших отличий заключается в составе аргументов любого метода класса. В качестве первого аргумента указывается экземпляр класса, для которого вызван этот метод. Имя значения не имеет и может быть различным в разных методах, но традиционно используется » self «. Отсюда в частности следует, что любой метод должен иметь хотя бы один параметр. В первом приближении можно считать, что этот параметр является аналогом » this » в C++.

Отличия self от this
self this
Произвольный идентификатор Зарезервированное слово
Указывается явно в списке параметров метода Не указывается в списке параметров
Является ссылкой Является указателем

Второе отличие напрямую связано с первым. При использовании внутри метода, атрибуты не связываются автоматически с объектом, для которого вызван метод. Необходимо явно указать, к какому объекту относится атрибут, приписав к нему слева » self. » (здесь и далее под » self » мы будем подразумевать первый параметр метода, если не указано другое). Приведём небольшой пример:
Код на C++

class ExampleC < int a; public: void set_a(int sa) void set_abs_a(int sa) < if (sa<0) set_a(-sa); else set_a(sa); >>
class ExamplePy: def set_a(self, sa): self.a = sa def set_abs_a(self, sa): if sa 

Вероятно, кто-то скажет, что последняя строка функции set_a в питоновском примере ошибочна — атрибут a не описан в определении класса ExamplePy ! Однако, на самом деле это не ошибка, а проявление принципиального отличия переменных (не только атрибутов объектов) Python от переменных C++. В то время, как в C++ с именем переменной на этапе компиляции связывается область памяти и тип, переменные Python являются элементами специального словаря (вернее, различных словарей), где имя переменной является ключом, который связан со значением переменной. Выполняя оператор присваивания, Python связывает значение выражения с именем переменной. Если с этим именем уже было связано какое-либо значение, связь со старым значением разрывается.

Как это всё работает

А работает это совсем не так, как в C++. И основное отличие состоит в том, что если в C++ описание класса это объявление, то в Python это исполняемый код. Эффектно проиллюстрировать это можно, поместив определение класса в ветвь условного оператора:

if 1: class Example: def f(self): print "aaa" else: class Example: def f(self): print "bbb" e = Example() e.f()

Попробуйте запустить эту программу сначала в приведённом здесь виде, а затем, заменив в условии 1 на 0.

Разберёмся, что здесь происходит. В результате выполнения определения класса создаётся объект с именем " Example ". Создание объекта e (экземпляра класса Example ) происходит путём обращения к объекту Example . То есть, класс в Python является объектом! Не удивляйтесь, в Python объектом является практически всё.

В C++ мы имели дело с полями и методами. Поля могли быть обычными (принадлежащими объекту) и статическими (принадлежащими классу). Методы так же были статические и обычные, последние разделялись на невиртуальные и виртуальные. В Python мы имеем дело с атрибутами класса и атрибутами объекта. Рассмотрим пример:

class my_class: "Some class" a = 10 def __init__(self, arg): self.b = arg def f(self): "Some method" print my_class.a, self.a, self.b c1 = my_class(1) c2 = my_class(2) c1.f() # 10 10 1 c2.f() # 10 10 2 c2.a = 20 c1.f() # 10 10 1 c2.f() # 10 20 1 my_class.a = 30 c1.f() # 30 30 1 c2.f() # 30 20 1

Здесь мы определили класс my_class с атрибутами a , __init__ и f . На самом деле класс содержит ещё два атрибута: __module__ и __doc__ . Первый содержит название модуля, в котором определён класс ("__main__", если это основная программа), второй содержит строку с описанием модуля (в данном случае "Some class"). Атрибут a можно сравнить со статическим полем класса — он доступен любому объекту класса, но не принадлежит ни одному из них. Попытка присвоить новое значение атрибуту объекта с тем же именем не повлияет на атрибут класса, а приведёт к созданию атрибута объекта, который "заслонит" для этого объекта атрибут класса. Доступ к атрибуту класса можно получить с помощью конструкции имя_класса.имя_атрибута , в данном примере: my_class.a . Не забываем, что класс — это объект!

Атрибуты __init__ и f это методы класса, причём __init__ неявно вызывается при создании экземпляра класса, то есть играет роль конструктора. Все методы в Python виртуальные. В методе __init__ мы присваиваем значение атрибуту b , который, в отличии от a , уже является атрибутом объекта, а не класса. b уникален для каждого объекта и не имеет никакого отношения к классу.

Теперь рассмотрим как работает наш пример. Сначала создаётся класс с двумя методами и атрибутом a , равным 10. Затем создаются два экземпляра класса. При создании им передаётся параметр со значением 1 и 2 соответственно, который присваивается атрибуту b соответствующего класса. Вызов метода f для каждого класса даёт:

10 10 1 10 10 2

После выполнения строки c2.a = 20 в экземпляре c2 появляется новый атрибут a , который заслоняет одноимённый атрибут класса. Однако последний по прежнему доступен через с помощью конструкции my_class.a . Теперь вызов метода f даёт:

10 10 1 10 20 2

Наконец, мы меняем значение атрибута класса строкой my_class.a = 30 . Это изменение затрагивает всё экземпляры класса и на выходе мы получаем:

30 30 1 30 20 2

Методы __init__ и f являются, так же как и a , атрибутами класса. Их отличие лишь в том, что это функции. Метод можно описать и вне класса, тогда в определении класса он не будет отличаться от атрибутов-данных.

def func(self, mult): print self.a*mult, self.b*mult class my_class: a = 5 g = func def __init__(self, b): self.b = b def f(self): print self.a, self.b c = my_class(3) c.f() c.g(10) my_class.h = lambda self,format:format % (self.a, self.b) print c.h('%d---%d') c1 = my_class(8) print c1.h('a=%d, b=%d') c1.h1 = lambda x: x*2 print c1.h1(2)

Здесь мы определили функцию от двух аргументов и присвоили ссылку на неё атрибуту класса my_class с именем g . По внешнему виду описание атрибута g ничем не отличается от a , но g это метод, так как func является функцией. Как и следовало ожидать, метод g требует один аргумент — второй аргумент func .

Затем мы делаем и вовсе невообразимую вещь — присваиваем новому атрибуту объекта my_class ссылку на функцию. В результате создаётся ещё один метод, причём доступный как для новых, так и для ранее созданных экземпляров my_class ! Впрочем, сильно удивляться не стоит. В насквозь динамическом Python экземпляр класса содержит ссылку на породивший его класс. Каждый раз при вызове метода интерпретатор ищет в классе (и, если не находит, то ищет в классах, от которых этот класс унаследован) атрибут-функцию с указанным именем. После этого он вызывает её, передавая в качестве первого аргумента ссылку на экземпляр класса, а в качестве остальных — явно указанные при вызове метода аргументы.

Интересно, что если мы присвоим ссылку на функцию атрибуту объекта-экземпляра класса (а не атрибуту класса), то никакого метода мы не получим. Будет просто ещё одна ссылка на функцию. При вызове функции с помощью этой ссылки ей не передаётся никаких неявных аргументов и она ничего не будет знать об объекте, атрибут которого на неё ссылается.

Наследование: можно, но необязательно

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

class base: "Базовый класс" def __init__(self): "Инициализируем список" self.lst = [] def add(self, item): "Добавляем элемент" self.lst.append(item) def print_list(self): "Выводим список" for item in self.lst: self.print_item(item) class child1(base): "Производный класс" def __init__(self, first): "Переопределяем __init__" base.__init__(self) # Вызываем конструктор базового класса self.lst.append(first) # Добавляем свой код def print_item(self, item): "Вывод элемента" print item class child2(base): "Производный класс" def print_item(self, item): "Вывод элемента" print "item: %s" % item def print_list(self): "Переопределяем print_list" print "lenght of list = %d" % len(self.lst) # Добавляем свой код base.print_list(self) # Вызываем print_list из базового класса c1 = child1('head') c1.add(1) c1.add([5,'qwerty']) c1.print_list() print '------------' c2 = child2() c2.add(2) c2.add([10,'item']) c2.print_list()

Сразу можно увидеть, что в методе print_list класса base вызывается не определённый в этом классе метод print_item . Это не ошибка, наличие этого метода будет необходимо только при выполнении print_list . Однако, если создать экземпляр класса base и вызвать для него print_list , возникнет исключение. Таким образом, класс base — абстрактный, он предназначен только для наследования, но не для создания экземпляров. При этом, в отличии от C++, нет необходимости описывать в классе base пустую функцию print_item . Встретив вызов self.print_item(item) , интерпретатор будет искать print_item в словаре класса, для объекта которого вызван print_list , а не найдя — в словаре(словарях) базового класса(классов). Если поиск завершится успехом, будет вызван найденный метод, иначе будет сгенерировано исключение. Разумеется, найденный метод должен принимать тот набор параметров, который передаётся при его вызове, иначе, опять же, будет сгенерировано исключение. Поскольку поиск производится по имени во время выполнения программы (позднее связывание), то все методы автоматически являются виртуальными.

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

Класс child2 переопределяет метод print_list , из которого явным образом вызывается метод базового класса, который, в свою очередь, вызывает метод print_item класса child2 . Обычный полиморфизм в действии.

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

В Python, так же как и в C++, доступно множественное наследование. Рассмотрим пример, где один класс наследуется от двух других, а те, в свою очередь, имеют общего предка. Таким образом, один класс дважды косвенно наследуется другим. В C++ в этом случае дважды наследуемый класс может включаться в состав "внука" в одном или в двух экземплярах, в зависимости от того, наследуется он прямыми потомками как виртуальный или не виртуальный. В Python все классы наследуются как виртуальные и входят в состав потомков в единственном экземпляре.

В нашем примере класс lister (самый старший в иерархии) может добавлять элементы в атрибут-список l (метод add ) и поэлементно передавать весь список не определённому в нём методу out_item (метод out_all ).

Класс filer может выводить данные из списка в файл, который открывается в конструкторе и закрывается в деструкторе.

Класс formater может выдавать элементы списка в указанном формате. Строка формата задаётся в конструкторе.

Наконец класс list_writer может записывать элементы списка в файл в указанном формате.

class lister: """Класс позволяет добавлять элементы в список и выводить все элементы. Для вывода элементов необходимо определить в производном классе метод out_item. """ def __init__(self): print "lister init:", if hasattr(self, 'l'): print "list already created" else: print "create empty list" self.l = [] def add(self, item): self.l.append(item) def out_all(self): for item in self.l: self.out_item(item) class filer(lister): """Класс позволяет выводить данные в файл. Файл открывается при создании экземпляра класса и закрывается при его удалении. """ def __init__(self, filename): print "filer init" lister.__init__(self) self.f = open(filename, 'w') def out_item(self, item): self.f.write(item) def __del__(self): print "close file" self.f.close() class formater(lister): """Класс позволяет получать данные в заданном формате. """ def __init__(self, format): print "formater init" lister.__init__(self) self.format = format def printf(self, item): return self.format % item def out_item(self, item): print self.printf(item) class list_writer(filer, formater): """Класс позволяет добавлять элементы и записывать всё элементы в указанный файл в заданном формате. """ def __init__(self, filename, format): print "list_writer init" filer.__init__(self, filename) formater.__init__(self, format) def out_item(self, item): filer.out_item(self, self.printf(item)) # Создаём экземпляр. Указываем имя файла и формат(будем работать с целыми числами). lw = list_writer("data.txt", "item: %d\n") # Добавляем элементы. lw.add(1) lw.add(2) lw.add(3) # Записываем элементы в файл. lw.out_all() # При завершении программы файл закрывается.

В конструкторе класса list_writer вызываются конструкторы классов filer и formater , в каждом из которых вызывается конструктор класса lister . В результате, он вызывается дважды. Чтобы отследить вызов конструктора для уже проинициализированного экземпляра, в код конструктора включена проверка на существование атрибута l (функция hasattr). Если атрибут существует, повторная инициализация не производится.

list_writer init filer init lister init: create empty list formater init lister init: list already created close file
Обходимся без наследования

Допустим, нам нужно, в зависимости от ситуации, выводить строки в файл или сформировать из них одну длинную строку. Создадим два класса:

class to_file: "Вывод в файл" def __init__(self, f): self.f = f def write(self, line): self.f.write(line) class to_line: "Формирование длинной строки" def __init__(self): self.buf = "" def write(self, line): self.buf += line def get(self): return self.buf def writer(dest): dest.write("Первая строка\n") dest.write("Вторая строка\n") f = open("data.txt", 'w') tf = to_file(f) writer(tf) f.close() tl = to_line() writer(tl) print tl.get()

Классы to_file и to_line совершенно не связаны друг с другом, ни один из них не наследуется прямо или косвенно от другого и они не имеют общего предка (точнее, ни один из них вообще не имеет предков). Однако, функция writer одинаково успешно работает с экземплярами обоих классов. Для этого достаточно, чтобы каждый класс содержал используемый функцией writer метод write , который в каждом из классов принимает одинаковый набор аргументов.

В приведённом примере метод write встречается не только у экземпляров классов to_file и to_line , но и у объекта f , возвращённого функцией open (ссылка на него записывается в self.f в конструктора класса to_file и используется в методе write ). А нельзя ли обойтись без класса to_file и передать в функцию writer сам объект f ? Никаких проблем:

f = open("data.txt", 'w') writer(f) f.close()

Серьёзным (в отличии от "игрушечного", приведённого здесь) примером является стандартный модуль Python StringIO , реализующий файловый интерфейс для строк (программисты на C++ сразу вспомнят строковые потоки). Определённый в модуле класс StringIO не имеет предков, тем не менее, его экземпляры можно использовать везде, где можно использовать файловые объекты (возвращённые функцией open ).

Никакой защиты

Напоследок я, возможно, огорчу C++ программистов, решивших изучать Python. В этом языке отсутствует возможность запретить доступ к атрибутам класса или экземпляра. Никаких модификаторов "private", "protected" и "public" здесь не предусмотрено. Действуют два соглашения:

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

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

class counter: def __init__(self): self.__a = 0 def __up(self): self.__a +=1 def get_a(self): self.__up() return self.__a c = counter() print c.get_a() # 1 print c.get_a() # 2 #c.__up() #print c.__a print c._counter__a # 2 c._counter__up() print c._counter__a # 3

Если раскомментировать любую из двух закомментированных строк, возникнет исключение AttributeError .

В версии 2.2 появилась возможность контролировать доступ к атрибутам с помощью слотов

Заключение

При написании статьи подразумевалось использование версии 2.1 . Большая часть информации остаётся верной и для новых версий (2.2 и 2.3), однако в современных версиях появились новые возможности, в том числе связанные с классами.

Похожие публикации:

  1. Как выровнять текстуру на полигонах substance painter
  2. Как подключить колонки к ноутбуку леново
  3. Как подключить сяоми наушники
  4. Как сделать sidebar html

В чем отличие свойств и полей python

В чем отличие в присвоении атрибута класса в конструкторе и нет

В первом случае переменная a является атрибутом класса, и доступна и как W.a и как:

w = W() w.a 

Во втором же случае переменная создаётся в момент инициализации объекта, а значит принадлежит объекту и доступна только как:

w = W() w.a 

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

Из фишек — можно например изменять значение сразу для всех объектов класса:

class A(): b = 1 a = A() b = A() a.b # результат 1 b.b # результат 1 # Когда меняем значение для члена класса A.b = 5 # Оно изменяется для всех объектов класса b.b # результат 5 a.b # результат 5 

Отслеживать
ответ дан 19 окт 2015 в 7:40
Алексей Стародубцев Алексей Стародубцев
649 1 1 золотой знак 4 4 серебряных знака 15 15 бронзовых знаков
Очень подробно! Все яснопонятно! Спасибо вам!
19 окт 2015 в 17:02

Немного расширю ваш пример:

class T: def __init__(self): self.a = 10 self.b = [1, 2, 3] c = 20 d = [15] 

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

Таким образом, мы имеем класс T , у которого есть три атрибута (не считая автоматические): c , d и __init__ (объявление функции тоже создаёт локальную переменную!):

>>> T.__dict__

Когда мы создаём экземпляр класса, выполняется метод __init__ класса, в который передаётся заготовка создаваемого экземпляра. Таким образом, в строке

t = T() 

Мы получаем экземпляр t класса T , который имеет два атрибута: a и b :

>>> t.__dict__

Что происходит дальше, когда мы пытаемся получить доступ к атрибутам?

Например, имеем три строки кода:

print(t.a) # 10 print(t.c) # 20 print(T.c) # 20 # print(T.a) # error: у класса нет такого атрибута 

В первой строке интерпретатор посмотрит в атрибуты переменной t , найдёт среди них имя a и вернёт его значение.

Во второй строке происходит следующее. Интерпретатор не может найти атрибут c среди атрибутов объекта t , поэтому обращается к объекту класса, экземпляром которого является этот объект. У класса уже имеется нужный атрибут, поэтому на этом поиск завершается, возвращается значение 20 .

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

Четвёртая строка выполнится с ошибкой, потому что атрибуты экземпляров не распространяются на классы.

Какие за счёт этого возникают эффекты?

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

t1 = T() t2 = T() print(t1.d, t2.d) # [15] [15] # изменим атрибут класса, пользуясь тем, что список является изменяемым типом t1.d.append(16) print(t1.d, t2.d) # [15, 16] [15, 16] # создадим новый атрибут уровня класса T.x = "Hello!" print(t1.x, t2.x) # Hello! Hello! 

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

t1.d = [-1, -2] print(t1.d, t2.d, T.d) # [-1, -2] [15, 16] [15, 16] 

Т.е. в строке t1.d = [-1, -2] не изменяется значение атрибута класса, а создаётся новый атрибут у объекта t1 . Поэтому изменить значение неизменяемого атрибута класса через экземпляр этого класса не получится. Если требуется подобная операция, стоит изменять атрибут класса.

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

С учётом этих замечаний обычно рекомендуется создавать все атрибуты в методе __init__ класса, а не в самом классе.

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

NEWOBJ.ru → Введение в ООП с примерами на C# →

Состояние объекта (state) – перечень полей объекта (обычно статический) и текущих значений каждого из этих полей (обычно динамический).

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

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

В настоящей книге мы используем термин поле ( field ), так как оно используется в C#. При этом в других языках программирования для обозначения полей класса могут применяться и другие термины: переменная-член ( member variable, member ), свойство ( property ). Отметим, что термин свойство, в свою очередь, имеет в C# специфическое значение и будет рассмотрен ниже в настоящей главе.

Приведем определение поведения объекта аналогичное определению состояния объекта [3].

Поведение объекта (behavior) — перечень (обычно статический) методов объекта и результатов их вызова (зависят от текущего состояния).

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

Мы оговариваем, что результаты вызова методов зависят от текущего состояния или, по-другому, определяются историей объекта (то есть историей изменения его состояния), так как один и тот же метод в общем случае может возвращать разные результаты в зависимости от состояния.

В предыдущих главах мы уже показывали, что методы имеют доступ ко всем полям объекта независимо от уровня видимости. При этом доступ выполняется через неявно передаваемую в метод переменную this , указывающую на объект, для которого был вызван метод. Если локальная переменная метода имеет такое же имя, как и некоторое поле класса, то эта локальная переменная скрывает ( hide ) поле класса. Мы уже рассматривали эту возможность ране в § 7.

Сокрытие переменных можно назвать разновидностью более общего понятия – перегрузки – использования в одной и той же области видимости одного и того же имени для обозначения разных элементов языка. Если «элемент языка» – метод, то мы имеем дело с перегрузкой методов.

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

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

Мы используем термин метод, так как он используется в C#, но в других языках и, в целом, в объектно-ориентированном программировании как синонимы также используются термины функция (function) и функция-член (member function). Исторически термин функция возник в процедурном программировании и обозначал функцию, не связанную ни с каким классом. В объектно-ориентированных языках чтобы различать функции, не связанные ни с каким классом, и функции класса, последние стали называть другими терминами, например, функциями-членами в C++ и методами в C# и Java, оставив термин функция для функций, объявленных вне классов. При этом во многих объектно-ориентированных языках вовсе нет возможности объявить функцию вне какого-либо класса.

В заключение напомним, что при проектировании класса, как правило, следует избегать открытых полей – для изменения состояния объекта следует использовать методы 24 .

§ 16. Неизменяемость. Изменение программистом значений переменных (состояния объектов) в ходе выполнения программы выглядит вполне естественной практикой. Однако существует множество ситуаций, когда желательно так или иначе ограничивать эту возможность.

Во-первых, некоторые объекты представляют семантически неизменяемые данные, константы. Например, мы не хотели бы иметь возможность изменять переменные, в которых хранится число π, или число e, или коэффициенты перевода единиц измерения, или другие подобные значения. Семантически константами могут быть и более сложные объекты, например, точка-центр координат, объект-прямоугольник с размерами страниц стандартного формата. параметры работы приложения, не допускающие изменения после запуска. Очень часто локальные переменные не должны изменяться после инициализации согласно логике метода. Конечно, можно сказать, что раз эти объекты не должны меняться, то и не следует их менять. Например, в Python это единственная рекомендация для разработчика 25 . Однако во многих других современных языках программирования принята практика ограничений изменения семантически неизменяемых объектов на уровне языка.

Во-вторых, часто желательно ограничить возможность изменения состояния объекта только некоторыми фрагментами кода. В следующем примере будет ли изменено состояние объекта reportData внутри метода Create ?

Report report = reportFactory.Create (reportData); 

Метод Create создает объект-отчет report , используя данные из объекта reportData . Разумно предположить, что метод Create использует данные reportData , но не изменяет их. Однако это не точно: код метода Create вполне может случайно или намеренно (в этом случае речь идет о неудачной архитектуре) изменить какие-то поля объекта reportData .

Приведем еще пример:

ReportDataProvider reportDataProvider = new ReportDataProvider (db); ReportData reportData = reportDataProvider.Select(); 

Здесь класс ReporDataProvider выполняет загрузку данных из базы данных с использованием переданного в конструкторе объекта-подключения db . Как и в предыдущем примере, разумно предположить, что объект reportDataProvider использует объект-подключение db , но не изменяет его. Однако (как и в предыдущем примере), это не точно. Особенно важна задача локализации фрагментов кода, изменяющих состояние объекта, в многопоточных приложениях. Так, если мы используем объект db для нескольких объектов-провайдеров, каждый из которых будет менять свойства подключения, то при параллельном запуске этих провайдеров отследить кто и что изменил значительно сложнее, чем при последовательном.

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

Способы ограничивать изменение состояния объектов сильно отличаются в различных языках программирования. Рассмотрим коротко основные возможности C#: механизм констант ( const ) и механизм readonly -полей классов.

Локальные переменные методов и поля классов значимых типов могут быть помечены как константы ключевым словом const . Такие объекты невозможно изменить во время выполнения приложения. Рассмотрите следующий пример:

public double Area (double r) < const double Pi = 3.14; // Будет скомпилировано как 3.14 * r * r return Pi * r * r; > 

Значения констант должны быть определены на момент компиляции приложения, поэтому мы не можем их использовать со ссылочными переменными – экземплярами классов 26 :

// Ошибка компиляции: // экземпляр класса Point создается на этапе выполнения приложения // в динамической памяти, // переменная ссылочного типа не может быть константой. const Point p = new Point(); 

Для ограничения изменения переменных ссылочного типа в C# используется механизм readonly -полей. Поля классов могут быть помечены как доступные только на чтение ключевым словом readonly (англ. «только чтение») . Это значит, что мы сможем присваивать значения этим полям только в конструкторе класса или непосредственно при инициализации поля. Рассмотрим следующий пример неизменяемого ( immutable ) класса:

class Point < private readonly float x; private readonly float y; public Point (float x, float y) < this.x = x; this.y = y; >> class Circle < private readonly Point center; private readonly float radius; public Circle (Point center, float radius) < this.center = center; this.radius = radius; >> 

Этот пример демонстрирует несколько важных особенностей.

Во-первых, в C# неизменяемость ( immutability ) – это свойство класса, а не объекта: или мы объявляем класс как неизменяемый и все его экземпляры будут неизменяемыми. Или мы объявляем класс как изменяемый и все его экземпляры будут также изменяемыми. У нас нет возможности сделать неизменяемым только некоторые экземпляры этого класса. Для сравнения, отметим, что в C++ такая возможность есть: мы можем отметить ключевым слово const переменную или даже параметр метода и это будет значить, что указанную переменная или параметр метода невозможно изменить, но другие, не отмеченные этим словом экземпляры будут изменяемыми, а объект-параметр будет изменяемым вне соответствующего метода.

Другая особенность, демонстрируемая примером с кругом – класс может быть не полностью неизменяемым. То есть часть полей может быть изменяемая, часть – нет. Полностью неизменяемый объект мы можем сделать только если все вложенные объекты (поля класса) также реализованы как неизменяемые. Так, в классе Circle недостаточно написать readonly Point , нужно чтобы и сам Point был неизменяемым, так как readonly для ссылочного типа обозначает лишь невозможность изменить его значение (ссылку), но не состояние объекта, на который он ссылается.

И последний момент, который мы разберем – особенность копирования неизменяемых объектов. В конструкторе для копирования объекта-центра мы используем копирование ссылки, так как уверены, что переданный объект уже не будет никогда изменен и вполне достаточно иметь всего один экземпляр объекта, на который будут ссылаться множество логически не связанных между собой переменных.

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

Несмотря на широкое использование в ежедневной практике, для объектно-ориентированного программирования неизменяемость – это всегда ограничение, так как исходно переменные в ООП изменяемые ( mutable ). Более того, в теории языков программирования введение неизменяемости для всех объектов – это определение функциональной парадигмы программирования [Мартин 11].

§ 17. Сохраняемость. Иногда требуется сохранить состояние объекта между запусками программы или при перемещении объекта между приложениями. Конечно, строго говоря, при восстановлении сохраненного состояния, мы в действительности создаем новый объект в памяти и присваиваем его полям значения, соответствующе тем, которые были у другого объекта. Однако удобно представлять этот процесс на более высоком уровне абстракции, как сохранение и восстановление.

Сохраняемость – способность объекта существовать во времени, переживая породивший его процесс, и (или) в пространстве, перемещаясь из своего первоначального адресного пространства. [Буч 3]

Для сохранения объекта в файл, передачи по сети или другому процессу, как правило, требуется привести значения полей объекта в формат, который будет понятен получателю и преобразовать их в непрерывную последовательность байт. Этот процесс называется сериализацией (от англ. serial – последовательный).

Сериализация (serialization) – преобразование состояния объекта в последовательность байт. Сериализация может быть двоичная, текстовая (JSON, XML) или какая-либо другая. Обратный процесс называется десериализацией.

На следующем рисунке предоставлен пример сериализации объекта в формат JSON.

Особая тема – сохранение объектов в реляционную базу данных. Чаще всего используется следующий подход: объект соответствует строке таблицы, а поля – столбцам этой таблицы. Значения значимых полей хранятся непосредственно в ячейках строки таблицы, а ссылочные поля реализуются с помощью внешних ключей, связывающих строку таблицы объекта со строкой или строками таблицы другого объекта, на который указывает ссылочное поле 27 . На следующем рисунке приведен пример сохранения уже рассмотренного выше объекта Circle c в две таблицы реляционной базы данных:

Очевидно, процесс сохранения и восстановления состояния объекта из базы достаточно трудоемкий для программиста, поэтому крайне привлекательна идея сохранения и загрузки состояния объекта в реляционную базу данных простым вызовом некоторых встроенных методов Save и Load . Библиотеки, упрощающие сохранение и загрузку состояния объектов или реализующие так или иначе эти методы Save и Load , называют ORM-библиотеками:

ORM (object-relational mapping) – «объектно-реляционное сопоставление» – механизм (реализуемый не языком программирования, а сторонними библиотеками), позволяющий сохранять или восстанавливать состояние объекта в приложении в реляционную базу данных.

Эти механизмы используются крайне широко и любое промышленное приложение использует ту или иную ORM, например, EntityFramework или Dapper .

Следует отметить, что полноценное «зеркалирование» базы данных в объекты памяти помимо трудоемкости, а точнее рутинности этого процесса, сопряжено с множеством концептуальных проблем, прежде всего, связанных с транзакциями. Чтобы преодолеть эти ограничения некоторые базы данных заявляются как объектные, а не реляционные, в том смысле, что при работе с ними мы не транслируем объектную модель программы в реляционную модель БД с помощью ORM, а напрямую сохраняем в БД в объектной модели. Хотя эти направления динамично развиваются, однако пока остаются нишевыми.

§ 18. Свойства в C#. В заключение главы, коротко рассмотрим специальную синтаксическую разновидность методов в C# – свойства. В C# мы можем объявить метод, возвращающий значение заданного типа или принимающий один параметр заданного типа, используя следующий специальный синтаксис, позволяющий пользователям класса обращаться к этим методам, как к полям:
class Circle < private float radius; // Обычные методы public float GetRaduis () < return raduis; >public void SetRaduis (float value) < radius = value; >// Свойство, эквивалентно двум предыдущим методам. public float Radius < get < return radius; >set < //value здесь – ключевое слово, обозначающее параметр метода типа float. radius = value; > > > // Использование обычных методов. float r = circle.GetRaduis(); circle.SetRaduis (5); // Использование свойств. // Синтаксичеки обращение к свойствам выглядит как обращение к полю класса. // public float Radius. // Однако по факту выполняется вызов методов Radius.get и Radius.set. float r = circle.Radius; circe.Radius = 5; 

Для свойства может объявлен только метод get , тогда мы создаем свойство доступное только на чтение, или, наоборот, только метод set , тогда мы создаем свойство, доступное только на запись (как правило, неудачное решение). Для методов get и set нет устоявшегося перевода на русский язык, по-английски их называют getter и setter .

Отметим еще раз, что свойства – специфическая возможность C# и представляет лишь синтаксическое упрощение для объявления и вызова определенных методов, а термин «свойство» вне языка C# часто используется для обозначения обычных полей класса.

Вопросы и задания

1. Дайте определение следующим терминам и сопоставьте русские термины с английскими: состояние объекта, поле, переменная-член, свойство, поведение объекта, метод, перегрузка метода, функция, функция-член, сокрытие локальных переменных неизменяемость, константа; state, field, member variable, property, behavior, method, overloading, member function, immutability.

2. Имеет ли открытый ( public ) метод класса доступ к закрытым ( private ) полям того же класса (1) того же объекта, для которого этот метод был вызван; (2) для другого объекта того же класса, который был передан как параметр этого метода?

3. В чем отличие между классом без состояния ( stateless ) и неизменяемым классом ( immutable )?

4. Почему нельзя перегрузить методы с одинаковыми параметрами, но отличающимися типами возвращаемых значений?

5. Изучите различия классов string и StringBuilder .

6. Назовите преимущества и недостатки использования неизменяемых типов данных.

7. Можно ли в C# объявить локальную переменную или параметр метода с ключевым словом readonly ?

8. Можно ли в конструкторе присваивать значения readonly -полю несколько раз?

9. Является ли массив неизменяемым типом данных в C#?

10. Повышают ли производительность приложений использование const и readonly в C#? Является ли производительность фактором, который необходимо учитывать, применяя механизмы языка программирования по ограничению изменения состояния объектов?

11. Объясните утверждение, что в C# неизменяемость – свойство классов, а не объектов.

12.* Познакомьтесь с подходом «event sourcing» (к сожалению, устоявшегося перевода этого термина нет), в рамках которого мы храним список транзакций, то есть событий об изменении состояния, но не храним само состояние.

13.* Приведите аргументы в пользу следующего тезиса: «Чем большей памятью мы располагаем и чем быстрее становятся наши компьютеры, тем меньше мы нуждаемся в изменяемых состояниях.» [11]

24. Или – в C# – свойства, см. § 18.

25. Системы статического анализа кода не в счет, так как это внешние по отношению к языку программирования инструменты.

26. Строго говоря, структуры C# (struct) также не могут быть константами, хотя и являются значимыми типами.

27. Нередко этот способ сохранения объектов в реляционную БД подвергают критике. Подробный анализ этого вопроса приведен, например, в [Дейт 4]. Однако по факту, именно описанная модель сегодня является самой распространенной.

© Тимофей Усов, 2019—2020.

Python/Объектно-ориентированное программирование на Python

Согласно Алану Кэю — автору языка программирования Smalltalk — объектно-ориентированным может называться язык, построенный с учетом следующих принципов [1] :

  • Все данные представляются объектами
  • Программа является набором взаимодействующих объектов, посылающих друг другу сообщения
  • Каждый объект имеет собственную часть памяти и может иметь в составе другие объекты
  • Каждый объект имеет тип
  • Объекты одного типа могут принимать одни и те же сообщения (и выполнять одни и те же действия)

Объекты, типы и классы [ править ]

Определение класса [ править ]

Для определения класса используется оператор class :

class имя_класса(надкласс1, надкласс2, . ): # определения атрибутов и методов класса 

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

Минимально возможное определение класса выглядит так:

class A: pass 

В терминологии Python члены класса называются атрибутами, функции класса — методами, а поля класса — свойствами (или просто атрибутами).

Определения методов аналогичны определениям функций, но (за некоторыми исключениями, о которых ниже) методы всегда имеют первый аргумент, называемый по общепринятому соглашению self :

class A: def m1(self, x): # блок кода метода 

Определения атрибутов — это обычные операторы присваивания, которые связывают некоторые значения с именами атрибутов.

class A: attr1 = 2 * 2 

В языке Python класс не является чем-то статическим, поэтому добавить атрибуты можно и после определения:

class A: pass def my_method(self, x): return x * x A.m1 = my_method A.attr1 = 2 * 2 
Создание экземпляра [ править ]

Для создания объекта — экземпляра класса (то есть, инстанцирования класса), достаточно вызвать класс по имени и задать параметры конструктора:

class Point: def __init__(self, x, y, z): self.coord = (x, y, z) def __repr__(self): return "Point(%s, %s, %s)" % self.coord 
>>> p = Point(0.0, 1.0, 0.0) >>> p Point(0.0, 1.0, 0.0) 

Переопределив классовый метод __new__ , можно управлять процессом создания экземпляра. Этот метод вызывается до метода __init__ и должен вернуть новый экземпляр либо None (в последнем случае будет вызван __new__ родительского класса). Метод __new__ используется для управления созданием неизменчивых (immutable) объектов, управления созданием объектов в случаях, когда __init__ не вызывается, например, при десериализации (unpickle). Следующий код демонстрирует один из вариантов реализации шаблона Одиночка:

>>> class Singleton(object): obj = None # Атрибут для хранения единственного экземпляра def __new__(cls, *dt, **mp): # класса Singleton. if cls.obj is None: # Если он еще не создан, то cls.obj = object.__new__(cls, *dt, **mp) # вызовем __new__ родительского класса return cls.obj # вернем синглтон . >>> obj = Singleton() >>> obj.attr = 12 >>> new_obj = Singleton() >>> new_obj.attr 12 >>> new_obj is obj # new_obj и obj - это один и тот же объект True 
Конструктор, инициализатор, деструктор [ править ]

Специальные методы вызываются при создании экземпляра класса (конструктор), при инициализировании экземпляра класса (инициализатор) и при удалении класса (деструктор). В языке Python реализовано автоматическое управление памятью, поэтому конструктор и деструктор требуются достаточно редко, для ресурсов, требующих явного освобождения.

Следующий класс имеет конструктор, инициализатор и деструктор:

class Line: def __new__(cls): # Конструктор return super(Line, cls).__new__(cls) def __init__(self, p1, p2): # Инициализатор self.line = (p1, p2) def __del__(self): # Деструктор print("Удаляется линия %s - %s" % self.line) 
>>> l = Line((0.0, 1.0), (0.0, 2.0)) >>> del l Удаляется линия (0.0, 1.0) - (0.0, 2.0) >>> 

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

Время жизни объекта [ править ]

Обычно время жизни объекта, определённого в программе на Python, не выходит за рамки времени выполнения процесса этой программы.

Для преодоления этого ограничения объект можно сохранить, а после — восстановить. Как правило, при записи объекта производится его сериализация, а при чтении — десериализация.

>>> import shelve >>> s = shelve.open("somefile.db") >>> s['myobject'] = [1, 2, 3, 4, 'свечка'] >>> s.close() >>> import shelve >>> s = shelve.open("somefile.db") >>> print s['myobject'] [1, 2, 3, 4, '\xd1\x81\xd0\xb2\xd0\xb5\xd1\x87\xd0\xba\xd0\xb0'] 
Инкапсуляция и доступ к свойствам [ править ]

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

Сокрытие информации о внутреннем устройстве объекта выполняется в Python на уровне соглашения между программистами о том, какие атрибуты относятся к общедоступному интерфейсу класса, а какие — к его внутренней реализации. Одиночное подчеркивание в начале имени атрибута говорит о том, что атрибут не предназначен для использования вне методов класса (или вне функций и классов модуля), однако, атрибут все-таки доступен по этому имени. Два подчеркивания в начале имени дают несколько большую защиту: атрибут перестает быть доступен по этому имени. Последнее используется достаточно редко.

Есть существенное отличие между такими атрибутами и личными (private) членами класса в таких языках как C++ или Java: атрибут остается доступным, но под именем вида _ИмяКласса__ИмяАтрибута , а при каждом обращении Python будет модифицировать имя в зависимости от того, через экземпляр какого класса происходит обращение к атрибуту. Таким образом, родительский и дочерний классы могут иметь атрибут с именем, например, «__f», но не будут мешать друг другу.

>>> class parent(object): def __init__(self): self.__f = 2 def get(self): return self.__f . >>> class child(parent): def __init__(self): self.__f = 1 parent.__init__(self) def cget(self): return self.__f . >>> c = child() >>> c.get() 2 >>> c.cget() 1 >>> c.__dict__ '_child__f': 1, '_parent__f': 2> # на самом деле у объекта "с" два разных атрибута 

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

Доступ к атрибуту может быть как прямой:

class A(object): def __init__(self, x): # атрибут получает значение в инициализаторе self.x = x a = A(5) print a.x >>> 5 

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

class A(object): def __init__(self, x): self._x = x def getx(self): # метод для получения значения return self._x def setx(self, value): # метод для присваивания нового значения self._x = value def delx(self): # метод для удаления атрибута del self._x x = property(getx, setx, delx, "Свойство x") # определяем x как свойство a = A(5) print a.x # Синтаксис доступа к атрибуту при этом прежний >>> 5 

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

Существуют два способа централизованно контролировать доступ к атрибутам. Первый основан на перегрузке методов __getattr__() , __setattr__() , __delattr__() , а второй — метода __getattribute__() . Второй метод помогает управлять чтением уже существующих атрибутов.

Эти способы позволяют организовать полностью динамический доступ к атрибутам объекта или, что используется очень часто, имитации несуществующих атрибутов. По такому принципу функционируют, например, все системы RPC для Python, имитируя методы и свойства, реально существующие на удаленном сервере.

Полиморфизм [ править ]

В компилируемых языках программирования полиморфизм достигается за счёт создания виртуальных методов, которые в отличие от невиртуальных можно перегрузить в потомке. В Python все методы являются виртуальными, что является естественным следствием разрешения доступа на этапе исполнения. (Следует отметить, что создание невиртуальных методов в компилируемых языках связано с меньшими накладными расходами на их поддержку и вызов).

>>> class Parent(object): def isParOrPChild(self) : return True def who(self) : return 'parent' >>> class Child(Parent): def who(self): return 'child' >>> x = Parent() >>> x.who(), x.isParOrPChild() ('parent', True) >>> x = Child() >>> x.who(), x.isParOrPChild() ('child', True) 

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

>>> class Child(Parent): def __init__(self): Parent.__init__(self) 

В общем случае для получения класса-предка применяется функция super .

class Child(Parent): def __init__(self): super(Child, self).__init__() 

Используя специально предусмотренное исключение NotImplementedError , можно имитировать чисто виртуальные методы:

>>> class abstobj(object): def abstmeth(self): raise NotImplementedError('Method abstobj.abstmeth is pure virtual') >>> abstobj().abstmeth() Traceback (most recent call last): File "", line 1, in module> File "", line 2, in method NotImplementedError: Method abstobj.abstmeth is pure virtual 

Или, с использованием декоратора, так:

>>> def abstract(func): def closure(*dt, **mp): raise NotImplementedError("Method %s is pure virtual" % func.__name__) return closure >>> class abstobj(object): @abstract def abstmeth(self): pass >>> abstobj().abstmeth() Traceback (most recent call last): File "", line 1, in module> File "", line 2, in method NotImplementedError: Method abstobj.abstmeth is pure virtual 

Изменяя атрибут __class__ , можно перемещать объект вверх или вниз по иерархии наследования (впрочем, как и к любому другому типу)

>>> c = child() >>> c.val = 10 >>> c.who() 'child' >>> c.__class__ = Parent >>> c.who() 'parent' >>> c.val 10 

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

Более того, полиморфизм в Python вообще не связан с наследованием, поэтому его можно считать сигнатурно-ориентированным полиморфизмом (signature-oriented polymorphism) [2] . Например, чтобы экземпляру класса «прикинуться» файловым объектом, ему достаточно реализовать методы, относящиеся к файлам (обычно .read() , .readlines() , .close() и т. п.).

Переопределение встроенных типов [ править ]

Встроенные типы и их методы имеют синтаксическую поддержку в языке Python или другие особые «привилегии». Конечно, любая операция может быть представлена синтаксисом вызова функции, однако, для частого применения это неудобно.

Воспользоваться точно такой же синтаксической поддержкой может и любой определённый пользователем класс. Для этого нужно лишь реализовать методы со специальными именами. Самый простой пример — переопределить функцию:

>>> class Add: . def __call__(self, x, y): # переопределение метода, . return x + y # который отвечает за операцию вызова функции . >>> add = Add() >>> add(3, 4) # это эквивалентно add.__call__(3, 4) 7 

Аналогично поддаются переопределению все операции встроенных типов. Ещё один пример связан с вычислением длины объекта с помощью функции len() . Эта встроенная функция вызывает специальный метод:

>>> class wrongList(list): # определяем собственный класс для списка . def __len__(self): # который всегда считает, что имеет нулевую длину . return 0 . >>> w = wrongList([1,2,3]) >>> len(w) # это эквивалентно w.__len__() 0 

Методы __getitem__,__setitem__,__delitem__,__contains__ позволяют создать интерфейс для словаря или списка( dict ).

Достаточно просто переопределить и числовые типы. Скажем, следующий класс использует инфиксную операцию * :

class Multiplyable: def __init__(self, value): self.value = value def __mul__(self, y): return self.value * y def __rmul__(self, x): return x * self.value def __imul__(self, y): return Multiplyable(self.value * y) def __str__(self): return "Multiplyable(%s)" % self.value >>> m = Multiplyable(1) >>> print m Multiplyable(1) >>> m *= 3 >>> print m Multiplyable(3) 

Последний из методов — .__str__() — отвечает за представление экземпляра класса при печати оператором print и в других подобных случаях.

Аналогичные методы имеются и у соответствующих встроенных типов:

>>> int.__add__ slot wrapper '__add__' of 'int' objects> >>> [].__getitem__ built-in method __getitem__ of list object at 0x00DA3D28> >>> class a(object):pass >>> a.__call__ method-wrapper '__call__' of type object at 0x00DDC318> 

Не все из них существуют на самом деле: большая часть имитируется интерпретатором Python для удобства программиста. Такое поведение позволяет экономить время при наиболее важных операциях (например, сложение целых не приводит к поиску и вызову метода __add__ у класса int ) и память не расходуется на этот поиск и вызов, но приводит к невозможности изменения методов у встроенных классов.

Отношения между классами [ править ]

Наследование и множественное наследование [ править ]

При описании предметной области классы могут образовывать иерархию, в корне которой стоит базовый класс, а нижележащие классы (подклассы) наследуют свои атрибуты и методы, уточняя и расширяя поведение вышележащего класса (надкласса). Обычно принципом построения классификации является отношение «IS-A» («есть» — между экземпляром и классом) и «AKO» («a kind of» — «разновидность» — между классом и суперклассом) [3] .

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

>>> class Par1(object): # наследуем один базовый класс - object def name1(self): return 'Par1' >>> class Par2(object): def name2(self): return 'Par2' >>> class Child(Par1, Par2): # создадим класс, наследующий Par1, Par2 (и, опосредованно, object) pass >>> x = Child() >>> x.name1(), x.name2() # экземпляру Child доступны методы из Par1 и Par2 'Par1','Par2' 

В Python (из-за «утиной типизации») отсутствие наследования ещё не означает, что объект не может предоставлять тот же самый интерфейс.

Множественное наследование в Python применяется в основном для добавления примесей (mixins) — специальных классов, вносящих некоторую черту поведения или набор свойств [4] .

Порядок разрешения доступа к методам и полям [ править ]

За достаточно простым в использовании механизмом доступа к атрибутам в w:Python кроется довольно сложный алгоритм. Далее будет приведена последовательность действий, производимых интерпретатором при разрешении запроса object.field (поиск прекращается после первого успешно завершённого шага, иначе происходит переход к следующему шагу).

  1. Если у object есть метод __getattribute__ , то он будет вызван с параметром ‘field’ (либо __setattr__ или __delattr__ в зависимости от действия над атрибутом)
  2. Если у object есть поле __dict__ , то ищется object.__dict__[‘field’]
  3. Если у object.__class__ есть поле __slots__ , то ‘field’ ищется в object.__class__.__slots__
  4. Проверяется object.__class__.__dict__[‘fields’]
  5. Производится рекурсивный поиск по __dict__ всех родительских классов (при множественном наследовании поиск производится в режиме deep-first, в том порядке как базовые классы перечислены в определении класса-потомка). Алгоритм поиска разный для «классических» и «новых» классов.
  6. Если у object есть метод __getattr__ , то вызывается он с параметром ‘field’
  7. Вызывается исключение AttributeError .

Если поиск окончен успешно, то проверяется, является ли атрибут классом «нового стиля». Если является, то проверяется наличие у него метода __get__ (либо __set__ или __delete__ , в зависимости от действия над атрибутом), если метод найден, то происходит следующий вызов object.field.__get__(object) и возвращается его результат (такие атрибуты называется в Python атрибутами со связанным поведением (binded behavior) и используются, например, для создания свойств [5] ).

Эта последовательность распространяется только на пользовательские атрибуты. Системные атрибуты, такие как __dict__ , __len__ , __add__ и другие, имеющие специальные поля в С-структуре описания класса находятся сразу.

«Новые» и «классические» классы [ править ]

В версиях до 2.2 некоторые объектно-ориентированные возможности Python были заметно ограничены. Например, было невозможно наследовать встроенные классы и классы из модулей расширения. Свойства (property) не выделялись явно. Начиная с версии 2.2, объектная система Python была существенно переработана и дополнена. Однако для совместимости со старыми версиями Python было решено сделать две объектные модели: «классические» типы (полностью совместимые со старым кодом) и «новые» [6] . В версии Python3 поддержка «старых» классов будет удалена.

Для построения «нового» класса достаточно унаследовать его от другого «нового». Если нужно создать «чистый» класс, то можно унаследоваться от object — родительского типа для всех «новых» классов.

class OldStyleClass: pass # класс "старого" типа class NewStyleClass(object): pass # и "нового" 

Все стандартные классы — классы «нового» типа. [7]

Агрегация. Контейнеры. Итераторы [ править ]

Агрегация, когда один объект входит в состав другого, или отношение «HAS-A» («имеет»), реализуется в Python с помощью ссылок. Python имеет несколько встроенных типов контейнеров: список, словарь, множество. Можно определить собственные классы контейнеров со своей логикой доступа к хранимым объектам. (Следует заметить, что в Python агрегацию можно считать разновидностью ассоциации, так реально объекты не вложены друг в друга в памяти и, более того, время жизни элемента может не зависеть от времени жизни контейнера.)

Следующий класс из модуля utils.py среды web.py является примером контейнера-словаря, дополненного возможностью доступа к значениям при помощи синтаксиса доступа к атрибутам:

class Storage(dict): def __getattr__(self, key): try: return self[key] except KeyError, k: raise AttributeError, k def __setattr__(self, key, value): self[key] = value def __delattr__(self, key): try: del self[key] except KeyError, k: raise AttributeError, k def __repr__(self): return ' + dict.__repr__(self) + '>' 

Вот как он работает:

>>> v = Storage(a=5) >>> v.a 5 >>> v['a'] 5 >>> v.a = 12 >>> v['a'] 12 >>> del v.a 

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

>>> cont = dict(a=1, b=2, c=3) >>> for k in cont: . print k, cont[k] . a 1 c 3 b 2 
Ассоциация и слабые ссылки [ править ]

Отношение использования («USE-A») экземпляров одного класса другими является достаточно общим отношением. При использовании один класс обычно зависит от интерфейса другого класса (хотя эта зависимость может быть и взаимной). Если один объект использует другой, он обязательно содержит ссылку на него. Объекты могут ссылаться и друг на друга. В этом случае возникают циклические ссылки. Если ссылающиеся друг на друга объекты удалить, то они уже не могут быть удалены интерпретатором Python с помощью механизма подсчета ссылок. Удалением таких объектов занимается сборщик мусора.

Ассоциацию объектов без присущих ссылкам проблем можно осуществить с помощью слабых ссылок. Слабые ссылки не препятствуют удалению объекта.

Для работы со слабыми ссылками применяется модуль weakref .

Метаклассы [ править ]

Обычных возможностей объектно-ориентированного программирования хватает далеко не всегда. В некоторых случаях требуется изменить сам характер системы классов: расширить язык новыми типами классов, изменить стиль взаимодействия между классами и окружением, добавить некоторые дополнительные аспекты, затрагивающие все используемые в приложении классы, и т. п.

При объявлении метакласса за основу можно взять класс type . Пример:

# описание метакласса class myobject(type): # небольшое вмешательство в момент выделения памяти для класса def __new__(cls, name, bases, dict): print "NEW", cls.__name__, name, bases, dict return type.__new__(cls, name, bases, dict) # небольшое вмешательство в момент инициализации класса def __init__(cls, name, bases, dict): print "INIT", cls.__name__, name, bases, dict return super(myobject, cls).__init__(name, bases, dict) # порождение класса на основе метакласса (заменяет оператор class) MyObject = myobject("MyObject", (), <>) # обычное наследование другого класса из только что порожденного class MySubObject(MyObject): def __init__(self, param): print param # получение экземпляра класса myobj = MySubObject("parameter") 

Разумеется, вместо оператора print код метакласса может выполнять более полезные функции: регистрировать класс, передавать действия с классами на удаленную систему, использовать классы для других целей (например, как декларации или ограничения) и т. п.

Методы [ править ]

Метод [ править ]

Синтаксис описания метода ничем не отличается от описания функции, разве что его положением внутри класса и характерным первым формальным параметром self , с помощью которого внутри метода можно ссылаться на сам экземпляр класса (название self является соглашением, которого придерживаются программисты на Python):

class MyClass(object): def mymethod(self, x): return x == self._x 
Статический метод [ править ]

Статические методы в Python являются синтаксическими аналогами статических функций в основных языках программирования. Они не получают ни экземпляр ( self ), ни класс ( cls ) первым параметром. Для создания статического метода (только «новые» классы могут иметь статические методы) используется декоратор staticmethod

>>> class D(object): @staticmethod def test(x): return x == 0 . >>> D.test(1) # доступ к статическому методу можно получать и через класс False >>> f = D() >>> f.test(0) # и через экземпляр класса True 

Статические методы реализованы с помощью свойств (property).

Метод класса [ править ]

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

>>> class A(object): def __init__(self, int_val): self.val = int_val + 1 @classmethod def fromString(cls, val): # вместо self принято использовать cls return cls(int(val)) . >>> class B(A):pass . >>> x = A.fromString("1") >>> print x.__class__.__name__ A >>> x = B.fromString("1") >>> print x.__class__.__name__ B 

Классовые методы достаточно часто используются для перегрузки конструктора. Классовые методы, как и статические, реализуются через свойства (property).

Мультиметоды [ править ]

Примером для иллюстрации сути мультиметода может служить функция add() из модуля operator :

>>> import operator as op >>> print op.add(2, 2), op.add(2.0, 2), op.add(2, 2.0), op.add(2j, 2) 4 4.0 4.0 (2+2j) 

В языке Python достаточно легко реализовать и определённые пользователем мультиметоды [8] . Например, эмулировать мультиметоды можно с помощью модуля multimethods.py (из Gnosis Utils) :

from multimethods import Dispatch class Asteroid(object): pass class Spaceship(object): pass def asteroid_with_spaceship(a1, s1): print "A-> def asteroid_with_asteroid(a1, a2): print "A-> def spaceship_with_spaceship(s1, s2): print "S-> collide = Dispatch() collide.add_rule((Asteroid, Spaceship), asteroid_with_spaceship) collide.add_rule((Asteroid, Asteroid), asteroid_with_asteroid) collide.add_rule((Spaceship, Spaceship), spaceship_with_spaceship) collide.add_rule((Spaceship, Asteroid), lambda x,y: asteroid_with_spaceship(y,x)) a, s1, s2 = Asteroid(), Spaceship(), Spaceship() collision1 = collide(a, s1)[0] collision2 = collide(s1, s2)[0] 

Устойчивость объектов [ править ]

Объекты всегда имеют своё представление в памяти компьютера и их время жизни не больше времени работы программы. Однако зачастую необходимо сохранять данные между запусками приложения и/или передавать их на другие компьютеры. Одним из решений этой проблемы является устойчивость объектов (англ. object persistence ) которая достигается с помощью хранения представлений объектов (сериализацией) в виде байтовых последовательностей и их последующего восстановления (десериализация).

Модуль pickle является наиболее простым способом «консервирования» объектов в Python.

Следующий пример показывает, как работает сериализация и десериализация:

# сериализация >>> import pickle >>> p = set([1, 2, 3, 5, 8]) >>> pickle.dumps(p) 'c__builtin__\nset\np0\n((lp1\nI8\naI1\naI2\naI3\naI5\natp2\nRp3\n.' # де-сериализация >>> import pickle >>> p = pickle.loads('c__builtin__\nset\np0\n((lp1\nI8\naI1\naI2\naI3\naI5\natp2\nRp3\n.') >>> print p set([8, 1, 2, 3, 5]) 

Получаемая при сериализации строка может быть передана по сети, записана в файл или специальное хранилище объектов, а позже — прочитана. Сериализации поддаются не все объекты. Некоторые объекты (например, классы и функции) представляются своими именами, поэтому для десериализации требуется наличие тех же самых классов. Нужно отметить, что нельзя десериализовать данные из непроверенных источников с помощью модуля pickle , так как при этом возможны практически любые действия на локальной системе. При необходимости обмениваться данными по незащищенным каналам или с ненадежными источниками можно воспользоваться другими модулями для сериализации.

В основе сериализации объекта стоит представление его состояния. По умолчанию состояние объекта — это все, что записано в его полях. Пользовательские классы могут управлять сериализацией, предоставляя состояние объекта явным образом (методы __getstate__ , __setstate__ и др.).

На стандартном для Python механизме сериализации построена работа модуля shelve (shelve (англ. глаг.) — ставить на полку; сдавать в архив). Модуль предоставляет функцию open . Объект, который она возвращает, работает аналогично словарю, но объекты сериализуются и сохраняются в файле:

>>> import shelve >>> s = shelve.open("myshelve.bin") >>> s['abc'] = [1, 2, 3] >>> s.close() # . >>> s = shelve.open("myshelve.bin") >>> s['abc'] [1, 2, 3] 

Сериализация pickle — не единственная возможная, и подходит не всегда. Для сериализации, не зависящей от языка программирования, можно использовать, например, XML.

Примечания [ править ]

  1. ↑Introduction to Object-Oriented Programming
  2. ↑в списке рассылки comp.lang.python
  3. ↑«AKO» и «IS-A»
  4. ↑Beazley, 2009
  5. ↑How-To Guide for Descriptors by R. Hettinger (недоступная ссылка — история) Проверено 2007-10-06 г.Архивировано из первоисточника 6 октября 2007.
  6. ↑New-style Classes
  7. ↑Объяснение Гвидо ван Россума об объединении типов и классов
  8. ↑Charming Python: Multiple dispatch

Литература [ править ]

  • David M. Beazley Python Essential Reference. — 4th Edition. — Addison-Wesley Professional, 2009. — 717 с. — ISBN 978-0672329784

Объектно-ориентированное программирование

Python имеет множество встроенных типов, например, int, str и так далее, которые мы можем использовать в программе. Но также Python позволяет определять собственные типы с помощью классов . Класс представляет некоторую сущность. Конкретным воплощением класса является объект.

Можно еще провести следующую аналогию. У нас у всех есть некоторое представление о человеке, у которого есть имя, возраст, какие-то другие характеристики Человек может выполнять некоторые действия — ходить, бегать, думать и т.д. То есть это представление, которое включает набор характеристик и действий, можно назвать классом. Конкретное воплощение этого шаблона может отличаться, например, одни люди имеют одно имя, другие — другое имя. И реально существующий человек будет представлять объект этого класса.

Класс определяется с помощью ключевого слова class :

class название_класса: атрибуты_класса методы_класса

Внутри класса определяются его атрибуты, которые хранят различные характеристики класса, и методы — функции класса.

Создадим простейший класс:

class Person: pass

В данном случае определен класс Person, который условно представляет человека. В данном случае в классе не определяется никаких методов или атрибутов. Однако поскольку в нем должно быть что-то определено, то в качестве заменителя функционала класса применяется оператор pass . Этот оператор применяется, когда синтаксически необходимо определить некоторый код, однако мы не хотим его, и вместо конкретного кода вставляем оператор pass.

После создания класса можно определить объекты этого класса. Например:

class Person: pass tom = Person() # определение объекта tom bob = Person() # определение объекта bob

После определения класса Person создаются два объекта класса Person — tom и bob. Для создания объекта применяется специальная функция — конструктор , которая называется по имени класса и которая возвращает объект класса. То есть в данном случае вызов Person() представляет вызов конструктора. Каждый класс по умолчанию имеет конструктор без параметров:

tom = Person() # Person() - вызов конструктора, который возвращает объект класса Person
Методы классов

Методы класса фактически представляют функции, которые определенны внутри класса и которые определяют его поведение. Например, определим класс Person с одним методом:

class Person: # определение класса Person def say_hello(self): print("Hello") tom = Person() tom.say_hello() # Hello

Здесь определен метод say_hello() , который условно выполняет приветствие — выводит строку на консоль. При определении методов любого класса следует учитывать, что все они должны принимать в качестве первого параметра ссылку на текущий объект, который согласно условностям называется self . Через эту ссылку внутри класса мы можем обратиться к функциональности текущего объекта. Но при самом вызове метода этот параметр не учитывается.

Используя имя объекта, мы можем обратиться к его методам. Для обращения к методам применяется нотация точки — после имени объекта ставится точка и после нее идет вызов метода:

объект.метод([параметры метода])

Например, обращение к методу say_hello() для вывода приветствия на консоль:

tom.say_hello() # Hello

В итоге данная программа выведет на консоль строку «Hello».

Если метод должен принимать другие параметры, то они определяются после параметра self , и при вызове подобного метода для них необходимо передать значения:

class Person: # определение класса Person def say(self, message): # метод print(message) tom = Person() tom.say("Hello METANIT.COM") # Hello METANIT.COM

Здесь определен метод say() . Он принимает два параметра: self и message. И для второго параметра — message при вызове метода необходимо передать значение.

self

Через ключевое слово self можно обращаться внутри класса к функциональности текущего объекта:

self.атрибут # обращение к атрибуту self.метод # обращение к методу

Например, определим два метода в классе Person:

class Person: def say(self, message): print(message) def say_hello(self): self.say("Hello work") # обращаемся к выше определенному методу say tom = Person() tom.say_hello() # Hello work

Здесь в одном методе — say_hello() вызывается другой метод — say() :

self.say("Hello work")

Поскольку метод say() принимает кроме self еще параметры (параметр message), то при вызове метода для этого параметра передается значение.

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

def say_hello(self): say("Hello work") # ! Ошибка

То мы столкнемся с ошибкой

Конструкторы

Для создания объекта класса используется конструктор. Так, выше когда мы создавали объекты класса Person, мы использовали конструктор по умолчанию, который не принимает параметров и который неявно имеют все классы:

tom = Person()

Однако мы можем явным образом определить в классах конструктор с помощью специального метода, который называется __init__() (по два прочерка с каждой стороны). К примеру, изменим класс Person, добавив в него конструктор:

class Person: # конструктор def __init__(self): print("Создание объекта Person") def say_hello(self): print("Hello") tom = Person() # Создание объекта Person tom.say_hello() # Hello

Итак, здесь в коде класса Person определен конструктор и метод say_hello() . В качестве первого параметра конструктор, как и методы, также принимает ссылку на текущий объект — self. Обычно конструкторы применяются для определения действий, которые должны производиться при создании объекта.

Теперь при создании объекта:

tom = Person()

будет производится вызов конструктора __init__() из класса Person, который выведет на консоль строку «Создание объекта Person».

Атрибуты объекта

Атрибуты хранят состояние объекта. Для определения и установки атрибутов внутри класса можно применять слово self . Например, определим следующий класс Person:

class Person: def __init__(self, name): self.name = name # имя человека self.age = 1 # возраст человека tom = Person("Tom") # обращение к атрибутам # получение значений print(tom.name) # Tom print(tom.age) # 1 # изменение значения tom.age = 37 print(tom.age) # 37

Теперь конструктор класса Person принимает еще один параметр — name. Через этот параметр в конструктор будет передаваться имя создаваемого человека.

Внутри конструктора устанавливаются два атрибута — name и age (условно имя и возраст человека):

def __init__(self, name): self.name = name self.age = 1

Атрибуту self.name присваивается значение переменной name. Атрибут age получает значение 1.

Если мы определили в классе конструктор __init__, мы уже не сможем вызвать конструктор по умолчанию. Теперь нам надо вызывать наш явным образом опреледеленный конструктор __init__, в который необходимо передать значение для параметра name:

tom = Person("Tom")

Далее по имени объекта мы можем обращаться к атрибутам объекта — получать и изменять их значения:

print(tom.name) # получение значения атрибута name tom.age = 37 # изменение значения атрибута age

В принципе нам необязательно определять атрибуты внутри класса — Python позволяет сделать это динамически вне кода:

class Person: def __init__(self, name): self.name = name # имя человека self.age = 1 # возраст человека tom = Person("Tom") tom.company = "Microsoft" print(tom.company) # Microsoft

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

tom = Person("Tom") print(tom.company) # ! Ошибка - AttributeError: Person object has no attribute company

Для обращения к атрибутам объекта внутри класса в его методах также применяется слово self:

class Person: def __init__(self, name): self.name = name # имя человека self.age = 1 # возраст человека def display_info(self): print(f"Name: Age: ") tom = Person("Tom") tom.display_info() # Name: Tom Age: 1

Здесь определяется метод display_info(), который выводит информацию на консоль. И для обращения в методе к атрибутам объекта применяется слово self: self.name и self.age

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

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

class Person: def __init__(self, name): self.name = name # имя человека self.age = 1 # возраст человека def display_info(self): print(f"Name: Age: ") tom = Person("Tom") tom.age = 37 tom.display_info() # Name: Tom Age: 37 bob = Person("Bob") bob.age = 41 bob.display_info() # Name: Bob Age: 41

Здесь создаются два объекта класса Person: tom и bob. Они соответствуют определению класса Person, имеют одинаковый набор атрибутов и методов, однако их состояние будет отличаться.

При выполнении программы Python динамически будет определять self — он представляет объект, у которого вызывается метод. Например, в строке:

tom.display_info() # Name: Tom Age: 37

Это будет объект tom

bob.display_info()

Это будет объект bob

В итоге мы получим следующий консольный вывод:

Name: Tom Age: 37 Name: Bob Age: 41

Похожие публикации:

  1. Как запустить юпитер ноутбук в анаконде
  2. Как отправить диск по почте россии
  3. Почему вайбер так много весит
  4. Чем отличается игровая мышь от обычной

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

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