Python атрибуты класса: Python. Урок 14. Классы и объекты

Содержание

Python. Урок 14. Классы и объекты

Данный урок посвящен объектно-ориентированному программированию в Python. Разобраны такие темы как создание объектов и классов, работа с конструктором, наследование и полиморфизм в Python.

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

Выделяют три основных “столпа” ООП- это инкапсуляция, наследование и полиморфизм.

Инкапсуляция

Под инкапсуляцией понимается сокрытие деталей реализации, данных и т.п. от внешней стороны. Например, можно определить класс “холодильник”, который будет содержать следующие данные: производитель, объем, количество камер хранения, потребляемая мощность и т. п., и методы: открыть/закрыть холодильник, включить/выключить, но при этом реализация того, как происходит непосредственно включение и выключение пользователю вашего класса не доступна, что позволяет ее менять без опасения, что это может отразиться на использующей класс «холодильник» программе. При этом класс становится новым типом данных в рамках разрабатываемой программы. Можно создавать переменные этого нового типа, такие переменные называются объекты.

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

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

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

Полиморфизм

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

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

Создание класса в Python начинается с инструкции class. Вот так будет выглядеть минимальный класс.

class C: 
    pass

Класс состоит из объявления (инструкция class), имени класса (нашем случае это имя C) и тела класса, которое содержит атрибуты и методы (в нашем минимальном классе есть только одна инструкция pass).

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

имя_объекта = имя_класса()

Статические и динамические атрибуты класса

Как уже было сказано выше, класс может содержать атрибуты и методы. Атрибут может быть статическим и динамическим (уровня объекта класса). Суть в том, что для работы со статическим атрибутом, вам не нужно создавать экземпляр класса, а для работы с динамическим – нужно. Пример:

class Rectangle:
    default_color = "green"

    def __init__(self, width, height):
        self.width = width
        self.height = height

В представленном выше классе, атрибут default_color – это статический атрибут, и доступ к нему, как было сказано выше, можно получить не создавая объект класса Rectangle.

>>> Rectangle.default_color
'green'

width и height – это динамические атрибуты, при их создании было использовано ключевое слово self. Пока просто примите это как должное, более подробно про self будет рассказано ниже. Для доступа к width и height предварительно нужно создать объект класса Rectangle:

>>> rect = Rectangle(10, 20)
>>> rect.width
10
>>> rect.height
20

Если обратиться через класс, то получим ошибку:

>>> Rectangle.width
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: type object 'Rectangle' has no attribute 'width'

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

Проверим ещё раз значение атрибута default_color:

>>> Rectangle.default_color
'green'

Присвоим ему новое значение:

>>> Rectangle. default_color = "red"
>>> Rectangle.default_color
'red'

Создадим два объекта класса Rectangle и проверим, что default_color у них совпадает:

>>> r1 = Rectangle(1,2)
>>> r2 = Rectangle(10, 20)
>>> r1.default_color
'red'
>>> r2.default_color
'red'

Если поменять значение default_color через имя класса Rectangle, то все будет ожидаемо: у объектов r1 и r2 это значение изменится, но если поменять его через экземпляр класса, то у экземпляра будет создан атрибут с таким же именем как статический, а доступ к последнему будет потерян:

Меняем default_color через r1:

>>> r1.default_color = "blue"
>>> r1.default_color
'blue'

При этом у r2 остается значение статического атрибута:

>>> r2.default_color
'red'
>>> Rectangle. default_color
'red'

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

Методы класса

Добавим к нашему классу метод. Метод – это функция, находящаяся внутри класса и выполняющая определенную работу.

Методы бывают статическими, классовыми (среднее между статическими и обычными) и уровня класса (будем их называть просто словом метод). Статический метод создается с декоратором @staticmethod, классовый – с декоратором @classmethod, первым аргументом в него передается cls, обычный метод создается без специального декоратора, ему первым аргументом передается self:

class MyClass:

    @staticmethod
    def ex_static_method():
        print("static method")

    @classmethod
    def ex_class_method(cls):
        print("class method")

    def ex_method(self):
        print("method")

Статический и классовый метод можно вызвать, не создавая экземпляр класса, для вызова ex_method() нужен объект:

>>> MyClass. ex_static_method()
static method

>>> MyClass.ex_class_method()
class method

>>> MyClass.ex_method()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: ex_method() missing 1 required positional argument: 'self'

>>> m = MyClass()
>>> m.ex_method()
method

Конструктор класса и инициализация экземпляра класса

В Python разделяют конструктор класса и метод для инициализации экземпляра класса. Конструктор класса это метод __new__(cls, *args, **kwargs) для инициализации экземпляра класса используется метод __init__(self). При этом, как вы могли заметить __new__ – это классовый метод, а __init__ таким не является. Метод __new__ редко переопределяется, чаще используется реализация от базового класса object (см. раздел Наследование), __init__ же наоборот является очень удобным способом задать параметры объекта при его создании.

Создадим реализацию класса Rectangle с измененным конструктором и инициализатором, через который задается ширина и высота прямоугольника:

class Rectangle:

    def __new__(cls, *args, **kwargs):
        print("Hello from __new__")
        return super().__new__(cls)

    def __init__(self, width, height):
        print("Hello from __init__")
        self.width = width
        self.height = height


>>> rect = Rectangle(10, 20)
Hello from __new__
Hello from __init__

>>> rect.width
10

>>> rect.height
20

Что такое self?

До этого момента вы уже успели познакомиться с ключевым словом self. self – это ссылка на текущий экземпляр класса, в таких языках как Java, C# аналогом является ключевое слово this. Через self вы получаете доступ к атрибутам и методам класса внутри него:

class Rectangle:

    def __init__(self, width, height):
        self. width = width
        self.height = height

    def area(self):
        return self.width * self.height

В приведенной реализации метод area получает доступ к атрибутам width и height для расчета площади. Если бы в качестве первого параметра не было указано self, то при попытке вызвать area программа была бы остановлена с ошибкой.

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

Если вы знакомы с языками программирования Java, C#, C++ то, наверное, уже задались вопросом: “а как управлять уровнем доступа?”. В перечисленных языка вы можете явно указать для переменной, что доступ к ней снаружи класса запрещен, это делается с помощью ключевых слов (private, protected и т.д.). В Python таких возможностей нет, и любой может обратиться к атрибутам и методам вашего класса, если возникнет такая необходимость. Это существенный недостаток этого языка, т.к. нарушается один из ключевых принципов ООП – инкапсуляция. Хорошим тоном считается, что для чтения/изменения какого-то атрибута должны использоваться специальные методы, которые называются getter/setter, их можно реализовать, но ничего не помешает изменить атрибут напрямую. При этом есть соглашение, что метод или атрибут, который начинается с нижнего подчеркивания, является скрытым, и снаружи класса трогать его не нужно (хотя сделать это можно).

Внесем соответствующие изменения в класс Rectangle:

class Rectangle:

    def __init__(self, width, height):
        self._width = width
        self._height = height

    def get_width(self):
        return self._width

    def set_width(self, w):
        self._width = w

    def get_height(self):
        return self._height

    def set_height(self, h):
        self._height = h

    def area(self):
        return self._width * self._height

В приведенном примере для доступа к _width и _height используются специальные методы, но ничего не мешает вам обратиться к ним (атрибутам) напрямую.

>>> rect = Rectangle(10, 20)

>>> rect.get_width()
10

>>> rect._width
10

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

class Rectangle:

    def __init__(self, width, height):
        self.__width = width
        self.__height = height

    def get_width(self):
        return self.__width

    def set_width(self, w):
        self.__width = w

    def get_height(self):
        return self.__height

    def set_height(self, h):
        self.__height = h

    def area(self):
        return self.__width * self.__height

Попытка обратиться к __width напрямую вызовет ошибку, нужно работать только через get_width():

>>> rect = Rectangle(10, 20)

>>> rect.__width
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Rectangle' object has no attribute '__width'

>>> rect. get_width()
10

Но на самом деле это сделать можно, просто этот атрибут теперь для внешнего использования носит название: _Rectangle__width:

>>> rect._Rectangle__width
10

>>> rect._Rectangle__width = 20

>>> rect.get_width()
20

Свойства

Свойством называется такой метод класса, работа с которым подобна работе с атрибутом. Для объявления метода свойством необходимо использовать декоратор @property.

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

Сделаем реализацию класса Rectangle с использованием свойств:

class Rectangle:

    def __init__(self, width, height):
        self.__width = width
        self.__height = height

    @property
    def width(self):
        return self.__width

    @width.setter
    def width(self, w):
        if w > 0:
            self. __width = w
        else:
            raise ValueError

    @property
    def height(self):
        return self.__height

    @height.setter
    def height(self, h):
        if h > 0:
            self.__height = h
        else:
            raise ValueError

    def area(self):
        return self.__width * self.__height

Теперь работать с width и height можно так, как будто они являются атрибутами:

>>> rect = Rectangle(10, 20)

>>> rect.width
10

>>> rect.height
20

Можно не только читать, но и задавать новые значения свойствам:

>>> rect.width = 50

>>> rect.width
50

>>> rect.height = 70

>>> rect.height
70

Если вы обратили внимание: в setter’ах этих свойств осуществляется проверка входных значений, если значение меньше нуля, то будет выброшено исключение ValueError:

>>> rect. width = -10
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "test.py", line 28, in width
    raise ValueError
ValueError

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

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

Синтаксически создание класса с указанием его родителя выглядит так:

class имя_класса(имя_родителя1, [имя_родителя2,…, имя_родителя_n])

Переработаем наш пример так, чтобы в нем присутствовало наследование:

class Figure:

    def __init__(self, color):
        self. __color = color

    @property
    def color(self):
        return self.__color

    @color.setter
    def color(self, c):
        self.__color = c


class Rectangle(Figure): 

    def __init__(self, width, height, color):
        super().__init__(color)
        self.__width = width
        self.__height = height

    @property
    def width(self):
        return self.__width

    @width.setter
    def width(self, w):
        if w > 0:
            self.__width = w
        else:
            raise ValueError

    @property
    def height(self):
        return self.__height

    @height.setter
    def height(self, h):
        if h > 0:
            self.__height = h
        else:
            raise ValueError 

    def area(self):
        return self.__width * self.__height

Родительским классом является Figure, который при инициализации принимает цвет фигуры и предоставляет его через свойства. Rectangle – класс наследник от Figure. Обратите внимание на его метод __init__: в нем первым делом вызывается конструктор (хотя это не совсем верно, но будем говорить так) его родительского класса:

super(). __init__(color)

super – это ключевое слово, которое используется для обращения к родительскому классу.

Теперь у объекта класса Rectangle помимо уже знакомых свойств width и height появилось свойство color:

>>> rect = Rectangle(10, 20, "green")

>>> rect.width
10

>>> rect.height
20

>>> rect.color
'green'

>>> rect.color = "red"

>>> rect.color
'red'

Полиморфизм

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

class Figure:

    def __init__(self, color):
        self. __color = color

    @property
    def color(self):
        return self.__color

    @color.setter
    def color(self, c):
        self.__color = c

    def info(self):
       print("Figure")
       print("Color: " + self.__color)


class Rectangle(Figure):

    def __init__(self, width, height, color):
        super().__init__(color)
        self.__width = width
        self.__height = height

    @property
    def width(self):
        return self.__width

    @width.setter
    def width(self, w):
        if w > 0:
            self.__width = w
        else:
            raise ValueError

    @property
    def height(self):
        return self.__height

    @height.setter
    def height(self, h):
        if h > 0:
            self.__height = h
        else:
            raise ValueError

    def info(self):
        print("Rectangle")
        print("Color: " + self.color)
        print("Width: " + str(self.width))
        print("Height: " + str(self.height))
        print("Area: " + str(self.area()))

    def area(self):
        return self.__width * self.__height

Посмотрим, как это работает

>>> fig = Figure("orange")

>>> fig.info()
Figure
Color: orange

>>> rect = Rectangle(10, 20, "green")

>>> rect.info()
Rectangle
Color: green
Width: 10
Height: 20
Area: 200

Таким образом, класс наследник может расширять функционал класса родителя.

P.S.

Если вам интересна тема анализа данных, то мы рекомендуем ознакомиться с библиотекой Pandas. На нашем сайте вы можете найти вводные уроки по этой теме. Все уроки по библиотеке Pandas собраны в книге “Pandas. Работа с данными”.

<<< Python. Урок 13. Модули и пакеты   Python. Урок 15. Итераторы и генераторы>>>

ОСновы работы с классами в python 3 ~ PythonRu

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

Python — это процедурно-ориентированный и одновременно объектно-ориентированный язык программирования.

Процедурно-ориентированный

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

Объектно-ориентированный

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

Создание класса в Python

Синтаксис для написания нового класса:

class ClassName:
    'Краткое описание класса (необязательно)'
    
  • Для создания класса пишется ключевое слово class, его имя и двоеточие (:). Первая строчка в теле класса описывает его. (По желанию) получить доступ к этой строке можно с помощью ClassName.__doc__
  • В теле класса допускается объявление атрибутов, методов и конструктора.

Атрибут:

