# UWAGA: wiele z przykładów z tego rozdziału to pliki skryptów,
# które jednak nie zostały nazwane. By je wykonać, należy skopiować
# kod i wkleić do dowolnego pliku tekstowego .py, a następnie
# uruchomić za pomocą polecenia powłoki, IDLE czy innej techniki.
# W tym miejscu książki zakładam, że każdy wie, jak to zrobić.


person.name                        # Pobranie wartości atrybutu
person.name = wartość              # Modyfikacja wartości atrybutu





class Person:
   def getName(self):
      if not valid():
         raise TypeError('nie można pobrać danych')
      else:
         return self.name.transform()
   def setName(self, value):
      if not valid(value):
         raise TypeError('nie można zmienić danych')
      else:
         self.name = transform(value)

person = Person()
person.getName()
person.setName('value')





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




class Person:                            # W wersji 2.6 należy użyć (object)
   def __init__(self, name):
      self._name = name
   def getName(self):
      print('pobieranie...')
      return self._name
   def setName(self, value):
      print('modyfikacja...')
      self._name = value
   def delName(self):
      print('usunięcie...')
      del self._name
   name = property(getName, setName, delName, "Dokumentacja właściwości name")

bob = Person('Robert Zielony')           # bob ma zarządzany atrybut
print(bob.name)                          # Wykonuje getName
bob.name = 'Robert A. Zielony'           # Wykonuje setName
print(bob.name)
del bob.name                             # Wykonuje delName

print('-'*20)
anna = Person('Anna Czerwona')           # anna także dziedziczy właściwość
print(anna.name)
print(Person.name.__doc__)               # Lub help(Person.name)




class Super:
   ...kod oryginalnej klasy Person...
   name = property(getName, setName, delName, 'Dokumentacja właściwości name')

class Person(Super):
   pass                         # Właściwości są dziedziczone

bob = Person('Robert Zielony')
...reszta bez zmian...





class PropSquare:
   def __init__(self, start):
      self.value = start
   def getX(self):                        # Przy pobraniu atrybutów
      return self.value ** 2
   def setX(self, value):                 # Przy przypisaniu atrybutów
      self.value = value
   X = property(getX, setX)               # Brak usuwania i dokumentacji

P = PropSquare(3)                         # 2 instancje klasy z właściwością
Q = PropSquare(32)                        # Każda ma inne informacje o stanie

print(P.X)                                # 3 ** 2
P.X = 4
print(P.X)                                # 4 ** 2
print(Q.X)                                # 32 ** 2





class Person:
   def __init__(self, name):
      self._name = name

   @property
   def name(self):                     # name = property(name)
      "Dokumentacja właściwości name"
      print('pobieranie...')
      return self._name

   @name.setter
   def name(self, value):              # name = name.setter(name)
      print('modyfikacja...')
      self._name = value

   @name.deleter
   def name(self):                     # name = name.deleter(name)
      print('usunięcie...')
      del self._name


bob = Person('Robert Zielony')         # bob ma zarządzany atrybut
print(bob.name)                        # Wykonuje komponent getter dla name (pierwszy dostęp do name)
bob.name = 'Robert A. Zielony'         # Wykonuje komponent setter dla name (drugi dostęp do name)
print(bob.name)
del bob.name                           # Wykonuje komponent deleter dla name (trzeci dostęp do name)

print('-'*20)
anna = Person('Anna Czerwona')         # anna także dziedziczy właściwość
print(anna.name)
print(Person.name.__doc__)             # Lub help(Person.name)





class Descriptor:
   "miejsce na łańcuch znaków dokumentacji"
   def __get__(self, instancja, właściciel): ...         # Zwraca wartość atrybutu
   def __set__(self, instancja, właściciel): ...         # Nic nie zwraca (None)
   def __delete__(self, instancja): ...                  # Nic nie zwraca (None)





>>> class Descriptor(object):
...    def __get__(self, instance, owner):
...       print(self, instance, owner, sep='\n')
...
>>> class Subject:
...    attr = Descriptor()                 # Instancja klasy Descriptor jest atrybutem klasy
...
>>> X = Subject()

