# статические свойства и методы классов, декоратор @staticmethod
class Point:
    count = 0

pt = Point()                # создавая экземпляры pt, pt1, pt2 класса Point они непосредственно обращаются к свойству count = 0
pt1 = Point()
pt2 = Point()
print( pt.count, pt1.count, pt2.count ) # вывод в консоль 0 0 0

Point.count = 10            # изменим значение count = 10 в классе Point
pt = Point()                # создавая экземпляры pt, pt1, pt2 класса Point они непосредственно обращаются к измененному свойству count = 10
pt1 = Point()               # экземпляры получают доступ к этой переменной непосредственно из класса Point
pt2 = Point()
print( pt.count, pt1.count, pt2.count ) # вывод в консоль 10 10 10

#
#                       Point
#                     count = 0
#                  ^       ^       ^
#                  pt     pt1     pt2
# каждый из экземпляров обращается к классу Point, берет это св-во и выводит его в консоль
# св-во классов с такими поведениями называют статическими св-ми
# противовес - локальные создаются внутри экземпляров классов

Point.count = 11            # изменим значение count = 11 в классе Point
pt = Point()                # создавая экземпляры pt, pt1, pt2 класса Point они непосредственно обращаются к измененному свойству count = 10
pt1 = Point()               # экземпляры получают доступ к этой переменной непосредственно из класса Point
pt2 = Point()
pt.count = 12               # в экземпляре pt создадим новое св-во count которое не будет связано с св-вом count в классе Point
print( pt.count, pt1.count, pt2.count ) # вывод в консоль 12 11 11

#
#                       Point
#                     count = 0
#                  ^       ^       ^
#                  pt     pt1     pt2
#             count = 12
#    просто меняется статическая переменная в классе Point,
#    мы создаем здесь локальное свойство в экземпляре класса pt - pt.count = 12


# Создадим статические свойства внутри класса
class Point1:
    count = 100

    #  При вызове конструктора создаем локальное св-во self.coordX = x непосредственно в создаваемом экземпляре классов
    #  Теперь в каждом создаваемом экземпляре pt будет появляться локальное св-во coordX, coordY
    def __init__(self, x = 0, y = 0):
        self.coordX = x
        self.coordY = y

Point.count = 111           # изменим значение count = 11 в классе Point
pt = Point1()                # создавая экземпляры pt, pt1, pt2 класса Point они непосредственно обращаются к измененному свойству count = 10
pt1 = Point1()               # экземпляры получают доступ к этой переменной непосредственно из класса Point
pt2 = Point1()
pt.count = 122              # в экземпляре pt создадим новое св-во count которое не будет связано с св-вом count в классе Point

print( pt.count, pt1.count, pt2.count, pt.__init__() )  # вывод в консоль 12 11 11

#                       Point1
#                     count = 10
#                  ^       ^       ^
#                  pt     pt1     pt2
#             count = 10
#             coordX    coordX   coordX
#             coordY    coordY   coordY
#    в каждом экземпляре pt создаются соответствующие св-ва coordX, coordY
#    статическое свойство внутри класса
#
#
#   Создадим статические методы
#   По идее, любой метод, объявленный в классе, ведет себя как статический.
#   Давайте сделаем наш счетчик приватным и в конструкторе будем его увеличивать на 1,
#   т.е. будем подсчитывать число экземпляров класса Point
class Point2:
    __count = 0                     # сделаем св-во count приватным __count

    def __init__(self, x=0, y=0):   # в конструкторе __init__ при создании экземпляра класса pt
        Point2.__count += 1          # будем увеличивать счетчик на 1
        self.coordX = x
        self.coordY = y

    # (self) необходим в методе getCount
    # мы то знаем, что будем использовать этот метод как статический и это явно нужно указать интерпретатору Python.
    # воспользуемся специальным декоратором функций @staticmethod - теперь метод getCount следует воспринимать как статический
    # создали чисто статический метод getCount(), который вызывается из класса Point2
    # внутри метода getCount() можем работать только со свойствами и методами класса Point2
    @staticmethod
    def getCount():             # Создадим метод getCount, который будет возвращать
        return Point2.__count       # приватное значение __count из класса Point2