Атрибут — это элемент класса. Например, у прямоугольника таких 2: ширина (width) и высота (height).

Метод:

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

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

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

class Rectangle :
    'Это класс Rectangle'
    
    def __init__(self, width, height):         
        self.width= width
        self.height = height

    def getWidth(self):        
        return self.width
     
    def getHeight(self):        
        return self.height
 
    
    def getArea(self):
        return self.width * self.height

Создание объекта с помощью класса Rectangle:


r1 = Rectangle(10,5)
r2 = Rectangle(20,11)
 
print("r1.width = ", r1.width)
print("r1.height = ", r1.height)
print("r1.getWidth() = ", r1.getWidth())
print("r1.getArea() = ", r1.getArea())
 
print("-----------------")
 
print("r2.width = ", r2.width)
print("r2.height = ", r2.height)
print("r2.getWidth() = ", r2.getWidth())
print("r2.getArea() = ", r2.getArea())

Что происходит при создании объекта с помощью класса?

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

Конструктор с аргументами по умолчанию

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

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

class Person:
    
    def __init__(self, name, age=1, gender="Male"):
        self.name = name
        self.age = age 
        self.gender= gender
         
    def showInfo(self):
        print("Name: ", self.name)
        print("Age: ", self.age)
        print("Gender: ", self.gender)

Например:

from person import Person
 

aimee = Person("Aimee", 21, "Female")
aimee.showInfo()
print(" --------------- ")
 

alice = Person( "Alice" )
alice.showInfo()
 
print(" --------------- ")
 

tran = Person("Tran", 37)
tran.showInfo()

Сравнение объектов

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

Если объект AA — это просто ссылка на объект BB, то он не будет сущностью, занимающей отдельную ячейку памяти. Вместо этого он лишь ссылается на местоположение BB.

Оператор == нужен, чтобы узнать, ссылаются ли два объекта на одно и то же место в памяти. Он вернет True, если это так. Оператор != вернет True, если сравнить 2 объекта, которые ссылаются на разные места в памяти.

from rectangle import Rectangle


r1 = Rectangle(20, 10)
r2 = Rectangle(20 , 10)
r3 = r1
 

test1 = r1 == r2 

test2 = r1 == r3 
 
print ("r1 == r2 ? ", test1) 
print ("r1 == r3 ? ", test2)

print (" -------------- ")
 
print ("r1 != r2 ? ", r1 != r2)
print ("r1 != r3 ? ", r1 != r3)

Атрибуты

В Python есть два похожих понятия, которые на самом деле отличаются:

  1. Атрибуты
  2. Переменные класса

Стоит разобрать на практике:

class Player:
    
    minAge  = 18
    maxAge = 50
     
    def __init__(self, name, age):
        self.name = name
        self.age = age

Атрибут

Объекты, созданные одним и тем же классом, будут занимать разные места в памяти, а их атрибуты с «одинаковыми именами» — ссылаться на разные адреса. Например:

from player import Player 
 
 
player1 = Player("Tom", 20)
 
player2 = Player("Jerry", 20)
 
print("player1.name = ", player1.name)
print("player1.age = ", player1.age)
 
print("player2.name = ", player2.name)
print("player2.age = ", player2.age)
 
print(" ------------ ")
 
print("Assign new value to player1.age = 21 ")
 

player1.age = 21
 
print("player1.name = ", player1.name)
print("player1.age = ", player1.age)
 
print("player2.name = ", player2.name)
print("player2.age = ", player2.age)

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

from player import Player 
 
 
player1 = Player("Tom", 20)
player2 = Player("Jerry", 20)
 

player1.address = "USA"
 
print("player1.name = ", player1.name)
print("player1.age = ", player1.age)
print("player1.address = ", player1.address)
 
print(" ------------------- ")
 
print("player2.name = ", player2.name)
print("player2.age = ", player2.age)
 

print("player2.address = ", player2.address)

Вывод:

player1.name =  Tom
player1.age =  20
player1.address =  USA
 ------------------- 
player2.name =  Jerry
player2.age =  20
Traceback (most recent call last):
  File "C:/Users/gvido/class.py", line 27, in <module>
    print("player2.address = ", player2.address)
AttributeError: 'Player' object has no attribute 'address'

Атрибуты функции

Обычно получать доступ к атрибутам объекта можно с помощью оператора «точка» (например, player1.name). Но Python умеет делать это и с помощью функции.

ФункцияОписание
getattr (obj, name[,default])Возвращает значение атрибута или значение по умолчанию, если первое не было указано
hasattr (obj, name)Проверяет атрибут объекта — был ли он передан аргументом «name»
setattr (obj, name, value)Задает значение атрибута. Если атрибута не существует, создает его
delattr (obj, name)Удаляет атрибут
from player import Player 
 
 
player1 = Player("Tom", 20)
 

print("getattr(player1,'name') = " , getattr(player1,"name"))

print("setattr(player1,'age', 21): ")

setattr(player1,"age", 21)
print("player1.age = ", player1.age)
 

hasAddress =  hasattr(player1, "address")
print("hasattr(player1, 'address') ? ", hasAddress)
 

print("Create attribute 'address' for object 'player1'")
setattr(player1, 'address', "USA")
print("player1.address = ", player1.address)
 

delattr(player1, "address")

Вывод:

getattr(player1,'name') =  Tom
setattr(player1,'age', 21): 
player1.age =  21
hasattr(player1, 'address') ?  False
Create attribute 'address' for object 'player1'
player1.address =  USA

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

Объекты класса — дочерние элементы по отношению к атрибутам самого языка Python. Таким образом они заимствуют некоторые атрибуты:

АтрибутОписание
__dict__Предоставляет данные о классе коротко и доступно, в виде словаря
__doc__Возвращает строку с описанием класса, или None, если значение не определено
__class__Возвращает объект, содержащий информацию о классе с массой полезных атрибутов, включая атрибут __name__
__module__Возвращает имя «модуля» класса или __main__, если класс определен в выполняемом модуле.
class Customer:
    'Это класс Customer'
    def __init__(self, name, phone, address):        
        self.name = name
        self.phone = phone
        self.address = address
 
  
john = Customer("John",1234567, "USA")
 
print ("john.__dict__ = ", john.__dict__)
print ("john.__doc__ = ", john.__doc__)
print ("john.__class__ = ", john.__class__)
print ("john.__class__.__name__ = ", john.__class__.__name__) 
print ("john.__module__ = ", john.__module__)  

Вывод:

john.__dict__ =  {'name': 'John', 'phone': 1234567, 'address': 'USA'}
john.__doc__ =  Это класс Customer
john.__class__ =  <class '__main__.Customer'>
john.__class__.__name__ =  Customer
john.__module__ =  __main__

Переменные класса

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

Для получения доступа к переменной класса лучше все-таки использовать имя класса, а не объект. Это поможет не путать «переменную класса» и атрибуты.

У каждой переменной класса есть свой адрес в памяти. И он доступен всем объектам класса.

from player import Player 
 
 
player1 = Player("Tom", 20)
player2 = Player("Jerry", 20)
 

print ("Player.minAge = ", Player.minAge)
 

print("player1.minAge = ", player1.minAge) 
print("player2.minAge = ", player2.minAge)
 
print(" ------------ ") 

print("Assign new value to minAge via class name, and print..")
 

Player.minAge = 19
 
print("Player.minAge = ", Player.minAge) 
print("player1.minAge = ", player1.minAge) 
print("player2.minAge = ", player2.minAge)

Вывод:

Player.minAge =  18
player1.minAge =  18
player2.minAge =  18
 ------------ 
Assign new value to minAge via class name, and print..
Player.minAge =  19
player1.minAge =  19
player2.minAge =  19

Составляющие класса или объекта

В Python присутствует функция dir, которая выводит список всех методов, атрибутов и переменных класса или объекта.

from player import Player
 
 

print(dir(Player))
print("\n\n")
 
player1 = Player("Tom", 20)
player1.address ="USA"


print(dir(player1))

Вывод:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', 
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', 
'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', 
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', 
'__repr__', '__setattr__', '__sizeof__', '__str__', 
'__subclasshook__', '__weakref__', 'maxAge', 'minAge']  

  
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', 
'__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', 
'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', 
'__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', 
'__repr__', '__setattr__', '__sizeof__', '__str__', 
'__subclasshook__', '__weakref__', 'address', 'age', 'maxAge', 
'minAge', 'name']

Доступ к атрибутам класса в языке Python

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

имя-экземпляра.имя-атрибута = значение
del имя-экземпляра.имя-атрибута

Альтернативным способом добавления, изменения либо удаления переменной экземпляра является использование встроенных функций Python:

getattr(имя-экземпляра, 'имя-атрибута') - возвращает значение атрибута экземпляра класса;
hasattr(имя-экземпляра, 'имя-атрибута') - возвращает True, если значение атрибута существует в экземпляре, в противном случае возвращает False;
setattr(имя-экземпляра, 'имя-атрибута', значение) - модифицирует существующее значение атрибута либо создает новый атрибут для экземпляра;
delattr(имя-экземпляра, 'имя-атрибута') - удаляет атрибут из экземпляра.

Имена атрибутов, автоматически предоставляемые интерпретатором Python, всегда содержат символ подчеркивания, чтобы обозначить «частный» характер этих атрибутов — их не следует изменять либо удалять. Таким же образом вы можете добавить свои собственные атрибуты, обозначив их как «частные», но не забывайте, что они, как и все другие, могут быть изменены.

Допустим, у нас имеется следующий класс Bird:

class Bird:
    count = 0
    def __init__(self, say):
        self.say = say
        Bird.count += 1
    def talk(self):
        return self.say

1. Начните новую программу на Python, сделав доступными функции класса Bird, созданного в предыдущем примере.

from Bird import *

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

chick = Bird("Cheep, cheep!")
chick.age = "1 month"

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

print( '\nChick Says:' , chick.talk() )
print( 'Chick age:' , chick.age )

4. Измените новый атрибут с помощью точечной записи и выведите его новое значение.

chick.age = "2 month"
print( 'Chick Now:' , chick.age )

5. Опять измените новый атрибут, используя в этот раз встроенную функцию.

setattr(chick, 'age', '3 month')

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

print( '\nChick Attributes...' )
for atttribute in dir( chick ):
    if atttribute[0] != '_':
        print( atttribute, ':', getattr( chick , atttribute ) )

Данный цикл пропускает все атрибуты, чьи имена начинаются с символа подчеркивания, так что «частные» атрибуты как __name__, __doc__ и т.п. не попадут в результирующий список.

7. Наконец удалите новый атрибут и проверьте его отсутствие, используя встроенные функции.

delattr(chick, 'age')
print( '\nChick age Attribute?' , hasattr( chick , 'age' ) )

Весь наш код выглядит следующим образом:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
class Bird:
    count = 0
    def __init__(self, say):
        self.say = say
        Bird.count += 1
    def talk(self):
        return self.say
chick = Bird('Cheep, cheep!')
chick.age = '1 month'
print('\nChick Says:', chick.talk())
print('Chick Age:', chick.age)
chick.age = '2 month'
print('Chick Now:', chick.age)
setattr(chick, 'age', '3 month')
print('\nChick Attributes...')
for attribute in dir(chick):
    if attribute[0] != '_':
        print(attribute, ':', getattr(chick, attribute))
delattr(chick, 'age')
print('\nChick age Attribute?', hasattr(chick, 'age'))

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

Интересное видео по теме для читателей:

Пользовательские атрибуты в Python / Хабр

Вы когда нибудь задумывались о том, что происходит, когда вы ставите точку в python? Что скрывает за собой символ str(“\u002E”)? Какие тайны он хранит? Если без мистики, вы знаете как происходит поиск и установка значений пользовательских атрибутов в python? Хотели бы узнать? Тогда… добро пожаловать!
Чтобы время, проведённое за чтением прошло легко, приятно и с пользой, было бы неплохо знать несколько базовых понятий языка. В частности, понимание type и object будут исключительно полезны, так же как знание нескольких примеров обеих сущностей. Почитать о них можно, в том числе, здесь.
Немного о терминологии, которую я использую, прежде чем мы приступим к тому, ради чего собрались:

  • Объект есть любая сущность в python (функция, число, строка… словом, всё).
  • Класс это объект, чьим типом является type (тип можно подсмотреть в атрибуте __class__).
  • Экземпляр некоторого класса A — это объект, у которого в атрибуте __class__ есть ссылка на класс A.

Ах, да, все примеры в статье написаны на python3! Это определённо следует учесть.
Если ничто из вышесказанного не смогло умерить ваше желание узнать, что там будет дальше, приступим!

__dict__

Атрибуты объекта можно условно разделить две группы: определённые python-ом (такие как __class__, __bases__) и определённые пользователем, о них я как раз собираюсь рассказать. __dict__ согласно этой классификации, относится к “системным” (определённым python-ом) атрибутам. Его задача — хранить пользовательские атрибуты. Он представляет собой dictionary, в котором ключом является имя_атрибута, значением, соответственно, значение_атрибута.
Чтобы найти атрибут объекта o, python обыскивает:

  1. Сам объект (o.__dict__ и его системные атрибуты).
  2. Класс объекта (o.__class__.__dict__). Только __dict__ класса, не системные атрибуты.
  3. Классы, от которых унасаледован класс объекта (o.__class__.__bases__.__dict__).

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

