Python Language
Классы
Поиск…
Вступление
Python предлагает себя не только как популярный язык сценариев, но также поддерживает парадигму объектно-ориентированного программирования. Классы описывают данные и предоставляют методы для управления этими данными, все они охватываются одним объектом. Кроме того, классы позволяют абстрагироваться путем отделения конкретных деталей реализации от абстрактных представлений данных.
Кодекс, использующий классы, обычно легче читать, понимать и поддерживать.
Основное наследование
Наследование в Python основано на аналогичных идеях, используемых в других объектно-ориентированных языках, таких как Java, C ++ и т. Д. Новый класс может быть получен из существующего класса следующим образом.
class BaseClass(object):
pass
class DerivedClass(BaseClass):
pass
BaseClass
- уже существующий ( родительский ) класс, а DerivedClass
- новый ( дочерний ) класс, который наследует (или подклассы ) атрибуты из BaseClass
. Примечание . Начиная с Python 2.2, все классы неявно наследуют от класса object
, который является базовым классом для всех встроенных типов.
Мы определяем родительский класс Rectangle
в приведенном ниже примере, который неявно наследует от object
:
class Rectangle():
def __init__(self, w, h):
self.w = w
self.h = h
def area(self):
return self.w * self.h
def perimeter(self):
return 2 * (self.w + self.h)
Класс Rectangle
может использоваться как базовый класс для определения Square
класса, так как квадрат является частным случаем прямоугольника.
class Square(Rectangle):
def __init__(self, s):
# call parent constructor, w and h are both s
super(Square, self).__init__(s, s)
self.s = s
Класс Square
автоматически наследует все атрибуты класса Rectangle
а также класс объекта. super()
используется для вызова метода __init__()
класса Rectangle
, по существу вызывающего любой переопределенный метод базового класса. Примечание : в Python 3 super()
не требует аргументов.
Объекты с производными классами могут получать и изменять атрибуты базовых классов:
r.area()
# Output: 12
r.perimeter()
# Output: 14
s.area()
# Output: 4
s.perimeter()
# Output: 8
Встроенные функции, которые работают с наследованием
issubclass(DerivedClass, BaseClass)
: возвращает True
если DerivedClass
является подклассом BaseClass
isinstance(s, Class)
: возвращает True
если s является экземпляром Class
или любого из производных классов Class
# subclass check
issubclass(Square, Rectangle)
# Output: True
# instantiate
r = Rectangle(3, 4)
s = Square(2)
isinstance(r, Rectangle)
# Output: True
isinstance(r, Square)
# Output: False
# A rectangle is not a square
isinstance(s, Rectangle)
# Output: True
# A square is a rectangle
isinstance(s, Square)
# Output: True
Переменные класса и экземпляра
Переменные экземпляра уникальны для каждого экземпляра, а переменные класса - для всех экземпляров.
class C:
x = 2 # class variable
def __init__(self, y):
self.y = y # instance variable
C.x
# 2
C.y
# AttributeError: type object 'C' has no attribute 'y'
c1 = C(3)
c1.x
# 2
c1.y
# 3
c2 = C(4)
c2.x
# 2
c2.y
# 4
Доступ к переменным класса можно получить в экземплярах этого класса, но присвоение атрибуту class создаст переменную экземпляра, которая затеняет переменную класса
c2.x = 4
c2.x
# 4
C.x
# 2
Обратите внимание, что мутирующие переменные класса из экземпляров могут привести к некоторым неожиданным последствиям.
class D:
x = []
def __init__(self, item):
self.x.append(item) # note that this is not an assigment!
d1 = D(1)
d2 = D(2)
d1.x
# [1, 2]
d2.x
# [1, 2]
D.x
# [1, 2]
Связанные, несвязанные и статические методы
Идея связанных и несвязанных методов была удалена в Python 3 . В Python 3, когда вы объявляете метод внутри класса, вы используете ключевое слово def
, создавая таким образом объект функции. Это регулярная функция, и окружающий класс работает как пространство имен. В следующем примере мы объявляем метод f
в классе A
, и он становится функцией Af
:
class A(object):
def f(self, x):
return 2 * x
A.f
# <function A.f at ...> (in Python 3.x)
В Python 2 поведение было иным: функциональные объекты внутри класса были неявно заменены объектами типа instancemethod
, которые назывались несвязанными методами, потому что они не были привязаны к какому-либо конкретному экземпляру класса. Можно было получить доступ к основной функции, используя свойство .__func__
.
A.f
# <unbound method A.f> (in Python 2.x)
A.f.__class__
# <type 'instancemethod'>
A.f.__func__
# <function f at ...>
Последнее поведение подтверждается проверкой - методы распознаются как функции в Python 3, а различие поддерживается в Python 2.
import inspect
inspect.isfunction(A.f)
# True
inspect.ismethod(A.f)
# False
import inspect
inspect.isfunction(A.f)
# False
inspect.ismethod(A.f)
# True
В обеих версиях функции / метода Python Af
можно вызывать напрямую, при условии, что вы передаете экземпляр класса A
в качестве первого аргумента.
A.f(1, 7)
# Python 2: TypeError: unbound method f() must be called with
# A instance as first argument (got int instance instead)
# Python 3: 14
a = A()
A.f(a, 20)
# Python 2 & 3: 40
Предположим теперь, что a
является экземпляром класса A
, что тогда af
? Ну, интуитивно это должен быть тот же метод f
класса A
, только он должен каким-то образом «знать», что он был применен к объекту a
- в Python, это называется методом, связанным с a
.
В суровых буднях детали следующим образом : запись af
вызывает магический __getattribute__
метод , который сначала проверяет , является ли имеет атрибут с именем a
a
f
(не), а затем проверяет , класс A
, содержит ли это метод с таким именем (он делает), и создает новый объект m
типа method
, который имеет ссылку на исходные Af
в m.__func__
и ссылку на объект a
в m.__self__
. Когда этот объект вызывается как функция, он просто выполняет следующее: m(...) => m.__func__(m.__self__, ...)
. Таким образом, этот объект называется связанным методом, потому что при вызове он знает, что для обеспечения объекта он был связан как первый аргумент. (Эти вещи работают одинаково в Python 2 и 3).
a = A()
a.f
# <bound method A.f of <__main__.A object at ...>>
a.f(2)
# 4
# Note: the bound method object a.f is recreated *every time* you call it:
a.f is a.f # False
# As a performance optimization you can store the bound method in the object's
# __dict__, in which case the method object will remain fixed:
a.f = a.f
a.f is a.f # True
Наконец, Python имеет методы класса и статические методы - специальные виды методов. Методы класса работают так же, как и обычные методы, за исключением того, что при вызове на объект они привязываются к классу объекта, а не к объекту. Таким образом, m.__self__ = type(a)
. Когда вы вызываете такой связанный метод, он передает класс a
в качестве первого аргумента. Статические методы еще проще: они вообще ничего не связывают и просто возвращают базовую функцию без каких-либо преобразований.
class D(object):
multiplier = 2
@classmethod
def f(cls, x):
return cls.multiplier * x
@staticmethod
def g(name):
print("Hello, %s" % name)
D.f
# <bound method type.f of <class '__main__.D'>>
D.f(12)
# 24
D.g
# <function D.g at ...>
D.g("world")
# Hello, world
Обратите внимание, что методы класса привязаны к классу даже при доступе к экземпляру:
d = D()
d.multiplier = 1337
(D.multiplier, d.multiplier)
# (2, 1337)
d.f
# <bound method D.f of <class '__main__.D'>>
d.f(10)
# 20
Стоит отметить, что на самом низком уровне функции, методы, staticmethods и т. Д. Фактически являются дескрипторами, которые вызывают __get__
, __set
__ и, необязательно, специальные методы __del__
. Дополнительные сведения о методах класса и staticmethods:
- В чем разница между @staticmethod и @classmethod в Python?
- Значение @classmethod и @staticmethod для начинающих?
Классы нового стиля и старого стиля
Классы нового стиля были введены в Python 2.2 для унификации классов и типов . Они наследуются от типа object
верхнего уровня. Класс нового стиля является определяемым пользователем типом и очень похож на встроенные типы.
# new-style class
class New(object):
pass
# new-style instance
new = New()
new.__class__
# <class '__main__.New'>
type(new)
# <class '__main__.New'>
issubclass(New, object)
# True
Классы старого стиля не наследуются от object
. Старые экземпляры всегда реализуются со встроенным типом instance
.
# old-style class
class Old:
pass
# old-style instance
old = Old()
old.__class__
# <class __main__.Old at ...>
type(old)
# <type 'instance'>
issubclass(Old, object)
# False
В Python 3 были удалены классы старого стиля.
Классы нового стиля в Python 3 неявно наследуют от object
, поэтому больше не нужно указывать MyClass(object)
.
class MyClass:
pass
my_inst = MyClass()
type(my_inst)
# <class '__main__.MyClass'>
my_inst.__class__
# <class '__main__.MyClass'>
issubclass(MyClass, object)
# True
Значения по умолчанию для переменных экземпляра
Если переменная содержит значение неизменяемого типа (например, строку), тогда можно назначить значение по умолчанию, подобное этому
class Rectangle(object):
def __init__(self, width, height, color='blue'):
self.width = width
self.height = height
self.color = color
def area(self):
return self.width * self.height
# Create some instances of the class
default_rectangle = Rectangle(2, 3)
print(default_rectangle.color) # blue
red_rectangle = Rectangle(2, 3, 'red')
print(red_rectangle.color) # red
Нужно быть осторожным при инициализации изменяемых объектов, таких как списки в конструкторе. Рассмотрим следующий пример:
class Rectangle2D(object):
def __init__(self, width, height, pos=[0,0], color='blue'):
self.width = width
self.height = height
self.pos = pos
self.color = color
r1 = Rectangle2D(5,3)
r2 = Rectangle2D(7,8)
r1.pos[0] = 4
r1.pos # [4, 0]
r2.pos # [4, 0] r2's pos has changed as well
Такое поведение вызвано тем, что параметры Python по умолчанию привязаны к выполнению функции, а не к объявлению функции. Чтобы получить переменную экземпляра по умолчанию, которая не используется совместно с экземплярами, следует использовать такую конструкцию:
class Rectangle2D(object):
def __init__(self, width, height, pos=None, color='blue'):
self.width = width
self.height = height
self.pos = pos or [0, 0] # default value is [0, 0]
self.color = color
r1 = Rectangle2D(5,3)
r2 = Rectangle2D(7,8)
r1.pos[0] = 4
r1.pos # [4, 0]
r2.pos # [0, 0] r2's pos hasn't changed
См. Также « Разрешимые аргументы по умолчанию» и «Наименьшее удивление» и параметр Mutable Default Argument .
Многократное наследование
Python использует алгоритм линеаризации C3 для определения порядка, в котором разрешаются атрибуты класса, включая методы. Это называется порядком разрешения метода (MRO).
Вот простой пример:
class Foo(object):
foo = 'attr foo of Foo'
class Bar(object):
foo = 'attr foo of Bar' # we won't see this.
bar = 'attr bar of Bar'
class FooBar(Foo, Bar):
foobar = 'attr foobar of FooBar'
Теперь, если мы создаем FooBar, если мы найдем атрибут foo, мы увидим, что атрибут Foo найден первым
fb = FooBar()
а также
>>> fb.foo
'attr foo of Foo'
Вот MRO FooBar:
>>> FooBar.mro()
[<class '__main__.FooBar'>, <class '__main__.Foo'>, <class '__main__.Bar'>, <type 'object'>]
Можно просто сказать, что алгоритм MRO Python
- Сначала глубина (например,
FooBar
затемFoo
), если только - общий родитель (
object
) блокируется дочерним элементом (Bar
) и - не допускаются круговые отношения.
То есть, например, Bar не может наследовать FooBar, а FooBar наследуется от Bar.
Подробный пример в Python см. В записи wikipedia .
Еще одна мощная функция в наследовании - super
. super может извлекать функции родительских классов.
class Foo(object):
def foo_method(self):
print "foo Method"
class Bar(object):
def bar_method(self):
print "bar Method"
class FooBar(Foo, Bar):
def foo_method(self):
super(FooBar, self).foo_method()
Множественное наследование с методом init метода, когда каждый класс имеет собственный метод init, мы пытаемся выполнить множественный ineritance, тогда только метод init получает вызов класса, который наследуется первым.
для примера ниже только метод инициализации класса Foo, получивший вызов Bar class init, не получивший вызов
class Foo(object):
def __init__(self):
print "foo init"
class Bar(object):
def __init__(self):
print "bar init"
class FooBar(Foo, Bar):
def __init__(self):
print "foobar init"
super(FooBar, self).__init__()
a = FooBar()
Выход:
foobar init
foo init
Но это не значит, что класс Bar не наследуется. Экземпляр конечного класса FooBar также является экземпляром класса Bar и класса Foo .
print isinstance(a,FooBar)
print isinstance(a,Foo)
print isinstance(a,Bar)
Выход:
True
True
True
Дескрипторы и пунктирные образы
Дескрипторы - это объекты, которые являются (обычно) атрибутами классов и имеют какие-либо из специальных методов __get__
, __set__
или __delete__
.
Дескрипторы данных имеют любой из __set__
или __delete__
Они могут контролировать точечный поиск экземпляра и использоваться для реализации функций, staticmethod
, classmethod
и property
. Точечный поиск (например, экземпляр foo
класса Foo
поиска атрибута bar
то есть foo.bar
) использует следующий алгоритм:
bar
просматривается в классе,Foo
. Если он есть, и это дескриптор данных , то используется дескриптор данных. Вот какproperty
может контролировать доступ к данным в экземпляре, и экземпляры не могут переопределить это. Если дескриптора данных нет, тогдаbar
просматривается в экземпляре__dict__
. Вот почему мы можем переопределять или блокировать методы, вызываемые из экземпляра с точным поиском. Если в экземпляре существуетbar
, он используется. Если нет, мы тогдапосмотрите в классе
Foo
дляbar
. Если это дескриптор , используется протокол дескриптора. Вот как реализованы функции (в этом контексте, несвязанные методы),classmethod
иstaticmethod
. Иначе он просто возвращает объект там, или естьAttributeError
Методы класса: альтернативные инициализаторы
Методы класса представляют альтернативные способы создания экземпляров классов. Чтобы проиллюстрировать это, давайте рассмотрим пример.
Предположим, что у нас относительно простой класс Person
:
class Person(object):
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
self.full_name = first_name + " " + last_name
def greet(self):
print("Hello, my name is " + self.full_name + ".")
Возможно, было бы удобно создать экземпляры этого класса, указав полное имя вместо имени и фамилии отдельно. Один из способов сделать это last_name
бы в том, чтобы last_name
был необязательным параметром и считал, что если он не указан, мы передали полное имя в:
class Person(object):
def __init__(self, first_name, age, last_name=None):
if last_name is None:
self.first_name, self.last_name = first_name.split(" ", 2)
else:
self.first_name = first_name
self.last_name = last_name
self.full_name = self.first_name + " " + self.last_name
self.age = age
def greet(self):
print("Hello, my name is " + self.full_name + ".")
Однако в этом бите кода есть две основные проблемы:
Параметры
first_name
иlast_name
теперь вводят в заблуждение, так как вы можете ввести полное имя дляfirst_name
. Кроме того, если есть больше случаев и / или более параметров, обладающих такой гибкостью, ветвление if / elif / else может раздражать быстро.Не так важно, но все же стоит указать: что, если
last_name
-None
, ноfirst_name
не разбивается на две или более вещи через пробелы? У нас есть еще один уровень проверки ввода и / или обработки исключений ...
Введите методы класса. Вместо того, чтобы иметь один инициализатор, мы создадим отдельный инициализатор, называемый from_full_name
, и from_full_name
его (встроенным) декоратором classmethod
.
class Person(object):
def __init__(self, first_name, last_name, age):
self.first_name = first_name
self.last_name = last_name
self.age = age
self.full_name = first_name + " " + last_name
@classmethod
def from_full_name(cls, name, age):
if " " not in name:
raise ValueError
first_name, last_name = name.split(" ", 2)
return cls(first_name, last_name, age)
def greet(self):
print("Hello, my name is " + self.full_name + ".")
Обратите внимание на cls
вместо self
в качестве первого аргумента from_full_name
. Методы класса применяется к общему классу, не является экземпляром данного класса (что self
обычно обозначает). Итак, если cls
- это наш класс Person
, тогда возвращаемое значение из from_full_name
класса from_full_name
- Person(first_name, last_name, age)
, которое использует __init__
Person
для создания экземпляра класса Person
. В частности, если мы должны были создать подкласс Employee
of Person
, то from_full_name
будет работать и в классе Employee
.
Чтобы показать, что это работает, как ожидается, давайте создадим экземпляры Person
более чем одним способом без ветвления в __init__
:
In [2]: bob = Person("Bob", "Bobberson", 42)
In [3]: alice = Person.from_full_name("Alice Henderson", 31)
In [4]: bob.greet()
Hello, my name is Bob Bobberson.
In [5]: alice.greet()
Hello, my name is Alice Henderson.
Другие ссылки:
https://docs.python.org/2/library/functions.html#classmethod
https://docs.python.org/3.5/library/functions.html#classmethod
Состав класса
Композиция класса допускает явные отношения между объектами. В этом примере люди живут в городах, принадлежащих странам. Композиция позволяет людям получить доступ к числу людей, проживающих в их стране:
class Country(object):
def __init__(self):
self.cities=[]
def addCity(self,city):
self.cities.append(city)
class City(object):
def __init__(self, numPeople):
self.people = []
self.numPeople = numPeople
def addPerson(self, person):
self.people.append(person)
def join_country(self,country):
self.country = country
country.addCity(self)
for i in range(self.numPeople):
person(i).join_city(self)
class Person(object):
def __init__(self, ID):
self.ID=ID
def join_city(self, city):
self.city = city
city.addPerson(self)
def people_in_my_country(self):
x= sum([len(c.people) for c in self.city.country.cities])
return x
US=Country()
NYC=City(10).join_country(US)
SF=City(5).join_country(US)
print(US.cities[0].people[0].people_in_my_country())
# 15
Патч обезьяны
В этом случае «патч обезьяны» означает добавление новой переменной или метода в класс после того, как он был определен. Например, предположим, что мы определили класс A
как
class A(object):
def __init__(self, num):
self.num = num
def __add__(self, other):
return A(self.num + other.num)
Но теперь мы хотим добавить еще одну функцию позже в код. Предположим, что эта функция такова.
def get_num(self):
return self.num
Но как мы можем добавить это как метод в A
? Это просто, мы просто по существу помещаем эту функцию в A
с оператором присваивания.
A.get_num = get_num
Почему это работает? Поскольку функции являются объектами, как и любой другой объект, а методы - это функции, принадлежащие классу.
Функция get_num
должна быть доступна всем существующим (уже созданным), а также новым экземплярам A
Эти дополнения доступны для всех экземпляров этого класса (или его подклассов) автоматически. Например:
foo = A(42)
A.get_num = get_num
bar = A(6);
foo.get_num() # 42
bar.get_num() # 6
Обратите внимание, что в отличие от некоторых других языков этот метод не работает для определенных встроенных типов, и он не считается хорошим стилем.
Список всех участников класса
Функция dir()
может использоваться для получения списка членов класса:
dir(Class)
Например:
>>> dir(list)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__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']
Общепринято смотреть только на «не магических» членов. Это можно сделать, используя простое понимание, в котором перечислены члены с именами, не начинающимися с __
:
>>> [m for m in dir(list) if not m.startswith('__')]
['append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
Предостережения:
Классы могут определять __dir__()
. Если этот метод существует, вызов dir()
вызовет __dir__()
, в противном случае Python попытается создать список членов класса. Это означает, что функция dir может иметь неожиданные результаты. Две цитаты, имеющие важное значение из официальной документации python :
Если объект не предоставляет dir (), функция пытается собрать информацию из атрибута dict объекта, если он определен, и из его объекта типа. Полученный список не обязательно завершен и может быть неточным, если у объекта есть пользовательский getattr ().
Примечание. Поскольку dir () предоставляется в основном как удобство для использования в интерактивном приглашении, он пытается предоставить интересный набор имен больше, чем пытается предоставить строго или последовательно определенный набор имен, а его подробное поведение может релизы. Например, атрибуты metaclass не входят в список результатов, когда аргумент является классом.
Введение в классы
Класс функционирует как шаблон, определяющий основные характеристики конкретного объекта. Вот пример:
class Person(object):
"""A simple class.""" # docstring
species = "Homo Sapiens" # class attribute
def __init__(self, name): # special method
"""This is the initializer. It's a special
method (see below).
"""
self.name = name # instance attribute
def __str__(self): # special method
"""This method is run when Python tries
to cast the object to a string. Return
this string when using print(), etc.
"""
return self.name
def rename(self, renamed): # regular method
"""Reassign and print the name attribute."""
self.name = renamed
print("Now my name is {}".format(self.name))
При взгляде на приведенный выше пример есть несколько вещей.
- Класс состоит из атрибутов (данных) и методов (функций).
- Атрибуты и методы просто определяются как обычные переменные и функции.
- Как отмечено в соответствующей docstring, метод
__init__()
называется инициализатором . Это эквивалентно конструктору на других объектно-ориентированных языках, и это метод, который сначала запускается при создании нового объекта или нового экземпляра класса. - Атрибуты, которые относятся ко всему классу, определяются сначала и называются атрибутами класса .
- Атрибуты, относящиеся к конкретному экземпляру класса (объекта), называются атрибутами экземпляра . Они обычно определяются внутри
__init__()
; это необязательно, но рекомендуется (поскольку атрибуты, определенные за пределами__init__()
подвергаются риску доступа к ним до их определения). - Каждый метод, включенный в определение класса, передает этот объект в качестве его первого параметра. Слово «
self
используется для этого параметра (использование «self
на самом деле условно, так как слово «self
не имеет неотъемлемого значения в Python, но это одна из самых уважаемых конвенций Python, и вы всегда должны следовать за ней). - Те, которые используются для объектно-ориентированного программирования на других языках, могут быть удивлены несколькими вещами. Во-первых, у Python нет реальной концепции
private
элементов, поэтому все, по умолчанию, имитирует поведениеpublic
ключевого слова C ++ / Java. Для получения дополнительной информации см. Пример «Члены частного класса» на этой странице. - Некоторые из методов класса имеют следующий вид:
__functionname__(self, other_stuff)
. Все такие методы называются «магическими методами» и являются важной частью классов в Python. Например, перегрузка операторов в Python осуществляется с помощью магических методов. Для получения дополнительной информации см. Соответствующую документацию .
Теперь давайте сделаем несколько экземпляров нашего класса Person
!
>>> # Instances
>>> kelly = Person("Kelly")
>>> joseph = Person("Joseph")
>>> john_doe = Person("John Doe")
В настоящее время у нас есть три объекта Person
, kelly
, joseph
и john_doe
.
Мы можем получить доступ к атрибутам класса из каждого экземпляра, используя оператор точки .
Еще раз обратите внимание на разницу между атрибутами класса и экземпляра:
>>> # Attributes
>>> kelly.species
'Homo Sapiens'
>>> john_doe.species
'Homo Sapiens'
>>> joseph.species
'Homo Sapiens'
>>> kelly.name
'Kelly'
>>> joseph.name
'Joseph'
Мы можем выполнять методы класса с использованием одного и того же точечного оператора .
:
>>> # Methods
>>> john_doe.__str__()
'John Doe'
>>> print(john_doe)
'John Doe'
>>> john_doe.rename("John")
'Now my name is John'
свойства
Классы Python поддерживают свойства , которые выглядят как обычные объектные переменные, но с возможностью установки пользовательского поведения и документации.
class MyClass(object):
def __init__(self):
self._my_string = ""
@property
def string(self):
"""A profoundly important string."""
return self._my_string
@string.setter
def string(self, new_value):
assert isinstance(new_value, str), \
"Give me a string, not a %r!" % type(new_value)
self._my_string = new_value
@string.deleter
def x(self):
self._my_string = None
Объект класса MyClass
, похоже , имеет свойство .string
, однако его поведение теперь жестко контролируется:
mc = MyClass()
mc.string = "String!"
print(mc.string)
del mc.string
Помимо полезного синтаксиса, как указано выше, синтаксис свойств позволяет проверять или добавлять другие атрибуты к этим атрибутам. Это может быть особенно полезно для общедоступных API-интерфейсов, где пользователю должен быть предоставлен уровень помощи.
Другим распространенным использованием свойств является включение класса для представления «виртуальных атрибутов» - атрибутов, которые фактически не хранятся, а вычисляются только по запросу.
class Character(object):
def __init__(name, max_hp):
self._name = name
self._hp = max_hp
self._max_hp = max_hp
# Make hp read only by not providing a set method
@property
def hp(self):
return self._hp
# Make name read only by not providing a set method
@property
def name(self):
return self.name
def take_damage(self, damage):
self.hp -= damage
self.hp = 0 if self.hp <0 else self.hp
@property
def is_alive(self):
return self.hp != 0
@property
def is_wounded(self):
return self.hp < self.max_hp if self.hp > 0 else False
@property
def is_dead(self):
return not self.is_alive
bilbo = Character('Bilbo Baggins', 100)
bilbo.hp
# out : 100
bilbo.hp = 200
# out : AttributeError: can't set attribute
# hp attribute is read only.
bilbo.is_alive
# out : True
bilbo.is_wounded
# out : False
bilbo.is_dead
# out : False
bilbo.take_damage( 50 )
bilbo.hp
# out : 50
bilbo.is_alive
# out : True
bilbo.is_wounded
# out : True
bilbo.is_dead
# out : False
bilbo.take_damage( 50 )
bilbo.hp
# out : 0
bilbo.is_alive
# out : False
bilbo.is_wounded
# out : False
bilbo.is_dead
# out : True
Класс Singleton
Синглтон - это шаблон, который ограничивает экземпляр класса одним экземпляром / объектом. Для получения дополнительной информации о шаблонах проектирования синглтона python см. Здесь .
class Singleton:
def __new__(cls):
try:
it = cls.__it__
except AttributeError:
it = cls.__it__ = object.__new__(cls)
return it
def __repr__(self):
return '<{}>'.format(self.__class__.__name__.upper())
def __eq__(self, other):
return other is self
Другой способ - украсить ваш класс. Следуя примеру из этого ответа, создайте класс Singleton:
class Singleton:
"""
A non-thread-safe helper class to ease implementing singletons.
This should be used as a decorator -- not a metaclass -- to the
class that should be a singleton.
The decorated class can define one `__init__` function that
takes only the `self` argument. Other than that, there are
no restrictions that apply to the decorated class.
To get the singleton instance, use the `Instance` method. Trying
to use `__call__` will result in a `TypeError` being raised.
Limitations: The decorated class cannot be inherited from.
"""
def __init__(self, decorated):
self._decorated = decorated
def Instance(self):
"""
Returns the singleton instance. Upon its first call, it creates a
new instance of the decorated class and calls its `__init__` method.
On all subsequent calls, the already created instance is returned.
"""
try:
return self._instance
except AttributeError:
self._instance = self._decorated()
return self._instance
def __call__(self):
raise TypeError('Singletons must be accessed through `Instance()`.')
def __instancecheck__(self, inst):
return isinstance(inst, self._decorated)
Для использования вы можете использовать метод Instance
@Singleton
class Single:
def __init__(self):
self.name=None
self.val=0
def getName(self):
print(self.name)
x=Single.Instance()
y=Single.Instance()
x.name='I\'m single'
x.getName() # outputs I'm single
y.getName() # outputs I'm single