pt = Point2()                # создавая экземпляры pt, pt1, pt2 класса Point они непосредственно обращаются к измененному свойству count = 0
pt1 = Point2()               # экземпляры получают доступ к этой переменной непосредственно из класса Point
pt2 = Point2()               # три раза создали экземпляр класса Point
#print(pt.getCount())         # выведем значение счетчика = 3, три раза вызвали конструктор __init__
                             # счетчик .__count увеличился три раза = 3
#Point2.getCount()            # error - не можем вызвать из-за self в методе getCount
#Point2.getCount(pt)          # либо указать экземпляр класса, либо что угодно, и ошибки не будет
#Point2.getCount(1)           # этот метод возвратит нам счетчик __count
print( pt.getCount(), Point2.getCount() )     # вызовем метод getCount() из класса Point2, либо через его экземпляр


# можно переопределить ф-цию, метод getCount()
# def getCount():
#     return 5

# pt.getCount() = getCount()

# print( pt.getCount(), Point2.getCount() )


#                       Point2
#                    __count = 0
#                      getCount()
#                  ^       ^       ^
#                  pt     pt1     pt2
#             count = 10
#             coordX    coordX   coordX
#             coordY    coordY   coordY
#  Обращение к методу getCount() происходит напрямую через класс Point2
#  каждый экземпляр класса pt вызывает этот метод getCount() непосредственно из этого класса Point2
#  т.к. метод getCount() возвращает приватное статическое св-во .__count, которое объявлено в классе Point2, то параметр self не нужен



# Создание класса-синглетон (singleton)
# класс, для которого можно создать только один экземпляр
# Создадим класс Point3, который может создавать только один экземпляр, т.е. это класс синглтон
class Point3:
    __count = 0                     # сделаем св-во count приватным __count
    __instance = None               # добавим в класс Point3 приватную переменную __instance, по умолчанию значение None

    # выполним перегрузку метода __new__,
    # метод __new__ выполняется перед тем как создать экземпляр класса Point3
    # в __new__ создается экземпляр, перегружая его можем контролировать сколько раз этот экземпляр будет создан - нам нужно создать его только один раз
    # параметр cls будет ссылаться на класс Point3

    def __new__(cls, *args, **kwargs):                  # выполним перегрузку метода __new__,
        if not isinstance(cls.__instance, cls):         # проверка если частное св-во __instance неравняется классу cls-Point3, принимает какое-то другое значение, по умолчанию так и есть
            cls.__instance = super(Point3, cls).__new__(cls)       # то мы сдесь должны создать этот экземпляр класса, для его создания обращаемся к базовому классу super(говорим о том что будем создавать (Point3, cls)). вызываем метод __new__ (базового класса cls)
                                                                   # создаем класс Point, ссылку на этот класс присваиваем частному св-ву __instance, __instance меняет свое значение с None на новый созданный экземпляр класса Point3
        else:
            print("Экземпляр класса Point уже создан!") # иначе будем выводить в консоль, что экземпляр класса Point уже создан

        return cls.__instance

    def __init__(self, x=0, y=0):   # в конструкторе __init__ при создании экземпляра класса pt
        Point3.__count += 1          # будем увеличивать счетчик на 1
        self.coordX = x
        self.coordY = y

    @staticmethod
    def getCount():  # Создадим метод getCount, который будет возвращать
        return Point3.__count  # приватное значение __count из класса Point2

pt = Point3()                # создавая экземпляры pt, pt1, pt2 класса Point они непосредственно обращаются к измененному свойству count = 0
pt1 = Point3()               # - в этом случае новый экземпляр класса pt1 не создавался
pt2 = Point3()               # - в этом случае новый экземпляр класса pt2 не создавался
                             # id наших ссылок ссылаются на один и тот же объект
print( id(pt), id(pt1), id(pt2), sep="\n") # выведем id объектов, которые были созданы
                             # дважды было выведено в консоль - Экземпляр класса Point уже создан!

# Недостатки синглтона
# - возможность обойти перегрузку метода __new__, путем создания дочерних кземпляров класса, которые наследовались бы от класса Point
# рассмотрено использование статических св-в классов на практике
# def __new__(cls, *args, **kwargs):
#    if not isinstance(cls.__instance, cls):