class StuffHolder:
    stuff = "class stuff"

a = StuffHolder()
b = StuffHolder()
a.stuff     # "class stuff"
b.stuff     # "class stuff"

b.b_stuff = "b stuff"
b.b_stuff   # "b stuff"
a.b_stuff   # AttributeError

В примере описан класс StuffHolder с одним атрибутом stuff, который, наследуют оба его экземпляра. Добавление объекту b атрибута b_stuff, никак не отражается на a.
Посмотрим на __dict__ всех действующих лиц:

StuffHolder.__dict__    # {... 'stuff': 'class stuff' ...}
a.__dict__              # {}
b.__dict__              # {'b_stuff': 'b stuff'}

a.__class__             # <class '__main__.StuffHolder'>
b.__class__             # <class '__main__.StuffHolder'>

(У класса StuffHolder в __dict__ хранится объект класса dict_proxy с кучей разного барахла, на которое пока не нужно обращать внимание).

Ни у a ни у b в __dict__ нет атрибута stuff, не найдя его там, механизм поиска ищет его в __dict__ класса (StuffHolder), успешно находит и возвращает значение, присвоенное ему в классе. Ссылка на класс хранится в атрибуте __class__ объекта.

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

a.new_stuff                 # AttributeError
b.new_stuff                 # AttributeError

StuffHolder.new_stuff = "new"
StuffHolder.__dict__        # {... 'stuff': 'class stuff', 'new_stuff': 'new'...}
a.new_stuff                 # "new"
b.new_stuff                 # "new"

В случае присваивания значения атрибуту экземпляра, изменяется только __dict__ экземпляра, то есть значение в __dict__ класса остаётся неизменным (в случае, если значением атрибута класса не является data descriptor):

StuffHolder.__dict__    # {... 'stuff': 'class stuff' ...}
c = StuffHolder()
c.__dict__              # {}

c.stuff = "more c stuff"
c.__dict__              # {'stuff': 'more c stuff'}
StuffHolder.__dict__    # {... 'stuff': 'class stuff' ...}

Если имена атрибутов в классе и экземпляре совпадают, интерпретатор при поиске значения выдаст значение экземпляра (в случае, если значением атрибута класса не является data descriptor):

StuffHolder.__dict__    # {... 'stuff': 'class stuff' ...}
d = StuffHolder()
d.stuff                 # "class stuff"

d.stuff = "d stuff"
d.stuff                 # "d  stuff"

По большому счёту это всё, что можно сказать про __dict__. Это хранилище атрибутов, определённых пользователем. Поиск в нём производится во время выполнения и при поиске учитывается __dict__ класса объекта и базовых классов. Также важно знать, что есть несколько способов переопределить это поведение. Одним из них является великий и могучий Дескриптор!

Дескрипторы

С простыми типами в качестве значений атрибутов пока всё ясно. Посмотрим, как ведёт себя функция в тех же условиях:

class FuncHolder:
    def func(self):
        pass
fh = FuncHolder()

FuncHolder.func     # <function func at 0x8f806ac>
FuncHolder.__dict__ # {...'func': <function func at 0x8f806ac>...}
fh.func             # <bound method FuncHolder.func of <__main__.FuncHolder object at 0x900f08c>>

WTF!? Спросите вы… возможно. Я бы спросил. Чем функция в этом случае отличается от того, что мы уже видели? Ответ прост: методом __get__.

FuncHolder.func.__class__.__get__   # <slot wrapper '__get__' of 'function' objects>

Этот метод переопределяет механизм получения значения атрибута func экземпляра fh, а объект, который реализует этот метод непереводимо называется non-data descriptor.

Из howto:

Дескриптор — это объект, доступ к которому через атрибут переопределён методами в дескриптор протоколе:

descr.__get__(self, obj, type=None) --> value   (переопределяет способ получения значения атрибута)
descr.__set__(self, obj, value) --> None        (переопределяет способ присваивания значения атрибуту)
descr.__delete__(self, obj) --> None            (переопределяет способ удаления атрибута)

Дескрипторы бывают двух видов:

  1. Data Descriptor (дескриптор данных) — объект, который реализует метод __get__() и __set__()
  2. Non-data Descriptor (дескриптор не данных?) — объект, который реализует метод __get__()

Отличаются они своим поведением по отношению к записям в __dict__ экземпляра. Если в __dict__ есть запись с тем же именем, что у дескриптора данных, у дескриптора преимущество. Если имя записи совпадает с именем “дескриптора не данных”, приоритет записи в __dict__ выше.

Дескрипторы данных

Рассмотрим повнимательней дескриптор данных:

class DataDesc:

    def __get__(self, obj, cls):
        print("Trying to access from {0} class {1}".format(obj, cls))

    def __set__(self, obj, val):
        print("Trying to set {0} for {1}".format(val, obj))

    def __delete__(self, obj):
        print("Trying to delete from {0}".format(obj))

class DataHolder:
        data = DataDesc()
d = DataHolder()

DataHolder.data # Trying to access from None class <class '__main__.DataHolder'>
d.data          # Trying to access from <__main__.DataHolder object at ...> class <class '__main__.DataHolder'>
d.data = 1      # Trying to set 1 for <__main__.DataHolder object at ...>
del(d.data)     # Trying to delete from <__main__.DataHolder object at ...>

Стоит обратить внимание, что вызов DataHolder.data передаёт в метод __get__ None вместо экземпляра класса.
Проверим утверждение о том, что у дата дескрипторов преимущество перед записями в __dict__ экземпляра:

d.__dict__["data"] = "override!"
d.__dict__  # {'data': 'override!'}
d.data      # Trying to access from <__main__.DataHolder object at ...> class <class '__main__.DataHolder'>

Так и есть, запись в __dict__ экземпляра игнорируется, если в __dict__ класса экземпляра (или его базового класса) существует запись с тем же именем и значением — дескриптором данных.

Ещё один важный момент. Если изменить значение атрибута с дескриптором через класс, никаких методов дескриптора вызвано не будет, значение изменится в __dict__ класса как если бы это был обычный атрибут:

DataHolder.__dict__ # {...'data': <__main__.DataDesc object at ...>...}
DataHolder.data = "kick descriptor out"
DataHolder.__dict__ # {...'data': 'kick descriptor out'...}
DataHolder.data     # "kick descriptor out"
Дескрипторы не данных

Пример дескриптора не данных:

class NonDataDesc:

    def __get__(self, obj, cls):
        print("Trying to access from {0} class {1}".format(obj, cls))

class NonDataHolder:
    non_data = NonDataDesc()
n = NonDataHolder()

NonDataHolder.non_data  # Trying to access from None class <class '__main__.NonDataHolder'>
n.non_data              # Trying to access from <__main__.NonDataHolder object at ...> class <class '__main__.NonDataHolder'>
n.non_data = 1
n.non_data              # 1
n.__dict__              # {'non_data': 1}

Его поведение слегка отличается от того, что вытворял дата-дескриптор. При попытке присвоить значение атрибуту non_data, оно записалось в __dict__ экземпляра, скрыв таким образом дескриптор, который хранится в __dict__ класса.

Примеры использования

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

class Descriptor:
    def __get__(self, obj, type):
        print("getter used")
    def __set__(self, obj, val):
        print("setter used")
    def __delete__(self, obj):
        print("deleter used")

class MyClass:
    prop = Descriptor()

Или можно воспользоваться встроенным классом property, он представляет собой дескриптор данных. Код, представленный выше можно переписать следующим образом:

class MyClass:

    def _getter(self):
        print("getter used")
    def _setter(self, val):
        print("setter used")
    def _deleter(self):
        print("deleter used")

    prop = property(_getter, _setter, _deleter, "doc string")

В обоих случаях мы получим одинаковое поведение:

m = MyClass()
m.prop          # getter used
m.prop = 1      # setter used
del(m.prop)     # deleter used

Важно знать, что property всегда является дескриптором данных. Если в его конструктор не передать какую либо из функций (геттер, сеттер или делитер), при попытке выполнить над атрибутом соответствующее действие — выкинется AttributeError.

class MySecondClass:
    prop = property()

m2 = MySecondClass()
m2.prop     # AttributeError: unreadable attribute
m2.prop = 1 # AttributeError: can't set attribute
del(m2)     # AttributeError: can't delete attribute

К встроенным дескрипторам также относятся:

  • staticmethod — то же, что функция вне класса, в неё не передаётся экземпляр в качестве первого аргумента.
  • classmethod — то же, что метод класса, только в качестве первого аргумента передаётся класс экземпляра.
class StaticAndClassMethodHolder:

    def _method(*args):
        print("_method called with ", args)
    static = staticmethod(_method)
    cls = classmethod(_method)

s = StaticAndClassMethodHolder()
s._method()     # _method called with (<__main__.StaticAndClassMethodHolder object at ...>,)
s.static()      # _method called with ()
s.cls()         # _method called with (<class '__main__.StaticAndClassMethodHolder'>,)
__getattr__(), __setattr__(), __delattr__() и __getattribute__()

Если нужно определить поведение какого-либо объекта как атрибута, следует использовать дескрипторы (например property). Тоже справедливо для семейства объектов (например функций). Ещё один способ повлиять на доступ к атрибутам: методы __getattr__(), __setattr__(), __delattr__() и __getattribute__(). В отличие от дескрипторов их следует определять для объекта, содержащего атрибуты и вызываются они при доступе к любому атрибуту этого объекта.

__getattr__(self, name) будет вызван в случае, если запрашиваемый атрибут не найден обычным механизмом (в __dict__ экземпляра, класса и т.д.):

class SmartyPants:
    def __getattr__(self, attr):
        print("Yep, I know", attr)
    tellme = "It's a secret"

smarty = SmartyPants()
smarty.name = "Smartinius Smart"

smarty.quicksort    # Yep, I know quicksort
smarty.python       # Yep, I know python
smarty.tellme       # "It's a secret"
smarty.name         # "Smartinius Smart"

__getattribute__(self, name) будет вызван при попытке получить значение атрибута. Если этот метод переопределён, стандартный механизм поиска значения атрибута не будет задействован. Следует иметь ввиду, что вызов специальных методов (например __len__(), __str__()) через встроенные функции или неявный вызов через синтаксис языка осуществляется в обход __getattribute__().

class Optimist:
    attr = "class attribute"

    def __getattribute__(self, name):
        print("{0} is great!".format(name))

    def __len__(self):
        print("__len__ is special")
        return 0

o = Optimist()
o.instance_attr = "instance"

o.attr          # attr is great!
o.dark_beer     # dark_beer is great!
o.instance_attr # instance_attr is great!
o.__len__       # __len__ is great!
len(o)          # __len__ is special\n 0

__setattr__(self, name, value) будет вызван при попытке установить значение атрибута экземпляра. Аналогично __getattribute__(), если этот метод переопределён, стандартный механизм установки значения не будет задействован:

class NoSetters:
    attr = "class attribute"
    def __setattr__(self, name, val):
        print("not setting {0}={1}".format(name,val))

no_setters = NoSetters()
no_setters.a = 1            # not setting a=1
no_setters.attr = 1         # not setting attr=1
no_setters.__dict__         # {}
no_setters.attr             # "class attribute"
no_setters.a                # AttributeError

__delattr__(self, name) — аналогичен __setattr__(), но используется при удалении атрибута.

При переопределении __getattribute__(), __setattr__() и __delattr__() следует иметь ввиду, что стандартный способ получения доступа к атрибутам можно вызвать через object:

class GentleGuy:
    def __getattribute__(self, name):
        if name.endswith("_please"):
            return object.__getattribute__(self, name.replace("_please", ""))
        raise AttributeError("And the magic word!?")

gentle = GentleGuy()

gentle.coffee = "some coffee"
gentle.coffee           # AttributeError
gentle.coffee_please    # "some coffee"
Соль

Итак, чтобы получить значение атрибута attrname экземпляра a в python:

  1. Если определён метод a.__class__.__getattribute__(), то вызывается он и возвращается полученное значение.
  2. Если attrname это специальный (определённый python-ом) атрибут, такой как __class__ или __doc__, возвращается его значение.
  3. Проверяется a.__class__.__dict__ на наличие записи с attrname. Если она существует и значением является дескриптор данных, возвращается результат вызова метода __get__() дескриптора. Также проверяются все базовые классы.
  4. Если в a.__dict__ существует запись с именем attrname, возвращается значение этой записи. Если a — это класс, то атрибут ищется и среди его базовых классов и, если там или в __dict__ a дескриптор данных — возвращается результат __get__() дескриптора.
  5. Проверяется a.__class__.__dict__, если в нём существует запись с attrname и это “дескриптор не данных”, возвращается результат __get__() дескриптора, если запись существует и там не дескриптор, возвращается значение записи. Также обыскиваются базовые классы.
  6. Если существует метод a.__class__.__getattr__(), он вызывается и возвращается его результат. Если такого метода нет — выкидывается AttributeError.