>>> X.attr
<__main__.Descriptor object at 0x0281E690>
<__main__.Subject object at 0x028289B0>
<class '__main__.Subject'>

>>> Subject.attr
<__main__.Descriptor object at 0x0281E690>
None
<class '__main__.Subject'>





>>> class D:
...    def __get__(*args): print('pobranie')
...
>>> class C:
...    a = D()
...
>>> X = C()
>>> X.a                              # Wykonuje metodę __get__ odziedziczonego deskryptora
pobranie
>>> C.a
pobranie
>>> X.a = 99                         # Przechowane w X, ukrywa C.a
>>> X.a
99
>>> list(X.__dict__.keys())
['a']
>>> Y = C()
>>> Y.a                              # Y nadal dziedziczy deskryptor
pobranie
>>> C.a
pobranie





>>> class D:
...    def __get__(*args): print('pobranie')
...    def __set__(*args): raise AttributeError('nie można ustawić')
...
>>> class C:
...    a = D()
...
>>> X = C()
>>> X.a                              # Przekierowane do C.a.__get__
pobranie
>>> X.a = 99                         # Przekierowane do C.a.__set__
AttributeError: nie można ustawić





class Name:                            # W Pythonie 2.6 należy użyć (object)
   "Dokumentacja deskryptora name"
   def __get__(self, instance, owner):
      print('pobieranie...')
      return instance._name
   def __set__(self, instance, value):
      print('modyfikacja...')
      instance._name = value
   def __delete__(self, instance):
      print('usunięcie...')
      del instance._name

class Person:                         # W Pythonie 2.6 należy użyć (object)
   def __init__(self, name):
      self._name = name
   name = Name()                      # Przypisanie deskryptora do atrybutu

bob = Person('Robert Zielony')        # bob ma zarządzany atrybut
print(bob.name)                       # Wykonuje Name.__get__
bob.name = 'Robert A. Zielony'        # Wykonuje Name.__set__
print(bob.name)
del bob.name                          # Wykonuje Name.__delete__

print('-'*20)
anna = Person('Anna Czerwona')        # anna także dziedziczy deskryptor
print(anna.name)
print(Name.__doc__)                   # Lub help(Name)





...
class Super:
   def __init__(self, name):
      self._name = name
   name = Name()

class Person(Super):                      # Deskryptory są dziedziczone
   pass
...





class Person:
   def __init__(self, name):
      self._name = name

   class Name:                               # Zastosowanie klasy zagnieżdżonej
      "Dokumentacja deskryptora name"
      def __get__(self, instance, owner):
         print('pobieranie...')
         return instance._name
      def __set__(self, instance, value):
         print('modyfikacja...')
         instance._name = value
      def __delete__(self, instance):
         print('usunięcie...')
         del instance._name
   name = Name()





...
print(Person.Name.__doc__)                 # Różnica: już nie Name.__doc__ poza klasą




class DescSquare:
   def __init__(self, start):               # Każdy deskryptor ma własny stan
      self.value = start
   def __get__(self, instance, owner):      # Przy pobieraniu atrybutów
      return self.value ** 2
   def __set__(self, instance, value):      # Przy przypisywaniu atrybutów
      self.value = value                    # Brak usuwania i dokumentacji

class Client1:
   X = DescSquare(3)                        # Przypisanie instancji deskryptora do atrybutu klasy

class Client2:
   X = DescSquare(32)                       # Inna instancja w innej klasie klienta
                                            # Można także utworzyć kod dwóch instancji tej samej klasy

c1 = Client1()
c2 = Client2()

print(c1.X)                                  # 3 ** 2
c1.X = 4
print(c1.X)                                  # 4 ** 2
print(c2.X)                                  # 32 ** 2





class DescState:                          # Wykorzystanie stanu deskryptora
   def __init__(self, value):
      self.value = value
   def __get__(self, instance, owner):    # Przy pobieraniu atrybutów
      print('pobranie DescState')
      return self.value * 10
   def __set__(self, instance, value):    # Przy przypisywaniu atrybutów
      print('ustawienie DescState')
      self.value = value

