Атрибуты класса
Свойства объектов — центральное понятие в объектно-ориентированном программировании. Этот термин охватывает методы и переменные, определенные в определении класса. Опять же, не все программисты Python используют термин "свойства": некоторые предпочитают атрибуты, характеристики или члены. В более широком мире объектно-ориентированного программирования термин "свойства" вполне принят, поэтому здесь мы используем именно его.
До сих пор мы в основном имели дело со свойствами объектов. К ним относятся методы и атрибуты, доступные в любом экземпляре класса. На самом деле сами классы тоже могут иметь свойства, которые иногда называют статическими свойствами, а точнее переменными класса и методами класса.
Переменные класса
У каждого экземпляра класса есть свои конкретные значения для каждого атрибута, определенного в классе, как мы видели в примерах предыдущих разделов. Но что, если мы хотим иметь некоторые данные, общие для разных экземпляров? Здесь появляются переменные класса, также известные как статические переменные. Переменная класса — это переменная, к которой обращаются через сам класс, а не через экземпляры, созданные на основе класса. В любой момент выполнения программы переменная класса имеет одно единственное значение, независимо от того, сколько экземпляров класса создано.
Переменная класса объявляется без префикса self и обычно вне любого определения метода, потому что она должна быть доступна откуда угодно внутри класса или даже снаружи класса.
class SavingsAccount:
general_rate = 0.03
def __init__(self, account_number: str, balance: float, interest_rate: float):
self.__account_number = account_number
self.__balance = balance
self.__interest_rate = interest_rate
def add_interest(self):
# The total interest rate equals
# the general rate + the interest rate of the account
total_interest = SavingsAccount.general_rate + self.__interest_rate
self.__balance += self.__balance * total_interest
@property
def balance(self):
return self.__balanceПоскольку переменная general_rate определена внутри класса, но вне любых определений методов, и не использует префикс self, это переменная класса.
К переменной класса обращаются через имя класса, например так:
# The general rate exists independently of any object instances
print("The general interest rate is", SavingsAccount.general_rate)
account = SavingsAccount("12345", 1000, 0.05)
# Add the total interest accrued to the balance on the account
account.add_interest()
print(account.balance)The general interest rate is 0.03 1080.0
Итак, к переменным класса обращаются через имя класса, например SavingsAccount.general_rate, а к переменным экземпляра — через имя объектной переменной, например account.balance. Переменная экземпляра, естественно, существует только тогда, когда создан экземпляр класса, но переменная класса доступна везде и в любой момент времени, где доступен сам класс.
Переменные класса полезны, когда нужны значения, общие для всех экземпляров класса. В примере выше мы предположили, что общая процентная ставка всех сберегательных счетов складывается из двух компонентов: общая ставка процентов общая для всех счетов, но у каждого счета также есть своя процентная ставка в переменной экземпляра. Общая ставка тоже может измениться, но тогда изменение одинаково повлияет на все экземп ляры класса.
class SavingsAccount:
general_rate = 0.03
def __init__(self, account_number: str, balance: float, interest_rate: float):
self.__account_number = account_number
self.__balance = balance
self.__interest_rate = interest_rate
def add_interest(self):
# The total interest rate equals
# the general rate + the interest rate of the account
total_interest = SavingsAccount.general_rate + self.__interest_rate
self.__balance += self.__balance * total_interest
@property
def balance(self):
return self.__balance
@property
def total_interest(self):
return self.__interest_rate + SavingsAccount.general_rateaccount1 = SavingsAccount("12345", 100, 0.03)
account2 = SavingsAccount("54321", 200, 0.06)
print("General interest rate:", SavingsAccount.general_rate)
print(account1.total_interest)
print(account2.total_interest)
# The general rate of interest is now 10 percent
SavingsAccount.general_rate = 0.10
print("General interest rate:", SavingsAccount.general_rate)
print(account1.total_interest)
print(account2.total_interest)General interest rate: 0.03 0.06 0.09 General interest rate: 0.1 0.13 0.16
Когда общая процентная ставка меняется, итоговая процентная ставка для всех экземпляров класса тоже меняется. Как видно выше, можно добавить метод-геттер с декоратором @property, даже если в классе нет атриб ута с таким же именем. Этот метод возвращает сумму общей процентной ставки и процентной ставки конкретного счета.
Посмотрим еще один пример. Класс PhoneNumber используется для определения одного телефонного номера, но он также содержит некоторые коды стран в словаре. Этот словарь — переменная класса, и поэтому он общий для всех экземпляров класса, потому что код страны для телефонных номеров из одной страны всегда одинаков.
class PhoneNumber:
country_codes = {"Finland": "+358", "Sweden": "+46", "United States": "+1"}
def __init__(self, name: str, phone_number: str, country: str):
self.__name = name
self.__phone_number = phone_number
self.__country = country
@property
def phone_number(self):
# When the country code prefix is added
# the initial zero is removed from the phone number
return PhoneNumber.country_codes[self.__country] + " " + self.__phone_number[1:]paulas_no = PhoneNumber("Paula Pythons", "050 1234 567", "Finland")
print(paulas_no.phone_number)+358 50 1234 567
Каждый объект PhoneNumber содержит имя владельца, сам номер и страну телефонного номера. Когда к атрибуту, содержащему телефонный номер, обращаются через метод-геттер, соответствующий код страны берется из словаря-переменной класса на основе атрибута страны, а результат добавляется в начало номера.
Пример реализации выше пока не очень функционален в остальном. В следующем примере мы добавили геттеры и сеттеры для всех атрибутов:
class PhoneNumber:
country_codes = {"Finland": "+358", "Sweden": "+46", "United States": "+1"}
def __init__(self, name: str, phone_number: str, country: str):
self.__name = name
# This is a call to the phone_number.setter method
self.phone_number = phone_number
# This is a call to the country.setter method
self.country = country
# the getter method for phone_number combines the country code
# and the attribute phone_number
@property
def phone_number(self):
# the initial zero is removed as the country code is prefixed
return PhoneNumber.country_codes[self.__country] + " " + self.__phone_number[1:]
@phone_number.setter
def phone_number(self, number):
# Making sure the number contains only numbers and space characters
for character in number:
if character not in "1234567890 ":
raise ValueError("A phone number can only contain numbers and spaces")
self.__phone_number = number
# a getter for only the number itself without the country code
@property
def local_number(self):
return self.__phone_number
@property
def country(self):
return self.__country
@country.setter
def country(self, country):
# Making sure the country is a key in the dictionary of country codes
if country not in PhoneNumber.country_codes:
raise ValueError("This country is not on the list.")
self.__country = country
@property
def name(self):
return self.__name
@name.setter
def name(self, name):
self.__name = name
def __str__(self):
return f"{self.phone_number} ({self.__name})"if __name__ == "__main__":
pn = PhoneNumber("Peter Pythons", "040 111 1111", "Sweden")
print(pn)
print(pn.phone_number)
print(pn.local_number)+46 40 111 1111 (Peter Pythons) +46 40 111 1111 040 111 1111
Методы класса
Метод класса, также называемый статическим методом, — это метод, который не привязан ни к одному конкретному экземпляру класса. Метод класса можно вызвать без создания каких-либо экземпляров класса.
Методы класса обычно являются инструментами, которые как-то связаны с назначением класса, но отделены от экземпляров в том смысле, что для их вызова не должно быть необходимо создавать экземпляры класса. Методы класса обычно публичные, чтобы их можно было вызывать и снаружи класса, и изнутри класса, включая вызовы из экземпляров класса.
Метод класса определяется с помощью аннотации @classmethod. Первый параметр всегда cls. Имя переменной cls похоже на параметр self. Разница в том, что cls указывает на класс, а self указывает на экземпляр класса. Ни один из этих параметров не включается в список аргументов при вызове функции; Python автоматически подставляет подходящее значение.
В следующем примере у нас есть класс, моделирующий регистрацию транспортных средств. Класс Registration содержит статический метод для проверки, является ли номерной знак допустимым. Этот метод является статическим методом класса, потому что полезно иметь возможность проверить допустимость номерного знака еще до создания хотя бы одного объекта Registration:
class Registration:
def __init__(self, owner: str, make: str, year: int, license_plate: str):
self.__owner = owner
self.__make = make
self.__year = year
# Call the license_plate.setter method
self.license_plate = license_plate
@property
def license_plate(self):
return self.__license_plate
@license_plate.setter
def license_plate(self, plate):
if Registration.license_plate_valid(plate):
self.__license_plate = plate
else:
raise ValueError("The license plate is not valid")
# A class method for validating the license plate
@classmethod
def license_plate_valid(cls, plate: str):
if len(plate) < 3 or "-" not in plate:
return False
# Check the beginning and end sections of the plate separately
letters, numbers = plate.split("-")
# the beginning section can have only letters
for character in letters:
if character.lower() not in "abcdefghijklmnopqrstuvwxyzåäö":
return False
# the end section can have only numbers
for character in numbers:
if character not in "1234567890":
return False
return Trueregistration = Registration("Mary Motorist", "Volvo", "1992", "abc-123")
if Registration.license_plate_valid("xyz-789"):
print("This is a valid license plate!")This is a valid license plate!
Допустимость номерного знака можно проверить даже без создания ни одного экземпляра класса, например с помощью Registration.license_plate_valid("xyz-789"). Тот же метод вызывается внутри конструктора класса. Обратите внимание: даже внутри конструктора к этому методу обращаются через имя класса, а не через self!
Текущие баллы можно посмотреть в синем индикаторе в правом нижнем углу страницы.