Чтобы установить значение value атрибута attrname экземпляра a:

  1. Если существует метод a.__class__.__setattr__(), он вызывается.
  2. Проверяется a.__class__.__dict__, если в нём есть запись с attrname и это дескриптор данных — вызывается метод __set__() дескриптора. Также проверяются базовые классы.
  3. В a.__dict__ добавляется запись value с ключом attrname.
__slots__

Как пишет Guido в своей истории python о том, как изобретались new-style classes:

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

На случай, если пользователи разочаруются ухудшением производительности, заботливые разработчики python придумали __slots__.
Наличие __slots__ ограничивает возможные имена атрибутов объекта теми, которые там указаны. Также, так как все имена атрибутов теперь заранее известны, снимает необходимость создавать __dict__ экземпляра.

class Slotter:
    __slots__ = ["a", "b"]

s = Slotter()
s.__dict__      # AttributeError
s.c = 1         # AttributeError
s.a = 1
s.a             # 1
s.b = 1
s.b             # 1
dir(s)          # [ ... 'a', 'b' ... ]

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

Заключение

Доступ к атрибутом в python можно контролировать огромным количеством способов. Каждый из них решает свою задачу, а вместе они подходят практически под любой мыслимый сценарий использования объекта. Эти механизмы — основа гибкости языка, наряду с множественным наследованием, метаклассами и прочими вкусностями. У меня ушло некоторое время на то, чтобы разобраться, понять и, главное, принять это множество вариантов работы атрибутов. На первый взгляд оно показалось слегка избыточным и не особенно логичным, но, учитывая, что в ежедневном программировании это редко пригодиться, приятно иметь в своём арсенале такие мощные инструменты.
Надеюсь, и вам эта статья прояснила парочку моментов, до которых руки не доходили разобраться. И теперь, с огнём в глазах и уверенностью в Точке, вы напишите огромное количество наичистейшего, читаемого и устойчивого к изменениям требований кода! Ну или комментарий.

Спасибо за ваше время.

Ссылки
  1. Shalabh Chaturvedi. Python Attributes and Methods
  2. Guido Van Rossum. The Inside Story on New-Style Classes
  3. Python documentation

UPD: Полезный линк от пользователя leron: Python Data Model

Класс и объект в Python

1- Объектно-ориентированный Python

Python — это procedural-oriented (процедурно-ориентированный) язык программирования, а также язык object oriented (объектно-ориентированного) программирования.

Процедурно-ориентированный (Procedural-oriented)

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

Объектно-ориентированный (Object Oriented)

«Объектно-ориентированный» в Python выражается в использовании класса, вы можете определить класс, класс является прототипом (prototype) для создания объектов (object/instance).

2- Создать класс в Python

Синтаксис для создания класса:

** class syntax **

class ClassName:
   'Brief description of the class (Optional)'
   # Code ...
  • Для определения класса используется ключевое слово class, за которым следует имя класса и двоеточие (:). Первая строка в теле класса представляет собой строку (string), которая кратко описывает этот класс. (Необязательно), вы можете получить доступ к этой строке через ClassName .__ doc__
  • В теле класса вы можете объявлять атрибуты (attributes), методы (methods) и конструкторы (constructors).

Атрибуты (Attribute):

Атрибут является членом класса. Например, прямоугольник имеет два атрибута, включая width (ширину) и height (высоту).

Методы (Method):

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

Конструктор (Сonstructor):

  • Constructor — это специальный метод класса, который всегда называется __init__.
  • Первым параметром конструктора всегда является self (ключевое слово ссылается на сам класс).
  • Constructor используется для создания объекта.
  • Constructor присваивает значения параметра свойствам объекта, который будет создан.
  • Вы можете определить максимум один коструктор (constructor) в классе.
  • Если класс е определен конструктором (constructor), то Python по умолчанию считает его унаследованным от коструктора (constructor) родительского класса.

rectangle.py

# Класс симулирующий прямоугольник.
class Rectangle :
    'This is Rectangle class' 
   
    # Метод, используемый для создания объект (Contructor).
    def __init__(self, width, height):
        
        self.width= width
        self.height = height
   
    
    
    def getWidth(self):        
        return self.width
    
    
    def getHeight(self):        
        return self.height

    # Метод расчитывающий площадь.
    def getArea(self):
        
        return self.width * self.height

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

testRectangle.py

from rectangle import Rectangle

# Создать 2 объекта: r1 & r2
r1 = Rectangle(10,5)

r2 = Rectangle(20,11)

print ("r1.width = ", r1.width)
print ("r1.height = ", r1.height)
print ("r1.getWidth() = ", r1.getWidth())
print ("r1.getArea() = ", r1.getArea())

print ("-----------------")

print ("r2.width = ", r2.width)
print ("r2.height = ", r2.height)
print ("r2.getWidth() = ", r2.getWidth())
print ("r2.getArea() = ", r2.getArea())

Что произойдёт когда вы создаёте объект из класса?

Когда вы создаете объект класса Rectangle, конструктор (constructor) этого класса будет вызван для создания объекта, а атрибутам объекта будет присвоено значение из параметра. Это похоже на приведенную ниже иллюстрацию:

3- Параметры по умолчанию в Constructor

В отличие от других языков, класс в Python имеет только один конструктор (constructor). Тем не менее, Python позволяет параметру иметь значение по умолчанию.

Примечание: Все необходимые параметры (required parameters) должны предшествовать всем параметрам со значениями по умолчанию.

person.py

class Person :
  
    # Параметр age и gender имеют значения по умолчанию.
    def __init__ (self, name, age = 1, gender = "Male" ):
        
        self.name = name
        self.age = age 
        self.gender= gender
        
    
    def showInfo(self):
        
        print ("Name: ", self.name)
        print ("Age: ", self.age)
        print ("Gender: ", self.gender)

Например:

testPerson.py

from person import Person

# Создать объект Person.
aimee = Person("Aimee", 21, "Female")


aimee.showInfo()

print (" --------------- ")

# age, gender по умолчанию.
alice = Person( "Alice" )

alice.showInfo()

print (" --------------- ")

# gender по умолчанию.
tran = Person("Tran", 37)

tran.showInfo()

4- Сравнение объектов

В Python, когда вы создаете объект через конструктор (constructor), будет создан реальный объект в памяти, он имеет определенный адрес.
Оператор, назначенный объекту AA объектом BB, не создает в памяти нового объекта, он просто указывает адрес AA к адресу BB.

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

compareObject.py

from rectangle import Rectangle


r1 = Rectangle(20, 10)

r2 = Rectangle(20 , 10)

r3 = r1

# Сравнить адрес r1 и r2
test1 = r1 == r2 # --> False

# Сравнить адрес r1 и  r3
test2 = r1 == r3 # --> True


print ("r1 == r2 ? ", test1)

print ("r1 == r3 ? ", test2)


print (" -------------- ")

print ("r1 != r2 ? ", r1 != r2)

print ("r1 != r3 ? ", r1 != r3)

5- Атрибут (Attribute)

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

  1. Атрибут (Attribute)
  2. Переменная класса

Чтобы упростить это, давайте проанализируем пример ниже:

player.py

class Player:
    
    # Переменная класса.
    minAge  = 18
    
    maxAge = 50
    
    
    def __init__(self, name, age):
        
        self.name = name
        self.age = age

Атрибут (attribute):

Объекты, созданные из одного класса, будут расположены по разным адресам в памяти (memory), а их атрибуты «одинаковое название» также имеют разные адреса в памяти. Как в изображении ниже:

testAttributePlayer.py

from player import Player 


player1 = Player("Tom", 20)

player2 = Player("Jerry", 20)

print ("player1.name = ", player1.name)
print ("player1.age = ", player1.age)


print ("player2.name = ", player2.name)
print ("player2.age = ", player2.age)

print (" ------------ ")

print ("Assign new value to player1.age = 21 ")

# Прикрепить значение атрибуту (attribute) age принадлежащий player1.
player1.age = 21 

print ("player1.name = ", player1.name)
print ("player1.age = ", player1.age)

print ("player2.name = ", player2.name)
print ("player2.age = ", player2.age)

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

testNewAttributePlayer.py

from player import Player 


player1 = Player("Tom", 20)

player2 = Player("Jerry", 20)

# Создать атрибут с названием 'address' для player1.
player1.address = "USA"

print ("player1.name = ", player1.name)
print ("player1.age = ", player1.age)
print ("player1.address = ", player1.address)

print (" ------------------- ")

print ("player2.name = ", player2.name)
print ("player2.age = ", player2.age)

# player2 не имеет атрибута 'address' (Ошибка происходит здесь).
print ("player2.address = ", player2.address)

6- Функции с доступом в атрибут

Как правило, вы получаете доступ к атрибуту объекта через оператор «точка» (например, player1.name). Однако Python позволяет вам иметь к ним доступчерез функцию (function).

Function (функция)Описание
getattr(obj, name[, default])Возвращает значение атрибута или возвращает значение по умолчанию, если объект не имеет этого атрибута.
hasattr(obj,name)Проверяет, имеет ли этот объект атрибут, указанный параметром «name», или нет.
setattr(obj,name,value)Устанавливает значение атрибута. Если атрибут не существует, он будет создан.
delattr(obj, name)Удаляет атрибут.

testAttFunctions.py

from player import Player 

player1 = Player("Tom", 20)

# getattr(obj, name[, default])
print ("getattr(player1,'name') = " , getattr(player1,"name") )


print ("setattr(player1,'age', 21): ")

# setattr(obj,name,value) 
setattr(player1,"age", 21)


print ("player1.age = ", player1.age)

# Проверить имеет ли player1 атрибут (attribute) address или нет?
hasAddress =  hasattr(player1, "address")

print ("hasattr(player1, 'address') ? ", hasAddress)

# Создать атрибут 'address' для обхекта 'player1'.
print ("Create attribute 'address' for object 'player1'")
setattr(player1, 'address', "USA")

print ("player1.address = ", player1.address)

# Удалить атрибут 'address'.
delattr(player1, "address")

7- Готовые атрибуты класса

Классы Python являются потомками класса object. Поэтому они наследуют следующие атрибуты:

АтрибутОписание
__dict__Информация про этот класс легко описана как словарь (Dictionary)
__doc__Возвращает в строчку описания класса, или в None если она не имеет значение.
__class__Возвращает объект,  содержащий информацию класса, этот объект имеет много полезных атрибутов, один из которых__name__.
__module__Возвращает имя модуля (module) класса, или возвращает к «__main__» если этот класс был определён в работающем модуле.

testBuildInAttributes.py

 
class Customer :
    'This is Customer class'
   
    def __init__(self, name, phone, address):
       
        self.name = name
        self.phone = phone
        self.address = address


john = Customer("John",1234567, "USA")
 

print ("john.__dict__ = ", john.__dict__)

print ("john.__doc__ = ", john.__doc__)


print ("john.__class__ = ", john.__class__)

print ("john.__class__.__name__ = ", john.__class__.__name__) 

print ("john.__module__ = ", john.__module__)  

 

8- Переменная класса

В Python понятие «Переменная класса»  (Class’s Variable) эквивалентна понятию статического поля (Static Field) для других языков, таких как Java, CSharp. Доступ к переменным класса можно получить через имя класса или объект.

Совет здесь — вам стоит получить доступ к «Переменной класса» через имя класса вместо объекта. Это помогает избежать путаницы между «Переменной класса» и атрибутом.

Каждая переменная класса имеет адрес в памяти (memory). Он используется совместно со всеми объектами класса.

testVariablePlayer.py

from player import Player 

player1 = Player("Tom", 20)


player2 = Player("Jerry", 20)

# Получить доступ через название класса.
print ("Player.minAge = ", Player.minAge)

# Получить доступ через обхект.
print ("player1.minAge = ", player1.minAge) 
print ("player2.minAge = ", player2.minAge)


print (" ------------ ") 


print ("Assign new value to minAge via class name, and print..")

# Прикрепить новое значение для  minAge через класс.
Player.minAge = 19


print ("Player.minAge = ", Player.minAge) 
print ("player1.minAge = ", player1.minAge) 
print ("player2.minAge = ", player2.minAge)

9- Перечислить членов класса или объекта

Python предоставляет вам функцию dir, эта функция дает список методов, атрибутов, переменных класса или объекта.

testDirFunction.py

from player import Player

# Распечатать список атрибутов (attribute), методов и переменных класса Player.
print ( dir(Player) )


print ("\n\n")

player1 = Player("Tom", 20)

player1.address ="USA"

# Распечатать список атрибутов, методов и переменных обхекта 'player1'.
print ( dir(player1) )

Запуск примера:

[‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘maxAge’, ‘minAge’]

[‘__class__’, ‘__delattr__’, ‘__dict__’, ‘__dir__’, ‘__doc__’, ‘__eq__’, ‘__format__’, ‘__ge__’, ‘__getattribute__’, ‘__gt__’, ‘__hash__’, ‘__init__’, ‘__init_subclass__’, ‘__le__’, ‘__lt__’, ‘__module__’, ‘__ne__’, ‘__new__’, ‘__reduce__’, ‘__reduce_ex__’, ‘__repr__’, ‘__setattr__’, ‘__sizeof__’, ‘__str__’, ‘__subclasshook__’, ‘__weakref__’, ‘address’, ‘age’, ‘maxAge’, ‘minAge’, ‘name’]

 