class CalcAttrs:
   X = DescState(2)                        # Atrybut klasy deskryptora
   Y = 3                                   # Atrybut klasy
   def __init__(self):
      self.Z = 4                           # Atrybut instancji

obj = CalcAttrs()
print(obj.X, obj.Y, obj.Z)                 # X jest obliczane, pozostałe nie są
obj.X = 5                                  # Przypisanie X jest przechwytywane
obj.Y = 6
obj.Z = 7
print(obj.X, obj.Y, obj.Z)





class InstState:                             # Wykorzystanie stanu instancji
   def __get__(self, instance, owner):
      print('pobranie InstState')            # Założenie ustawienia przez klasę klienta
      return instance._Y * 100
   def __set__(self, instance, value):
      print('ustawienie InstState')
      instance._Y = value

# Klasa klienta

class CalcAttrs:
   X = DescState(2)                          # Atrybut deskryptora klasy
   Y = InstState()                           # Atrybut deskryptora klasy
   def __init__(self):
      self._Y = 3                            # Atrybut instancji
      self.Z = 4                             # Atrybut instancji

obj = CalcAttrs()
print(obj.X, obj.Y, obj.Z)                   # X oraz Y są obliczane, Z nie jest
obj.X = 5                                    # Przypisania X oraz Y są przechwytywane
obj.Y = 6
obj.Z = 7
print(obj.X, obj.Y, obj.Z)





class Property:
   def __init__(self, fget=None, fset=None, fdel=None, doc=None):
      self.fget = fget
      self.fset = fset
      self.fdel = fdel                     # Zapisanie metod bez wiązania
      self.__doc__ = doc                   # lub innych obiektów wywoływalnych

   def __get__(self, instance, instancetype=None):
      if instance is None:
         return self
      if self.fget is None:
         raise AttributeError("nie można pobrać atrybutu")
      return self.fget(instance)           # Przekazanie instancji do self
                                           # w akcesorach właściwości

   def __set__(self, instance, value):
      if self.fset is None:
         raise AttributeError("nie można ustawić atrybutu")
      self.fset(instance, value)

   def __delete__(self, instance):
      if self.fdel is None:
         raise AttributeError("nie można usunąć atrybutu")
      self.fdel(instance)

class Person:
   def getName(self): ...
   def setName(self, value): ...
   name = Property(getName, setName)       # Użyć jak property()





def __getattr__(self, nazwa):            # Przy pobraniu niezdefiniowanego atrybutu [obiekt.nazwa]
def __getattribute__(self, nazwa):       # Przy pobraniu wszystkich atrybutów [obiekt.nazwa]
def __setattr__(self, nazwa, wartość):   # Przy przypisaniu wszystkich atrybutów [obiekt.nazwa=wartość]
def __delattr__(self, nazwa):            # Przy usunięciu wszystkich atrybutów[del obiekt.nazwa]





class Catcher:
   def __getattr__(self, name):
      print('Pobranie:', name)
   def __setattr__(self, name, value):
      print('Ustawienie:', name, value)

X = Catcher()
X.job                            # Wyświetla "Pobranie: job"
X.pay                            # Wyświetla "Pobranie: pay"
X.pay = 99                       # Wyświetla "Ustawienie: pay 99"





class wrapper:
   def __init__(self, object):
      self.wrapped = object                       # Zapisanie obiektu
   def __getattr__(self, attrname):
      print('Śledzenie:', attrname)               # Śledzenie pobrania
      return getattr(self.wrapped, attrname)      # Delegacja pobrania





    def __getattribute__(self, name):
        x = self.other                                PĘTLA!

    def __getattribute__(self, name):
        x = object.__getattribute__(self, 'other')    # Wymuszenie klasy wyżej w celu uniknięcia siebi

    def __setattr__(self, name, value):
        self.other = value                            # PĘTLA!

    def __setattr__(self, name, value):
        self.__dict__['other'] = value                # Użycie słownika atrybutów w celu uniknięcia siebie

    def __setattr__(self, name, value):
        object.__setattr__(self, 'other', value)      # Wymuszenie wyższej klasy nadrzędnej w celu uniknięcia siebie

    def __getattribute__(self, name):
        x = self.__dict__['other']                    # PĘTLA!