10- Уничтожить объект

Что такое «атрибуты объекта» в python и вообще?

Допустим, у вас есть класс Person

class Person:
    name = "Samuel"
    age = 50
    country = "India"

    def method1(self):
        print("Method 1")

print(dir(Person))

Вывод вышеприведенной программы выглядит следующим образом:

['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', 
'__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__',
'__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__',
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', 
'__str__', '__subclasshook__', '__weakref__', 
'age', 'country', 'method1', 'name']

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

имя, возраст, страна
— это атрибуты, а метод1 — это метод этого класса. Следовательно, когда вы используете dir (Person), он будет отображать все атрибуты этого класса.

Механизм dir() по умолчанию ведет себя по-разному с различными типами объектов, поскольку он пытается получить наиболее релевантную, а не полную информацию:

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

Например,

a = [1,2,3,45]
print(dir(a))

Это будет печатать:

['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', 
 '__dir__', '__doc__', '__eq__', '__format__', '__ge__', 
 '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', 
 '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', 
 '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', 
 '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', 
 '__setattr__', '__setitem__', '__sizeof__', '__str__', 
 '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 
 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

Здесь вы можете увидеть несколько других имен атрибутов insert, pop, remove и т. д. Если свериться с предыдущим списком, то их там не было. Это потому, что разные типы объектов имеют разные атрибуты , и с этими атрибутами вы можете использовать эти объекты в разных формах.
Вы можете использовать a.pop(2) для удаления элемента и т. д.

Лен , я полагаю, относится к len()

Как правило, len() -это открытый интерфейс, который вы используете для получения длины объекта. Метод __len__ — это реализация, которую должен реализовать объект, поддерживающий концепцию длины. len() звонков __len__()

Мне кажется, что я вижу некоторые функции-члены в возвращаемом списке атрибутов, len , как я предполагаю, относится к len(), но что это за другие перечисленные вещи?

Посетите следующую ссылку, https://docs.python.org/3.6/ ссылка/datamodel. html#объект. реж . Большинство из них перечислены здесь. Там же описаны и обычаи.

Продвинутый Python: 9 важнейших аспектов при определении классов | by Jenny V | NOP::Nuances of Programming

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

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

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

В данной статье представлен обзор 9 актуальных и наилучших практик, которые стоит рассмотреть применительно к пользовательским классам.

1. Правильные имена

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

  • Используйте легко произносимые существительные. Эта рекомендация особенно актуальна при условии совместной работы над проектом. Вряд ли вам захочется оказаться на месте человека, которому во время презентации придется сказать: “В этом случае мы создаем экземпляр класса Zgnehst”. Кроме того, из правила о легко произносимом имени вытекает еще одно, согласно которому оно не должно быть длинным. Сложно представить случаи, когда бы вам потребовалось больше трех слов для определения имени класса. Одно слово — наилучший вариант, два — приемлемый, а три — предельно допустимое количество.
  • Отражайте в имени суть содержащихся данных и предполагаемые функциональности. Все как в реальной жизни — мальчиков нарекают мужскими именами. Когда мы слышим имя Максим, то понимаем, что оно принадлежит мальчику. Этот принцип также применим к именам класса (или в целом любой другой переменной). Правило простое — не вводите людей в замешательство! Если вы работаете с информацией о студентах, то классу следует дать соответствующее имя— Student, а не KiddosAtCampus (Парни из универа), которое не несет должной смысловой нагрузки.
  • Соблюдайте соглашения об именах. Для именования классов рекомендуется использовать верблюжий стиль (горбатый регистр), например так: GoodName. Далее приводится неполный список неприемлемых имен класса: goodName, Good_Name, good_name и GOodnAme. Следование общепринятым правилам написания имен позволит прояснить ваши намерения. В итоге при чтении кода ни у кого не возникнет сомнения, что объект с именем GoodName является классом.

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

2. Явные атрибуты экземпляров

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

class Student:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name

def verify_registration_status(self):
status = self.get_status()
self.status_verified = status == "registered"

def get_guardian_name(self):
self.guardian = "Goodman"

def get_status(self):
# получает статус регистрации из базы данных
status = query_database(self.first_name, self.last_name)
return status

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

class Student:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
self.status_verified = None
self.guardian = None

Если какие-то атрибуты экземпляров нельзя установить изначально, то можно это сделать с помощью значений плейсхолдера, например None. Хотя это и не столь важно, но подобное изменение также помогает предотвратить возможную ошибку, когда вы забываете вызвать методы экземпляра для установки требуемых атрибутов, следствием чего является AttributeError (‘Student’ object has no attribute ‘status_verified’).

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

3. Используем свойства, но не увлекаемся

Некоторые учатся программированию на Pyhton, уже владея другими объектно-ориентированными языками, например Java, и привыкли создавать геттеры и сеттеры для атрибутов экземпляров. В Python этот шаблон можно воспроизвести с помощью декоратора property, и в следующем примере мы рассмотрим основной способ его использования для реализации геттеров и сеттеров.

class Student:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name

@property
def name(self):
print("Getter for the name")
return f"{self.first_name} {self.last_name}"

@name.setter
def name(self, name):
print("Setter for the name")
self.first_name, self.last_name = name.split()

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

>>> student = Student("John", "Smith")
... print("Student Name:", student.name)
... student.name = "Johnny Smith"
... print("After setting:", student.name)
...
Getter for the name
Student Name: John Smith
Setter for the name
Getter for the name
After setting: Johnny Smith

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

class Student:
def __init__(self, first_name, last_name):
self._first_name = first_name
self._last_name = last_name

@property
def first_name(self):
return self._first_name

@property
def last_name(self):
return self._last_name

@property
def name(self):
return f"{self._first_name} {self._last_name}"

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

4. Определение содержательных строковых представлений

Функции, содержащие двойные символы нижнего подчеркивания до и после имени, относятся к особым или магическим методам Python. Они используются в специальных случаях для основных операций интерпретатора, включая и ранее рассмотренный метод __init__. Для создания правильных строковых представлений пользовательского класса необходимы два метода, __repr__ и __str__, благодаря которым читающие ваш код получат более интуитивно понятную информацию о классах.

Главное их отличие в том, что метод __repr__ определяет строку, с помощью которой вы можете пересоздать объект, вызвав eval(repr(“the repr”)), тогда как строка, определяемая методом __str__, является более описательной и предоставляет больше возможностей для кастомизации. Иначе говоря, строка, используемая в методе __repr__, предназначена для просмотра разработчиками, а строка в методе __str__ — для обычных пользователей. Рассмотрим следующий пример реализации строковых представлений.

class Student:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name

def __repr__(self):
return f"Student({self.first_name!r}, {self.last_name!r})"

def __str__(self):
return f"Student: {self.first_name} {self.last_name}"

Обратите внимание, что при реализации метода __repr__ f-строка использует !r, в результате чего эти строки отображаются в кавычках, поскольку они необходимы для создания экземпляра с правильно отформатированными строками. Без !r-форматирования строка будет выглядеть как Student(John, Smith), что будет неверным способом создания экземпляра Student. Посмотрим, в каком виде предстают строки в этих реализациях. Если быть более конкретным, то метод __repr__ вызывается при обращении к объекту в интерактивном режиме интерпретатора, а метод __str__ — по умолчанию при выводе объекта.

>>> student = Student("David", "Johnson")
>>> student
Student('David', 'Johnson')
>>> print(student)
Student: David Johnson

5.Статические методы, методы экземпляра и класса

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

В случаях, когда речь идет об отдельных объектах экземпляра, например при необходимости обновления его конкретных атрибутов или обращения к ним, следует задействовать методы экземпляра. Их сигнатура выглядит следующим образом: def do_something(self):, где аргумент self относится к объекту экземпляра, которые вызывает метод.

В других же случаях следует применять статические методы или методы класса. И тот, и другой можно легко определить с помощью соответствующих декораторов: staticmethod и classmethod. Отличаются эти методы тем, что метод классов позволяет вам обратиться к атрибутам класса или обновить их, а статические методы не зависят от экземпляров или самого класса. Типичный пример метода класса — обеспечение удобного способа создания экземпляра, тогда как статический метод может быть просто вспомогательной функцией. В следующем коде приводятся примеры разных видов методов.

class Student:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name

def begin_study(self):
print(f"{self.first_name} {self.last_name} begins studying.")

@classmethod
def from_dict(cls, name_info):
first_name = name_info['first_name']
last_name = name_info['last_name']
return cls(first_name, last_name)

@staticmethod
def show_duties():
return "Study, Play, Sleep"

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

6. Инкапсуляция при помощи приватных атрибутов

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

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

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

class Student:
def get_mean_gpa(self):
grades = self._get_grades()
gpa_list = Student._converted_gpa_from_grades(grades)
return sum(gpa_list) / len(gpa_list)

def _get_grades(self):
# получает баллы из базы данных
grades = [99, 100, 94, 88]
return grades

@staticmethod
def _converted_gpa_from_grades(grades):
# преобразует баллы в GPA (средний балл)
gpa_list = [4.0, 4.0, 3.7, 3.4]
return gpa_list

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

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

7. Разделение задач и уменьшение зацепления

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

class Student:
def __init__(self, first_name, last_name, student_id):
self.first_name = first_name
self.last_name = last_name
self.student_id = student_id

def check_account_balance(self):
account_number = get_account_number(self.student_id)
balance = get_balance(account_number)
return balance

def load_money(self, amount):
account_number = get_account_number(self.student_id)
balance = get_balance(account_number)
balance += amount
update_balance(account_number, balance)

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

class Student:
def __init__(self, first_name, last_name, student_id):
self.first_name = first_name
self.last_name = last_name
self.student_id = student_id
self.account = Account(self.student_id)

def check_account_balance(self):
return self.account.get_balance()

def load_money(self, amount):
self.account.load_money(amount)

class Account:
def __init__(self, student_id):
self.student_id = student_id
# получает дополнительную информацию из базы данных
self.balance = 400

def get_balance(self):
# Теоретически student.account.balance сработает, но на всякий случай
# нам необходимо добавить шаги для проверки, такие как запрос базы данных,
# и еще раз убедиться, что база актуальна
return self.balance

def load_money(self, amount):
# получает баланс из базы данных
self.balance += amount
self.save_to_database()

Этот код показывает, как можно спроектировать структуры данных с помощью дополнительного класса Account. Как видно, мы переместили в него все связанные со счетом операции. Для извлечения необходимых данных класс Student будет обрабатывать функциональность, обращаясь к информации в классе Account. Если мы захотим реализовать больше функций, относящихся к классу, то сможем просто обновить класс Account.

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

8. __slots__ для оптимизации

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

class StudentRegular:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name

class StudentSlot:
__slots__ = ['first_name', 'last_name']

def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name

Данный код показывает простой пример реализации __slots__ в классе. Точнее говоря, вы составляете последовательность из всех атрибутов, которая произведет в хранилище данных сопоставление один-к-одному, ускорив доступ и снизив затраты памяти. Как мы недавно отметили, обычные классы для доступа к атрибутам используют словари, что не касается случаев с реализацией __slots__. Это демонстрирует следующий пример кода, где в классе со __slots__ отсутствует __dict__:

>>> student_r = StudentRegular('John', 'Smith')
>>> student_r.__dict__
{'first_name': 'John', 'last_name': 'Smith'}
>>> student_s = StudentSlot('John', 'Smith')
>>> student_s.__dict__
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'StudentSlot' object has no attribute '__dict__'

Однако применение __slots__ сопровождается одним побочным эффектом — оно препятствует динамическому созданию дополнительных атрибутов. Некоторые предлагают использовать его в качестве механизма контроля наличия атрибутов в классе, но это не отвечает его назначению.

9. Документация

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

# количество расчетных часов 
a = 6
# почасовая оплата
b = 100
# общая сумма
c = a * b

# Альтернатива приведенной выше версии без комментариев

billable_hours = 6
hourly_rate = 100
total_charge = billable_hours * hourly_rate

Это совсем не значит, что я против написания комментариев и строк документации — все зависит от конкретных случаев. Если ваш код используется несколькими людьми или по нескольким поводам (например, вы один обращаетесь к коду, но по нескольку раз), то стоит написать к нему хорошие комментарии. Они могут помочь вам или вашим коллегам прочитать код, но никто не должен предполагать, что ваш код делает именно то, что сказано в комментариях. Иначе говоря, написание хорошего кода — это всегда приоритетная задача, о которой нельзя забывать.

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

Заключение

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

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен

Атрибуты класса Python: примеры переменных

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

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

Я глубоко вздохнул и начал печатать. Через несколько строк у меня было что-то вроде этого:

  класс Услуга (объект):
    данные = []

    def __init __ (self, other_data):
        себя.other_data = другие_данные
    ...
  

Меня остановил интервьюер:

  • Опрашивающий: «Эта строка: data = [] . Я не думаю, что это правильный Python? »
  • Я: «Я почти уверен, что это так. Он просто устанавливает значение по умолчанию для атрибута экземпляра ».
  • Опрашивающий: «Когда выполняется этот код?»
  • Я: «Я не совсем уверен. Я просто исправлю это, чтобы избежать путаницы «.

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

  класс Услуга (объект):

    def __init __ (self, other_data):
        себя.данные = []
        self.other_data = other_data
    ...
  

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

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

Атрибуты класса Python

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

Я тоже ошибался в том, что он не устанавливает «значение по умолчанию» для атрибута экземпляра. Вместо этого он определяет данных как атрибут класса со значением [] .

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

Переменная класса

Python и переменная экземпляра: в чем разница?

Атрибут класса Python — это атрибут класса (круговой, я знаю), а не атрибут экземпляра класса.

Давайте воспользуемся примером класса Python, чтобы проиллюстрировать разницу. Здесь class_var — это атрибут класса, а i_var — это атрибут экземпляра:

  класс MyClass (объект):
    class_var = 1

    def __init __ (self, i_var):
        self.i_var = i_var
  

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

  foo = MyClass (2)
bar = MyClass (3)

фу.class_var, foo.i_var
## 1, 2
bar.class_var, bar.i_var
## 1, 3
MyClass.class_var ## <- Это ключ
## 1
  

Для программистов на Java или C ++ атрибут класса подобен статическому члену, но не идентичен ему. Чем они отличаются, мы увидим позже.

Класс и пространство имен экземпляра

Чтобы понять, что здесь происходит, давайте кратко поговорим о пространствах имен Python .

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

В зависимости от контекста вам может потребоваться доступ к пространству имен с использованием точечного синтаксиса (например, object.name_from_objects_namespace ) или в качестве локальной переменной (например, object_from_namespace ). В качестве конкретного примера:

  класс MyClass (объект):
    ## Нет необходимости в точечном синтаксисе
    class_var = 1

    def __init __ (self, i_var):
        self.i_var = i_var

## Нужен точечный синтаксис, поскольку мы покинули область пространства имен классов
Мои занятия.class_var
## 1
  

Классы Python и экземпляры классов имеют свои собственные отдельные пространства имен, представленные предопределенными атрибутами MyClass .__ dict__ и instance_of_MyClass .__ dict__ соответственно.

Когда вы пытаетесь получить доступ к атрибуту из экземпляра класса, он сначала смотрит на его пространство имен instance . Если он находит атрибут, он возвращает связанное значение. Если нет, то это , а затем просматривает пространство имен class и возвращает атрибут (если он присутствует, в противном случае выдается ошибка).Например:

  foo = MyClass (2)

## Находит i_var в пространстве имен экземпляра foo
foo.i_var
## 2

## Не находит class_var в пространстве имен экземпляра…
## Итак, посмотрите в пространство имен класса (MyClass .__ dict__)
foo.class_var
## 1
  

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

  def instlookup (inst, имя):
    ## упрощенный алгоритм...
    если inst .__ dict __. has_key (name):
        return inst .__ dict __ [имя]
    еще:
        вернуть inst .__ class __.__ dict __ [имя]
  

А, в наглядной форме:

Как атрибуты класса обрабатывают присвоение

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

  • Если атрибут класса установлен путем доступа к классу, он переопределит значение для всех экземпляров . Например:

      foo = MyClass (2)
    фу.class_var
    ## 1
    MyClass.class_var = 2
    foo.class_var
    ## 2
      

    На уровне пространства имен… мы устанавливаем MyClass .__ dict __ ['class_var'] = 2 . (Примечание: это не точный код (это будет setattr (MyClass, 'class_var', 2) ), поскольку __dict__ возвращает dictproxy, неизменяемую оболочку, которая предотвращает прямое назначение, но помогает для демонстрации) . Затем, когда мы обращаемся к foo.class_var , class_var имеет новое значение в пространстве имен классов, и поэтому возвращается 2.

  • Если переменная класса Paython установлена ​​путем доступа к экземпляру, она переопределит значение только для этого экземпляра . Это существенно переопределяет переменную класса и превращает ее в переменную экземпляра, доступную интуитивно только для этого экземпляра . Например:

      foo = MyClass (2)
    foo.class_var
    ## 1
    foo.class_var = 2
    foo.class_var
    ## 2
    MyClass.class_var
    ## 1
      

    На уровне пространства имен… мы добавляем атрибут class_var в foo.__dict__ , поэтому, когда мы ищем foo.class_var , мы возвращаем 2. Между тем, другие экземпляры MyClass будут иметь , а не , будут иметь class_var в своих пространствах имен экземпляров, поэтому они продолжают находить class_var в MyClass. __dict__ и таким образом вернуть 1.

Изменчивость

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

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

  класс Услуги (объект):
    данные = []

    def __init __ (self, other_data):
        self.other_data = other_data
    ...
  

Моя цель состояла в том, чтобы иметь пустой список ( [] ) в качестве значения по умолчанию для данных , а для каждого экземпляра службы иметь свои собственные данные , которые со временем будут изменены на экземпляре - -экземплярная основа.Но в этом случае мы получаем следующее поведение (напомним, что Service принимает некоторый аргумент other_data , который в этом примере произвольный):

  s1 = Сервис (['a', 'b'])
s2 = Сервис (['c', 'd'])

s1.data.append (1)

s1.data
## [1]
s2.data
## [1]

s2.data.append (2)

s1.data
## [1, 2]
s2.data
## [1, 2]
  

Это бесполезно - изменение переменной класса через один экземпляр изменяет ее для всех остальных!

На уровне пространства имен… все экземпляры Service обращаются к одному и тому же списку в Service и изменяют его.__dict__ без создания собственных атрибутов data в пространствах имен своих экземпляров.

Мы могли бы обойти это, используя присваивание; то есть, вместо того, чтобы использовать изменчивость списка, мы могли бы назначить нашим объектам Service свои собственные списки, как показано ниже:

  s1 = Сервис (['a', 'b'])
s2 = Сервис (['c', 'd'])

s1.data = [1]
s2.data = [2]

s1.data
## [1]
s2.data
## [2]
  

В этом случае мы добавляем s1 .__ dict __ ['data'] = [1] , то есть исходную службу .__dict __ ['data'] остается без изменений.

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

Мое личное решение: если вы просто используете переменную класса для присвоения значения по умолчанию потенциальной переменной экземпляра Python, не используйте изменяемые значения .В этом случае каждый экземпляр Service собирался в конечном итоге переопределить Service.data собственным атрибутом экземпляра, поэтому использование пустого списка по умолчанию привело к крошечной ошибке, которую легко упустить. Вместо приведенного выше мы могли бы использовать:

  1. Полностью привязан к атрибутам экземпляра, как показано во введении.
  2. Избегайте использования пустого списка (изменяемое значение) по умолчанию:

      класс Услуга (объект):
        data = None
    
        def __init __ (self, other_data):
            себя.other_data = другие_данные
        ...
      

    Конечно, мы должны будем обработать случай None надлежащим образом, но это небольшая цена.

Итак, когда следует использовать атрибуты класса Python?

Атрибуты класса

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

  1. Сохранение констант . Поскольку к атрибутам класса можно получить доступ как к атрибутам самого класса, часто бывает удобно использовать их для хранения общеклассовых констант, зависящих от класса.Например:

      класс Круг (объект):
        пи = 3,14159
    
        def __init __ (себя, радиус):
            self.radius = радиус
    
        область определения (self):
            вернуть Circle.pi * self.radius * self.radius
    
    Circle.pi
    ## 3.14159
    
    c = Круг (10)
    c.pi
    ## 3.14159
    c.area ()
    ## 314.159
      
  2. Определение значений по умолчанию . В качестве тривиального примера мы могли бы создать ограниченный список (то есть список, который может содержать только определенное количество элементов или меньше) и выбрать ограничение по умолчанию в 10 элементов:

      класс MyClass (объект):
        limit = 10
    
        def __init __ (сам):
            себя.данные = []
    
        def item (self, i):
            вернуть self.data [i]
    
        def add (self, e):
            если len (self.data)> = self.limit:
                поднять исключение ("Слишком много элементов")
            self.data.append (e)
    
    MyClass.limit
    ## 10
      

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

      foo = MyClass ()
    foo.limit = 50
    ## foo теперь может содержать 50 элементов - другие экземпляры могут содержать 10
      

    Это имеет смысл только в том случае, если вы хотите, чтобы ваш типичный экземпляр MyClass содержал всего 10 элементов или меньше - если вы задаете всем своим экземплярам разные ограничения, тогда limit должен быть переменной экземпляра.(Однако помните: будьте осторожны при использовании изменяемых значений по умолчанию.)

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

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

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

      класс Человек (объект):
        all_names = []
    
        def __init __ (я, имя):
            self.name = имя
            Person.all_names.append (имя)
    
    Джо = Человек ('Джо')
    bob = Человек ('Боб')
    распечатать Person.all_names
    ## ['Джо', 'Боб']
      

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

      класс Человек (объект):
        all_people = []
    
        def __init __ (я, имя):
            self.name = имя
            Person.all_people.append (сам)
    
    Джо = Человек ('Джо')
    bob = Человек ('Боб')
    печать Person.all_people
    ## [<__ main __. Объект Person в 0x10e428c50>, <__ main __. Объект Person в 0x10e428c90>]
      
  4. Исполнение (вроде… см. Ниже).

Под капотом

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

Напомним, что пространство имен класса создается и заполняется во время определения класса. Это означает, что мы выполняем только одно присвоение - из - для данной переменной класса, в то время как переменные экземпляра должны назначаться каждый раз при создании нового экземпляра. Возьмем пример.

  def call_class ():
    распечатать "Назначение класса"
    возврат 2

класс Bar (объект):
    y = вызываемый_класс ()

    def __init __ (self, x):
        self.x = x

## "Назначение класса"

def named_instance ():
    print "Назначение экземпляра"
    возврат 2

класс Foo (объект):
    def __init __ (self, x):
        себя.y = вызываемый_экземпляр ()
        self.x = x

Бар (1)
Бар (2)
Фу (1)
## "Назначение экземпляра"
Фу (2)
## "Назначение экземпляра"
  

Мы назначаем Bar.y только один раз, а instance_of_Foo.y при каждом вызове __init__ .

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

  импорт

класс Bar (объект):
    у = 2

    def __init __ (self, x):
        self.x = x

класс Foo (объект):
    def __init __ (self, x):
        себя.у = 2
        self.x = x

dis.dis (Бар)
## Дизассемблирование __init__:
## 7 0 LOAD_FAST 1 (x)
## 3 LOAD_FAST 0 (самостоятельно)
## 6 STORE_ATTR 0 (x)
## 9 LOAD_CONST 0 (Нет)
## 12 RETURN_VALUE

dis.dis (Foo)
## Дизассемблирование __init__:
## 11 0 LOAD_CONST 1 (2)
## 3 LOAD_FAST 0 (самостоятельно)
## 6 STORE_ATTR 0 (г)

## 12 9 LOAD_FAST 1 (x)
## 12 LOAD_FAST 0 (самостоятельно)
## 15 STORE_ATTR 1 (x)
## 18 LOAD_CONST 0 (Нет)
## 21 RETURN_VALUE
  

Когда мы смотрим на байт-код, снова становится очевидным, что Foo.__init__ должен выполнять два назначения, а Bar .__ init__ выполняет только одно.

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

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

Примечание. Я использую MacBook Pro с OS X 10.8.5 и Python 2.7.2.

Инициализация

  10000000 звонков в `Bar (2)`: 4.940 с
10000000 звонков в `Foo (2)`: 6.043 с
  

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

Так почему это так? Одно предположительное объяснение : мы выполняем два присваивания в Foo .__ init__ , но только одно в Bar.__init__ .

Переуступка

  10000000 звонков в `Bar (2) .y = 15`: 6,232 с
10000000 обращений к `Foo (2) .y = 15`: 6,855 с
10000000 присвоений `Bar`: 6,232 с - 4,940 с = 1,292 с
10000000 присваиваний `Foo`: 6,855 с - 6,043 с = 0,812 с
  

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

Из вышесказанного, похоже, что Foo занимает всего около 60% от длины Bar для обработки назначений.

Почему это так? Одно предположительное объяснение : когда мы назначаем Bar (2) .y , мы сначала смотрим в пространство имен экземпляра ( Bar (2) .__ dict __ [y] ), не можем найти y , а затем смотрим в пространстве имен класса ( Bar .__ dict __ [y] ), а затем выполните правильное присвоение. Когда мы присваиваем Foo (2).y , мы выполняем вдвое меньше запросов, чем сразу назначаем пространству имен экземпляра ( Foo (2) .__ dict __ [y] ).

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

Заключение

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

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

Приложение : Переменные частного экземпляра

Одна вещь, которую я хотел включить, но у меня не было естественной точки входа…

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

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

  класс Бар (объект):
    def __init __ (сам):
    self .__ zap = 1

a = Бар ()
a .__ zap
## Traceback (последний вызов последний):
## Файл "", строка 1, в 
## AttributeError: объект 'Bar' не имеет атрибута '__baz'

## Хм.Так что в пространстве имен?
а .__ dict__
{'_Bar__zap': 1}
a._Bar__zap
## 1
  

Посмотрите на это: атрибут экземпляра __zap автоматически получает префикс с именем класса, что дает _Bar__zap .

Хотя все еще можно установить и получить с помощью a._Bar__zap , это изменение имени является средством создания «частной» переменной, поскольку оно не позволяет вам и другим получить к ней доступ случайно или по незнанию.

Edit: как любезно заметил Педро Вернек, это поведение в значительной степени предназначено для помощи при создании подклассов.В руководстве по стилю PEP 8 они видят, что это служит двум целям: (1) предотвращение доступа подклассов к определенным атрибутам и (2) предотвращение конфликтов пространств имен в этих подклассах. Несмотря на свою полезность, изменение переменных не должно рассматриваться как приглашение к написанию кода с предполагаемым различием между публичным и частным, как это присутствует в Java.

Атрибут класса

и атрибут экземпляра в Python: что вы могли пропустить

Как объектно-ориентированный язык Python предоставляет две области для атрибутов: атрибуты класса и атрибуты экземпляра.

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

Атрибут класса и атрибут экземпляра

Начнем с основ:

  • Атрибут экземпляра - это переменная Python, принадлежащая одному и только одному объекту.Эта переменная доступна только в области видимости этого объекта и определяется внутри функции-конструктора, __init __ (self, ..) класса.

  • Атрибут класса - это переменная Python, которая принадлежит классу, а не конкретному объекту. Он распределяется между всеми объектами этого класса и определяется вне функции конструктора, __init __ (self, ...) , класса.

Приведенный ниже ExampleClass является базовым классом Python с двумя атрибутами: class_attr и instance_attr .

  класс ExampleClass (объект):
  class_attr = 0

  def __init __ (self, instance_attr):
    self.instance_attr = instance_attr  

instance_attr доступен только из области видимости объекта. Атрибут класса ( class_attr ) доступен как свойство класса, так и как свойство объектов, поскольку он является общим для всех них.

 , если __name__ == '__main__':

foo = ExampleClass (1)
bar = ExampleClass (2)

    # выводим атрибут экземпляра объекта foo
    печать (foo.istance_attr)
    # 1
    # распечатать атрибут экземпляра объекта var
    печать (bar.instance_attr)
    # 2
    # распечатать атрибут класса класса ExampleClass как свойство самого класса
    печать (ExampleClass.class_attr)
    # 0
    # распечатать атрибут класса как свойство объектов foo, bar
    печать (bar.class_attr)
    # 0
    печать (foo.class_attr)
    # 0
    # пытаемся распечатать атрибут экземпляра как свойство класса
    печать (ExampleClass.instance_attr)
    #AttributeError: объект типа 'ExampleClass' не имеет атрибута 'instance_attr'

  

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

За кадром

За кулисами это игра пространств имен. Если вы уже читали «Дзен Python», последняя строка гласит: «Пространства имен - одна отличная идея - давайте сделаем их больше!» Итак, что такое пространство имен?

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

Классы и объекты

Python имеют разные пространства имен, например, у нас есть ExampleClass .__ dict__ в качестве пространства имен для нашего класса и foo .__ dict __ (bar .__ dict__) в качестве пространства имен для нашего объекта foo (bar) .

 , если __name__ = '__main__':
 foo = ExampleClass (1)
    bar = ExampleClass (2)

    print str (ExampleClass .__ dict__)
    # {'__ module__': '__main__', 'class_attr': 0, '__dict__': <атрибут '__dict__' объектов 'ExampleClass'>, '__weakref__': , '__doc__ ': Нет,' __init__ ': <функция __init__ в 0x031192F0>}
    печать str (foo.__dict__)
    # {'instance_attr': 1}
    печать str (bar .__ dict__)
    # {'instance_attr': 2}  

Когда вы обращаетесь к атрибуту (экземпляру или атрибуту класса) как к свойству объекта, используя точечное соглашение, он сначала выполняет поиск в пространстве имен этого объекта для этого имени атрибута. Если он найден, он возвращает значение, в противном случае он ищет в пространстве имен класса. Если и там ничего не найдено, возникает AttributeError . Пространство имен объекта находится перед пространством имен класса.

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

  def instlookup (inst, имя):
    ## упрощенный алгоритм ...
    если inst .__ dict __. has_key (name):
        return inst .__ dict __ [имя]
    еще:
        return inst .__ class __.__ dict __ [имя]  

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

Атрибуты класса изменяются на атрибуты экземпляра

Да, это может показаться странным, но они есть!

Давайте рассмотрим следующий сценарий, а затем вместе прокомментируем его.

 , если __name__ = '__main__':
 foo = ExampleClass (1)
    bar = ExampleClass (2)

    # распечатать атрибут класса как свойство foo
    напечатать foo.class_attr
    # 0
    # изменить атрибут класса как свойство foo
    фу.class_attr = 5
    напечатать foo.class_attr
    # 5
    # распечатать атрибут класса как атрибут бара
    печать bar.class_attr
    # 0
    #oups !!!!

  

class_attr используется всеми объектами класса. Однако, когда мы изменили значение из объекта foo , это изменение не было видно из объекта bar , который все еще имеет старое значение, 0 , а не новое значение, 5 ! Погодите ... давайте проверим наши пространства имен и попытаемся понять, что, черт возьми, происходит.

 , если __name__ = '__main__':
 foo = ExampleClass (1)
    bar = ExampleClass (2)


    печать str (foo .__ dict__)
    # {'instance_attr': 1} пространство имен объекта foo имело только его атрибут экземпляра
    напечатать foo.class_attr
    # 0
    foo.class_attr = 5
    печать str (foo .__ dict__)
    '' '{' instance_attr ': 1,' class_attr ': 5} после завершения обработки
    в атрибуте класса из объекта foo он добавляется как атрибут экземпляра
    в его пространстве имен
    '' '
  

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

С неизменяемыми объектами это поведение всегда одинаково. Однако с изменяемыми объектами, такими как списки, например, это не всегда так, в зависимости от того, как вы изменяете свой атрибут класса.

Давайте изменим наш предыдущий класс, чтобы он имел список в качестве атрибута класса.

  класс ExampleClass (объект):
  class_attr = []

  def __init __ (self, instance_attr):
    self.instance_attr = instance_attr  

Мы изменяем этот список как свойство объекта foo , добавляя к нему новый элемент.

 , если __name__ = '__main__':
 foo = ExampleClass (1)
    bar = ExampleClass (2)

    # распечатать атрибут класса как свойство foo
    напечатать foo.class_attr
    # []
    # изменить атрибут класса как свойство foo
    foo.class_attr.append (0)
    напечатать foo.class_attr
    # [0]
    # распечатать атрибут класса как атрибут бара
    печать bar.class_attr
    # [0]
  

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

Однако, если вы прикрепите новый список к этому атрибуту ( foo.class_attr = list ("foo") ), вы получите то же поведение, что и неизменяемые объекты.

 , если __name__ = '__main__':
 foo = ExampleClass (1)
    bar = ExampleClass (2)

    # распечатать атрибут класса как свойство foo
    напечатать foo.class_attr
    # []
    # изменить атрибут класса как свойство foo
    фу.class_attr = list ("пример")
    напечатать foo.class_attr
    #[пример]
    # распечатать атрибут класса как атрибут бара
    печать bar.class_attr
    # []  

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

Заключение

Атрибуты класса

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

Определение классов в Python

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

  >>> число = 20
>>> тип (число)
<класс 'int'>
>>> s = "Python"
>>> тип (ы)
<класс 'str'>
  

Определение класса

Класс в Python можно определить с помощью ключевого слова class .

  класс :
    
    
    .
    .
    <заявлениеN>
  

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

  1. Атрибуты класса
  2. Конструктор
  3. Атрибуты экземпляра
  4. Характеристики
  5. Методы класса

Класс также можно определить без каких-либо членов.В следующем примере определяется пустой класс с использованием ключевого слова pass .

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

Выше Student () возвращает объект класса Student , который назначен локальной переменной std .Класс Student - пустой класс, потому что он не содержит никаких членов.

Атрибуты класса

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

  класс Студент:
    schoolName = 'Школа XYZ'
  

Выше schoolName - это атрибут класса, определенный внутри класса.Значение schoolName останется неизменным для всех объектов, если не будет изменено явно.

  >>> Student.schoolName
"Школа XYZ"
>>> std = Студент ()
>>> std.schoolName
"Школа XYZ"
  

Как видите, к атрибуту класса обращается Student.schoolName , а также std.schoolName .
Изменение значения атрибута класса с использованием имени класса изменит его во всех экземплярах.Однако изменение значения атрибута класса с использованием экземпляра не отразится на других экземплярах или классе.

  >>> Student.schoolName = 'ABC School' # изменить значение атрибута, используя имя класса
>>> std = Студент ()
>>> std.schoolName
Значение "ABC School" # изменено для всех экземпляров
>>> std.schoolName = 'My School' # изменение атрибута экземпляра
>>> std.schoolName
'Моя школа'
>>> Студент.schoolName # изменение уровня экземпляра не отражено в атрибуте класса
"Школа ABC"
>>> std2 = Студент ()
>>> std2.schoolName
"Школа ABC"
  

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

  класс Студент:
    count = 0
    def __init __ (сам):
        Ученик.count + = 1
  

В приведенном выше примере count - это атрибут в классе Student.
Каждый раз, когда создается новый объект, значение count увеличивается на 1.
Теперь вы можете получить доступ к атрибуту count после создания объектов, как показано ниже.

  >>> std1 = Студент ()
>>> Студент.считать
1
>>> std2 = Студент ()
>>> Student.count
2
  

Конструктор

В Python метод конструктора вызывается автоматически всякий раз, когда создается новый объект класса, так же, как конструкторы в C # или Java.
Конструктор должен иметь специальное имя __init __ () и специальный параметр self .

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

В следующем примере определяется конструктор.

  класс Студент:
    def __init __ (self): # метод конструктора
        print ('Конструктор вызван')
  

Теперь, когда вы создаете объект класса Student , будет вызываться метод конструктора __init __ () , как показано ниже.

  >>> s1 = Студент ()
Конструктор вызван
>>> s2 = Студент ()
Конструктор вызван
  

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

Атрибуты экземпляра

Атрибуты экземпляра - это атрибуты или свойства, прикрепленные к экземпляру класса.Атрибуты экземпляра определяются в конструкторе.

В следующем примере в конструкторе определяются атрибуты экземпляра name и age .

  класс Студент:
    schoolName = 'XYZ School' # атрибут класса

    def __init __ (self): # конструктор
        себя.name = '' # атрибут экземпляра
        self.age = 0 # атрибут экземпляра
  

Доступ к атрибуту экземпляра можно получить, используя точечную нотацию: [имя экземпляра]. [Имя атрибута] , как показано ниже.

  >>> std = Студент ()
>>> std.name
''
>>> std.age
0
  

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

  >>> std = Студент ()
>>> std.name = "Bill" # присвоить значение атрибуту экземпляра
>>> std.age = 25 # присвоить значение атрибуту экземпляра
>>> std.name # значение атрибута экземпляра доступа
Билл
>>> std.age # значение доступа к атрибуту экземпляра
25
  

Вы можете указать значения атрибутов экземпляра через конструктор.Следующий конструктор включает параметры name и age, кроме параметра self .

  класс Студент:
    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст
  

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

  >>> std = Студент ('Bill', 25)
>>> std.name
'Билл'
>>> std.age
25
  

Вам не нужно указывать значение параметра self . Он будет назначен внутри Python.

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

  класс Студент:
    def __init __ (self, name = "Гость", возраст = 25)
        self.name = имя
        self.age = возраст
  

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

  >>> std = Студент ()
>>> std.name
'Гость'
>>> std.age
25
  

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

Свойства класса

В Python свойство в классе можно определить с помощью функции property ().

Метод property () в Python предоставляет интерфейс для атрибутов экземпляра.
Он инкапсулирует атрибуты экземпляра и предоставляет свойство, как в Java и C #.

Метод property () принимает в качестве аргументов методы get, set и delete и возвращает объект класса property .

В следующем примере показано, как создать свойство в Python с помощью функции property () .

  класс Студент:
    def __init __ (сам):
        сам .__ имя = ''
    def setname (self, name):
        print ('setname () называется')
        self .__ name = имя
    def getname (сам):
        print ('getname () называется')
        вернуть self .__ name
    имя = свойство (getname, setname)
  

В приведенном выше примере свойство (getname, setname) возвращает объект свойства и присваивает ему name .Таким образом, свойство name скрывает атрибут частного экземпляра __name .
Доступ к свойству name осуществляется напрямую, но внутренне оно вызывает метод getname () или setname () , как показано ниже.

  >>> std = Студент ()
>>> std.name = "Стив"
setname () называется
>>> std.name
getname () называется
'Стив'
  

Рекомендуется использовать декоратор свойств вместо метода property () .

Методы класса

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

  класс Студент:
    def displayInfo (self): # метод класса
        print ("Информация о студенте")
  

Self - это просто условное имя для первого аргумента метода в классе.Метод, определенный как mymethod (self, a, b) , должен вызываться как x.mymethod (a, b) для объекта x класса.

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

  >>> std = Студент ()
>>> std.displayInfo ()
"Информация для студентов"
  

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

  класс Студент:
    def displayInfo (obj): # метод класса
        print ("Информация о студенте")
  

Определение метода в классе без параметра self вызовет исключение при вызове метода.

  класс Студент:
    def displayInfo (): # метод без параметра self
        print ("Информация о студенте")
  
  >>> std = Студент ()
>>> std.displayInfo ()
Отслеживание (последний вызов последний):
std.displayInfo ()
TypeError: displayInfo () принимает 0 позиционных аргументов, но дан 1
  

Метод может получить доступ к атрибутам экземпляра с помощью параметра self .

  класс Студент:
    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст
    def displayInfo (self): # метод класса
        print ('Имя ученика:', self.name, ', Age:', self.age)
  

Теперь вы можете вызвать метод, как показано ниже.

  >>> std = Студент ('Стив', 25)
>>> std.displayInfo ()
Имя студента: Стив, возраст: 25
  

Удаление атрибута, объекта, класса

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

  >>> std = Студент ('Стив', 25)
>>> del std.name # удаляемый атрибут
>>> std.name
Отслеживание (последний вызов последний):
Файл "", строка 1, в 
std.name
AttributeError: объект "Студент" не имеет атрибута "имя"
>>> del std # удаление объекта
>>> std.name
Отслеживание (последний вызов последний):
Файл "", строка 1, в 
std.name
NameError: имя 'std' не определено
>>> del Student # удаление класса
>>> std = Студент ('Стив', 25)
Отслеживание (последний вызов последний):
Файл "", строка 1, в 
std = Студент ()
NameError: имя "Студент" не определено
  

Атрибуты класса

и атрибуты экземпляра в Python

Атрибуты класса - это переменные, определенные непосредственно в классе, которые совместно используются всеми объектами класса.

Атрибуты экземпляра - это атрибуты или свойства, прикрепленные к экземпляру класса. Атрибуты экземпляра определяются в конструкторе.

В следующей таблице перечислены различия между атрибутом класса и атрибутом экземпляра:

Атрибут класса Атрибут экземпляра
Определяется непосредственно внутри класса. Определяется внутри конструктора с помощью параметра self .
Совместно со всеми объектами. Специфично для объекта.
Доступ осуществляется с использованием имени класса, а также с использованием объекта с точечной нотацией, например.грамм. имя класса.class_attribute или object.class_attribute Доступ осуществляется с использованием точечной нотации объекта, например object.instance_attribute
Изменение значения с помощью classname.class_attribute = значение будет отражено для всех объектов. Изменение значения атрибута экземпляра не будет отражено на других объектах.

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

  класс Студент:
    count = 0
    def __init __ (сам):
        Student.count + = 1
  

В приведенном выше примере count - это атрибут в классе Student.Каждый раз, когда создается новый объект, значение count увеличивается на 1.
Теперь вы можете получить доступ к атрибуту count после создания объектов, как показано ниже.

  >>> std1 = Студент ()
>>> Student.count
1
>>> std2 = Студент ()
>>> Student.count
2
  

Ниже показаны атрибуты экземпляра.

  класс Студент:
    def __init __ (я, имя, возраст):
        self.name = имя
        self.age = возраст
  

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

  >>> std = Студент ('Bill', 25)
>>> std.название
'Билл'
>>> std.age
25
>>> std.name = 'Стив'
>>> std.age = 45
>>> std.name
'Стив'
>>> std.age
45
  

Посетите класс Python для получения дополнительной информации.


Как получить список атрибутов класса в Python

На днях я пытался выяснить, есть ли простой способ получить определенные атрибуты класса (также известные как «переменные экземпляра»).Причина заключалась в том, что мы использовали созданные нами атрибуты для сопоставления с полями в анализируемом файле. Итак, в основном мы читаем файл построчно, и каждую строку можно разделить на 150+ частей, которые необходимо сопоставить с полями, которые мы создаем в классе. Загвоздка в том, что мы недавно добавили в класс больше полей, и в коде есть проверка, которая жестко запрограммирована на количество полей, которые должны быть в файле. Таким образом, когда я добавил больше полей, это прервало проверку. Надеюсь, все это имело смысл. Теперь вы знаете предысторию, поэтому мы можем двигаться дальше.Я нашел три разных способа добиться этого, поэтому мы перейдем от самого сложного к самому простому.

Как должно знать большинство программистов Python, Python предоставляет небольшую удобную встроенную функцию под названием dir . Я могу использовать это в экземпляре класса, чтобы получить список всех атрибутов и методов этого класса вместе с некоторыми унаследованными магическими методами, такими как '__delattr__', '__dict__', '__doc__', '__format__' и т. Д. попробуйте сами, выполнив следующие действия:

x = директория (myClassInstance)
 

Однако мне не нужны ни магические методы, ни методы.Мне просто нужны атрибуты. Чтобы все было предельно ясно, давайте напишем код!

########################################################################## #####################
класс Test:
    "" "" ""

    # ------------------------------------------------- ---------------------
    def __init __ (сам):
        self.varOne = ""
        self.varTwo = ""
        self.varThree = ""
        
    # ------------------------------------------------- ---------------------
    def methodOne (self):
        "" "" ""
        print "Вы только что позвонили в methodOne!"

# ------------------------------------------------- ---------------------
если __name__ == "__main__":
    t = Тест ()
 

Мы хотим получить список, содержащий только self.varOne, self.varTwo и self.varThree. Первый метод, который мы рассмотрим, - это использование модуля Python inspect.

импортная инспекция
переменные = [i для i в dir (t), если не inspect.ismethod (i)]
 

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

.

переменные = [i for i in dir (t) if not callable (i)]
 

Вы можете узнать больше о callable в документации Python. По сути, все, что делает callable, - это возвращает True или False в зависимости от того, является ли объект, который вы ему передали, вызываемым. Методы вызываются, переменные - нет. Таким образом, мы перебираем каждый элемент в классе dict и добавляем их в список только в том случае, если они являются вызываемыми , а не (т.е.е. не методы). Довольно красиво и не требует импорта! Но есть еще более простой способ!

Самый простой способ, который я нашел, - использовать магический метод __dict__ . Это встроено в каждый создаваемый вами класс, если вы его не переопределите. Поскольку мы имеем дело со словарем Python, мы можем просто вызвать метод ключей !

переменные = t .__ dict __. keys ()
 

Настоящий вопрос теперь в том, следует ли вам использовать магический метод для этого? Большинство программистов на Python, вероятно, не одобрят это.Они волшебны, поэтому их не следует использовать, если вы не занимаетесь метапрограммированием. Лично я считаю, что это вполне приемлемо для данного варианта использования. Сообщите мне о любых других методах, которые я пропустил или которые, по вашему мнению, лучше.

ресурса

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

Резюме : в этом руководстве вы узнаете об атрибутах класса Python и о том, когда их использовать.

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

Начнем с простого Circle class:

 

class Circle: def __init __ (себя, радиус): себя.пи = 3,14159 self.radius = радиус область определения (self): return self.pi * self.radius ** 2 окружность def (self): return 2 * self.pi * self.radius

Язык кода: Python (python)

Класс Circle имеет два атрибута: pi и radius . В нем также есть два метода, которые вычисляют площадь и длину окружности.

И pi , и радиус называются атрибутами экземпляра .Другими словами, они принадлежат определенному экземпляру класса Circle . Если вы измените атрибуты экземпляра, это не повлияет на другие экземпляры.

Помимо атрибутов экземпляра, Python также поддерживает атрибуты класса . Атрибуты класса не связаны с каким-либо конкретным экземпляром класса. Но они общие для всех экземпляров класса.

Если вы программировали на Java или C #, вы увидите, что атрибуты класса похожи на статические члены, но не совпадают.

Чтобы определить атрибут класса, вы помещаете его вне метода __init __ () . Например, следующее определяет pi как атрибут класса:

 

class Circle: пи = 3,14159 def __init __ (себя, радиус): self.radius = радиус область определения (self): return self.pi * self.radius ** 2 окружность def (self): return 2 * self.pi * self.radius

Язык кода: Python (python)

После этого вы можете получить доступ к атрибуту класса через экземпляры класса или через имя класса:

 

имя_объекта.class_attribute class_name.class_attribute

Язык кода: Oracle Rules Language (ruleslanguage)

В методах area () и circle () мы получаем доступ к атрибуту класса pi через переменную self .

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

 

c = Круг (10) печать (c.Пи) print (Circle.pi)

Язык кода: Python (python)

Вывод:

 

3.14159 3.14159

Язык кода: Python (python)

Как работают атрибуты класса Python

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

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

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

 

класс Тест: х = 10 def __init __ (сам): self.x = 20 test = Test () печать (test.Икс) print (Test.x)

Язык кода: Python (python)

Как это работает.

Класс Test имеет два атрибута с одинаковым именем ( x ), один - это атрибут экземпляра, а другой - атрибут класса.

Когда мы обращаемся к атрибуту x через экземпляр класса Test , он возвращает 20, которое является переменной атрибута экземпляра.

Однако, когда мы обращаемся к атрибуту x через класс Test , он возвращает 10, что является значением атрибута класса x .

Когда использовать атрибуты класса Python

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

1) Сохранение констант класса

Поскольку константа не меняется от экземпляра к экземпляру класса, удобно хранить ее как атрибут класса.

Например, класс Circle имеет константу pi , которая одинакова для всех экземпляров класса.Следовательно, это хороший кандидат на атрибуты класса.

2) Отслеживание данных по всем экземплярам

Следующее добавляет атрибут класса circle_list к классу Circle . Когда вы создаете новый экземпляр класса Circle , конструктор добавляет этот экземпляр в список:

 

class Circle: circle_list = [] пи = 3,14159 def __init __ (себя, радиус): self.radius = радиус себя.circle_list.append (самостоятельно) область определения (self): return self.pi * self.radius ** 2 окружность def (self): return 2 * self.pi * self.radius c1 = Круг (10) c2 = Круг (20) print (len (Circle.circle_list))

Язык кода: Python (python)

3) Определение значений по умолчанию

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

В следующем примере определяется класс Product .Все экземпляры класса Product будут иметь скидку по умолчанию, заданную атрибутом default_discount class:

 

class Product: default_discount = 0 def __init __ (self, price): self.price = цена self.discount = Product.default_discount def set_discount (самостоятельно, скидка): self.discount = скидка def net_price (self): return self.price * (1 - самостоятельная скидка) p1 = Продукт (100) печать (p1.net_price ()) p2 = Продукт (200) p2.set_discount (0,05) печать (p2.net_price ())