class Person:
   def __init__(self, name):              # Dla [Person()]
      self._name = name                   # Wywołuje __setattr__!

   def __getattr__(self, attr):           # Dla [obiekt.niezdefiniowany]
      if attr == 'name':                  # Przechwycenie name: nie przechowano
         print('pobieranie...')
         return self._name                # Nie tworzy pętli: prawdziwy atrybut
      else:                               # Pozostałe są błędami
         raise AttributeError(attr)

   def __setattr__(self, attr, value):    # Dla [obiekt.dowolny = wartość]
      if attr == 'name':
         print('modyfikacja...')
         attr = '_name'                   # Ustawienie nazw wewnętrznych
      self.__dict__[attr] = value         # Tutaj unikanie pętli

   def __delattr__(self, attr):           # Dla [del obiekt.dowolny]
      if attr == 'name':
         print('usunięcie..')
         attr = '_name'                   # Tutaj także unikanie pętli
      del self.__dict__[attr]             # jednak jest to o wiele rzadsze

bob = Person('Robert Zielony')            # bob ma zarządzany atrybut
print(bob.name)                           # Wykonuje __getattr__
bob.name = 'Robert A. Zielony'            # Wykonuje __setattr__
print(bob.name)
del bob.name                              # Wykonuje __delattr__

print('-'*20)
anna = Person('Anna Czerwona')            # anna także dziedziczy właściwość
print(anna.name)
#print(Person.name.__doc__)               # Tutaj brak odpowiednika





    # Należy zastąpić metodę __getattr__ za pomocą poniższej

    def __getattribute__(self, attr):                 # Dla [obiekt.dowolny]
        if attr == 'name':                            # Przechwycenie wszystkich nazw
            print('pobieranie...')
            attr = '_name'                            # Odwzorowanie na nazwę wewnętrzną
        return object.__getattribute__(self, attr)    # Tutaj unikanie pętli




class AttrSquare:
   def __init__(self, start):
      self.value = start               # Wywołuje __setattr__!

   def __getattr__(self, attr):        # Przy pobraniu niezdefiniowanego atrybutu 
      if attr == 'X':
         return self.value ** 2        #Wartość nie jest niezdefiniowana
      else:
         raise AttributeError(attr)

   def __setattr__(self, attr, value): # Przy przypisaniu wszystkich atrybutów
      if attr == 'X':
         attr = 'value'
      self.__dict__[attr] = value

A = AttrSquare(3)                      # 2 instancje klasy z przeciążaniem operatorów
B = AttrSquare(32)                     # Każda ma inne informacje o stanie

print(A.X)                             # 3 ** 2
A.X = 4
print(A.X)                             # 4 ** 2
print(B.X)                             # 32 ** 2





class AttrSquare:
   def __init__(self, start):
      self.value = start               # Wywołuje __setattr__!

   def __getattribute__(self, attr):   # Przy pobraniu wszystkich atrybutów
      if attr == 'X':
         return self.value ** 2        # Znowu wywołuje __getattribute__!
      else:
         return object.__getattribute__(self, attr)

   def __setattr__(self, attr, value): # Przy przypisaniu wszystkich atrybutów
      if attr == 'X':
         attr = 'value'
      object.__setattr__(self, attr, value)





   def __getattribute__(self, attr):
      if attr == 'X':
         return object.__getattribute__(self, 'value') ** 2




class GetAttr:
   attr1 = 1
   def __init__(self):
      self.attr2 = 2
   def __getattr__(self, attr):            # Tylko dla niezdefiniowanych atrybutów
      print('pobieranie: ' + attr)         # Nie attr1: dziedziczony z klasy
      return 3                             # Nie attr2: przechowany w instancji