Язык кода: Python (python)

Сводка

  • Атрибут класса используется всеми экземплярами класса. Чтобы определить атрибут класса, вы помещаете его вне метода __init __ () .
  • Используйте class_name.class_attribute или object_name.class_attribute для доступа к значению class_attribute .
  • Используйте атрибуты класса для хранения контуров класса, отслеживания данных по всем экземплярам и установки значений по умолчанию для всех экземпляров класса.

Вы нашли это руководство полезным?

Атрибут класса Python и атрибут экземпляра

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

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

Давайте приступим!


Общие сведения об атрибуте класса Python

Атрибут класса Python - это атрибут / переменная, заключенная в класс.То есть его область действия находится в классе Python.

Атрибут Class создает только одну копию самого себя , и эта единственная копия используется всеми функциями и объектами в этом конкретном классе.

Синтаксис:

class Имя класса:
     переменная = значение
 

Давайте теперь разберемся с реализацией того же на примере ниже.


Реализация атрибута класса на примере

класс class_attribute:
val = 1

def product (self):
class_attribute.val * = 10
печать (class_attribute.val)

obj1 = атрибут_класса ()
obj1.product ()
 
obj2 = атрибут_класса ()
obj2.product ()
 

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

Далее мы получаем доступ к переменной val в функции product () и манипулируем значением, умножая его на 10.

Как Очевидно, что одна и та же копия переменной val используется обоими созданными объектами. Таким образом, сначала val = 1.

Когда объект obj1 вызывает функцию, используется та же копия «val» (значение не сбрасывается) и, таким образом, становится val = 10.При вызове объектом obj2 val становится val * 10, т.е. 10 * 10 = 100.

Вывод:


Понимание атрибута экземпляра Python

Атрибут экземпляра Python является локальным атрибутом / переменной, область действия которой находится в пределах конкретная функция, использующая атрибут. Таким образом, он заключен в конкретную функцию.

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

Синтаксис:

def имя-функции ():
    переменная = значение
 

Теперь реализуем локальные атрибуты на примере.


Реализация атрибута экземпляра на примере

класс instance_attribute:

def product (self):
val = 20
val * = 10
печать (val)


obj1 = атрибут_экземпляра ()
obj1.product ()
 
obj2 = атрибут_экземпляра ()
obj2.продукт()
 

В этом примере мы объявили и инициализировали атрибут экземпляра как val = 20.

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

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