X = GetAttr()
print(X.attr1)
print(X.attr2)
print(X.attr3)

print('-'*40)

class GetAttribute(object):                # (object) potrzebne jedynie w wersji 2.6
   attr1 = 1
   def __init__(self):
      self.attr2 = 2
   def __getattribute__(self, attr):       # Dla pobrań wszystkich atrybutów
      print('pobieranie: ' + attr)         # Tutaj użycie klasy nadrzędnej w celu uniknięcia pętli
      if attr == 'attr3':
         return 3
      else:
         return object.__getattribute__(self, attr)

X = GetAttribute()
print(X.attr1)
print(X.attr2)
print(X.attr3)





# Dwa dynamicznie obliczane atrybuty z właściwościami

class Powers:
   def __init__(self, square, cube):
      self._square = square                 # _square to wartość bazowa
      self._cube = cube                     # square to nazwa właściwości

   def getSquare(self):
      return self._square ** 2
   def setSquare(self, value):
      self._square = value
   square = property(getSquare, setSquare)

   def getCube(self):
      return self._cube ** 3
   cube = property(getCube)

X = Powers(3, 4)
print(X.square)                             # 3 ** 2 = 9
print(X.cube)                               # 4 ** 3 = 64
X.square = 5
print(X.square)                             # 5 ** 2 = 25





# To samo, ale z wykorzystaniem deskryptorów

class DescSquare:
   def __get__(self, instance, owner):
      return instance._square ** 2
   def __set__(self, instance, value):
      instance._square = value

class DescCube:
   def __get__(self, instance, owner):
      return instance._cube ** 3

class Powers:                             # W wersji 2.6 należy użyć (object)
   square = DescSquare()
   cube = DescCube()
   def __init__(self, square, cube):
      self._square = square               # "self.square = square" także działa,
      self._cube = cube                   # ponieważ wywołuje metodę __set__ deskryptora!

X = Powers(3, 4)
print(X.square)                           # 3 ** 2 = 9
print(X.cube)                             # 4 ** 3 = 64
X.square = 5
print(X.square)                           # 5 ** 2 = 25





# To samo, ale z ogólnym przechwytywaniem niezdefiniowanego atrybutu __getattr__

class Powers:
   def __init__(self, square, cube):
      self._square = square
      self._cube = cube

   def __getattr__(self, name):
      if name == 'square':
         return self._square ** 2
      elif name == 'cube':
         return self._cube ** 3
      else:
         raise TypeError('nieznany atrybut:' + name)

   def __setattr__(self, name, value):
      if name == 'square':
         self.__dict__['_square'] = value
      else:
         self.__dict__[name] = value

X = Powers(3, 4)
print(X.square)                             # 3 ** 2 = 9
print(X.cube)                               # 4 ** 3 = 64
X.square = 5
print(X.square)                             # 5 ** 2 = 25





# To samo, ale z ogólnym przechwytywaniem wszystkich atrybutów za pomocą __getattribute__ 

class Powers:
   def __init__(self, square, cube):
      self._square = square
      self._cube = cube
   def __getattribute__(self, name):
      if name == 'square':
         return object.__getattribute__(self, '_square') ** 2
      elif name == 'cube':
         return object.__getattribute__(self, '_cube') ** 3
      else:
         return object.__getattribute__(self, name)
   def __setattr__(self, name, value):
      if name == 'square':
         self.__dict__['_square'] = value
      else:
         self.__dict__[name] = value

X = Powers(3, 4)
print(X.square)                             # 3 ** 2 = 9
print(X.cube)                               # 4 ** 3 = 64
X.square = 5
print(X.square)                             # 5 ** 2 = 25





### Plik: getattr.py

class GetAttr:
   eggs = 88                      # Obiekt eggs przechowywany w klasie, spam w instancji
   def __init__(self):
      self.spam = 77
   def __len__(self):             # Tutaj len, inaczej __getattr__ wywołane zostanie z __len__
      print('__len__: 42')
      return 42
   def __getattr__(self, attr):   # Dostarczenie __str__ po żądaniu, inaczej pusta funkcja
      print('getattr: ' + attr)
      if attr == '__str__':
         return lambda *args: '[Getattr str]'
      else:
         return lambda *args: None

class GetAttribute(object):       # W wersji 2.6 object jest wymagany, w 3.0 — domniemany
   eggs = 88                      # W 2.6 wszystkie automatycznie są isinstance(object)
   def __init__(self):            # Musi jednak pochodzić od niej, by uzyskać dostęp do narzędzi klas w nowym stylu,
      self.spam = 77              # w tym __getattribute__, niektórych wartości domyślnych __X__
   def __len__(self):
      print('__len__: 42')
      return 42
   def __getattribute__(self, attr):
      print('getattribute: ' + attr)
      if attr == '__str__':
         return lambda *args: '[GetAttribute str]'
      else:
         return lambda *args: None

for Class in GetAttr, GetAttribute:
   print('\n' + Class.__name__.ljust(50, '='))

   X = Class()
   X.eggs                         # Atrybut klasy
   X.spam                         # Atrybut instancji
   X.other                        # Brakujący atrybut
   len(X)                         # __len__ definiowane w sposób bezpośredni

   try:                           # Klasy w nowym stylu muszą obsługiwać [], +; do bezpośredniego wywołania redefiniować
      X[0]                        # __getitem__?
   except:
      print('niepowodzenie []')

   try:
      X + 99                      # __add__?
   except:
      print('niepowodzenie +')

   try:
      X()                         # __call__? (niejawne, za pomocą funkcji wbudowanej)
   except:
      print('niepowodzenie ()')
   X.__call__()                   # __call__? (jawne, nie dziedziczone)

   print(X.__str__())             # __str__? (jawne, dziedziczone po type)
   print(X)                       # __str__? (niejawne , za pomocą funkcji wbudowanej)





C:\misc> c:\python26\python getattr.py

C:\misc> c:\python30\python getattr.py




### Plik: person.py

class Person:
   def __init__(self, name, job=None, pay=0):
      self.name = name
      self.job = job
      self.pay = pay
   def lastName(self):
      return self.name.split()[-1]
   def giveRaise(self, percent):
      self.pay = int(self.pay * (1 + percent))
   def __str__(self):
      return '[Person: %s, %s]' % (self.name, self.pay)

class Manager:
   def __init__(self, name, pay):
      self.person = Person(name, 'manager', pay)   # Osadzenie obiektu Person 
   def giveRaise(self, percent, bonus=.10):
      self.person.giveRaise(percent + bonus)       # Przechwycenie i delegacja
   def __getattr__(self, attr):
      return getattr(self.person, attr)            # Delegacja wszystkich pozostałych atrybutów
   def __str__(self):
      return str(self.person)                      # Ponownie musi przeciążyć operator (w wersji 3.0)

if __name__ == '__main__':
   anna = Person('Anna Czerwona', job='programista', pay=100000)
   print(anna.lastName())
   anna.giveRaise(.10)
   print(anna)
   tom = Manager('Tomasz Czarny', 50000)           # Manager.__init__
   print(tom.lastName())                           # Manager.__getattr__ -> Person.lastName
   tom.giveRaise(.10)                              # Manager.giveRaise -> Person.giveRaise
   print(tom)                                      # Manager.__str__ -> Person.__str__





C:\misc> c:\python30\python person.py        




# Usunięcie metody __str__ klasy Manager

class Manager:
   def __init__(self, name, pay):
      self.person = Person(name, 'manager', pay)   # Osadzenie obiektu Person 
   def giveRaise(self, percent, bonus=.10):
      self.person.giveRaise(percent + bonus)       # Przechwycenie i delegacja
   def __getattr__(self, attr):
      return getattr(self.person, attr)            # Delegacja wszystkich pozostałych atrybutów





C:\misc> c:\python30\python person.py

C:\misc> c:\python26\python person.py



# Zastąpienie __getattr__ za pomocą __getattribute__

class Manager:                                      # W wersji 2.6 należy użyć (object) 
   def __init__(self, name, pay):
      self.person = Person(name, 'manager', pay)    # Osadzenie obiektu Person 
   def giveRaise(self, percent, bonus=.10):
      self.person.giveRaise(percent + bonus)         # Przechwycenie i delegacja
   def __getattribute__(self, attr):
      print('**', attr)
      if attr in ['person', 'giveRaise']:
         return object.__getattribute__(self, attr)  # Pobranie moich atrybutów
      else:
         return getattr(self.person, attr)           # Delegacja dla wszystkich pozostałych





C:\misc> c:\python30\python person.py




# Inny kod __getattribute__ minimalizujący dodatkowe wywołania

class Manager:
   def __init__(self, name, pay):
      self.person = Person(name, 'manager', pay)
   def __getattribute__(self, attr):
      print('**', attr)
      person = object.__getattribute__(self, 'person')
      if attr == 'giveRaise':
         return lambda percent: person.giveRaise(percent+.10)
      else:
         return getattr(person, attr)
   def __str__(self):
      person = object.__getattribute__(self, 'person')
      return str(person)





class CardHolder:
   acctlen = 8                             # Dane klasy
   retireage = 59.5

   def __init__(self, acct, name, age, addr):
      self.acct = acct                     # Dane instancji
      self.name = name                     # Te także wywołują metody ustawiające właściwości
      self.age = age                       # Zmiana nazwy __X w celu uzyskania nazwy klasy
      self.addr = addr                     # Atrybut addr nie jest zarządzany
                                           # Atrybut remain nie ma danych

   def getName(self):
      return self.__name
   def setName(self, value):
      value = value.lower().replace(' ', '_')
      self.__name = value
   name = property(getName, setName)

   def getAge(self):
      return self.__age
   def setAge(self, value):
      if value < 0 or value > 150:
         raise ValueError('niepoprawny wiek')
      else:
         self.__age = value
   age = property(getAge, setAge)

   def getAcct(self):
      return self.__acct[:-3] + '***'
   def setAcct(self, value):
      value = value.replace('-', '')
      if len(value) != self.acctlen:
         raise TypeError('niepoprawny numer konta')
      else:
         self.__acct = value
   acct = property(getAcct, setAcct)

   def remainGet(self):                             # Mógłby być metodą, nie atrybutem
      return self.retireage - self.age              # O ile niewykorzystywany jeszcze jako atrybut
   remain = property(remainGet)





bob = CardHolder('1234-5678', 'Robert Zielony', 40, 'ul. Poziomkowa 15')
print(bob.acct, bob.name, bob.age, bob.remain, bob.addr, sep=' / ')
bob.name = 'Robert A. Zielony'
bob.age = 50
bob.acct = '23-45-67-89'
print(bob.acct, bob.name, bob.age, bob.remain, bob.addr, sep=' / ')

anna = CardHolder('5678-12-34', 'Anna Czerwona', 35, 'ul. Poziomkowa 16')
print(anna.acct, anna.name, anna.age, anna.remain, anna.addr, sep=' / ')
try:
   anna.age = 200
except:
   print('Niepoprawny wiek dla Anny')

try:
   anna.remain = 5
except:
   print("Nie można ustawić anna.remain")

try:
   anna.acct = '1234567'
except:
   print('Niepoprawne konto dla Anny')





class CardHolder:
   acctlen = 8                              # Dane klasy
   retireage = 59.5

   def __init__(self, acct, name, age, addr):
      self.acct = acct                      # Dane instancji
      self.name = name                      # Te także wywołują metodę __set__
      self.age = age                        # Zmiana nazwy __X nie jest konieczna: w deskryptorze
      self.addr = addr                      # atrybut addr nie jest zarządzany
                                            # Atrybut remain nie ma danych

   class Name:
      def __get__(self, instance, owner):   # Zmienne klasy: lokalne dla CardHolder
         return self.name
      def __set__(self, instance, value):
         value = value.lower().replace(' ', '_')
         self.name = value
   name = Name()

   class Age:
      def __get__(self, instance, owner):
         return self.age                    # Użycie danych deskryptora
      def __set__(self, instance, value):
         if value < 0 or value > 150:
            raise ValueError('niepoprawny wiek')
         else:
            self.age = value
   age = Age()

   class Acct:
      def __get__(self, instance, owner):
         return self.acct[:-3] + '***'
      def __set__(self, instance, value):
         value = value.replace('-', '')
         if len(value) != instance.acctlen:           # Użycie danych instancji klasy
            raise TypeError('niepoprawny numer konta')
         else:
            self.acct = value
   acct = Acct()

   class Remain:
      def __get__(self, instance, owner):
         return instance.retireage - instance.age     # Wywołuje Age.__get__
      def __set__(self, instance, value):
         raise TypeError('nie można ustawić remain')  # Inaczej zezwolilibyśmy na ustawienie
   remain = Remain()




class CardHolder:
   acctlen = 8                              # Dane klasy
   retireage = 59.5

   def __init__(self, acct, name, age, addr):
      self.acct = acct                      # Dane instancji
      self.name = name                      # Te także wywołują metodę __setattr__
      self.age = age                        # Zmiana nazwy __acct nie jest konieczna: nazwa jest sprawdzana 
      self.addr = addr                      # Atrybut addr nie jest zarządzany
                                            # Atrybut remain nie ma danych

   def __getattr__(self, name):
      if name == 'acct':                    # Przy pobraniu niezdefiniowanego atrybutu
         return self._acct[:-3] + '***'     # Atrybuty name, age, addr są zdefiniowane
      elif name == 'remain':
         return self.retireage - self.age   # Nie wywołuje__getattr__
      else:
         raise AttributeError(name)

   def __setattr__(self, name, value):
      if name == 'name':                          # Dla wszystkich przypisań do atrybutów
         value = value.lower().replace(' ', '_')  # Atrybut addr przechowany w sposób bezpośredni
      elif name == 'age':                         # Zmiana nazwy acct na _acct
         if value < 0 or value > 150:
            raise ValueError('niepoprawny wiek')
      elif name == 'acct':
         name = '_acct'
         value = value.replace('-', '')
         if len(value) != self.acctlen:
            raise TypeError('niepoprawny numer konta')
      elif name == 'remain':
         raise TypeError('nie można ustawić remain')
      self.__dict__[name] = value                  # Uniknięcie zapętlenia





class CardHolder:
   acctlen = 8                              # Dane klasy
   retireage = 59.5

   def __init__(self, acct, name, age, addr):
      self.acct = acct                      # Dane instancji
      self.name = name                      # Te także wywołują metodę __setattr__
      self.age = age                        # Zmiana nazwy __acct nie jest konieczna: nazwa jest sprawdzana
      self.addr = addr                      # Atrybut addr nie jest zarządzany
                                            # Atrybut remain nie ma danych

   def __getattribute__(self, name):
      superget = object.__getattribute__    # Bez pętli — jeden poziom w górę
      if name == 'acct':                    # Dla wszystkich pobrań atrybutów
         return superget(self, 'acct')[:-3] + '***'
      elif name == 'remain':
         return superget(self, 'retireage') - superget(self, 'age')
      else:
         return superget(self, name)              # name, age, addr: przechowane

   def __setattr__(self, name, value):
      if name == 'name':                          # Dla wszystkich przypisań atrybutów
         value = value.lower().replace(' ', '_')  # Atrybut addr przechowany w sposób bezpośredni
      elif name == 'age':
         if value < 0 or value > 150:
            raise ValueError('niepoprawny wiek')
      elif name == 'acct':
         value = value.replace('-', '')
         if len(value) != self.acctlen:
            raise TypeError('niepoprawny numer konta')
      elif name == 'remain':
         raise TypeError('nie można ustawić remain')
      self.__dict__[name] = value                 # Unikanie pętli, oryginalne nazwy
