🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: pl.comp.lang.python
Article #26987

Re: dyskryptory

#26987
From: Rob Wolfe
Date: Sun, 29 Nov 2009 11:52
258 lines
8690 bytes
ak3031884 <ak3031884@gmail.com> writes:

> Tak ten przykład podoba mi sie i pokazuje słuszność stosowania __set__
> i __get__, aczkolwiek w  "starym stylu klas" można to też ładnie
> napisać.
>
> Powiedzcie mi jeszcze jaka jest różnica między funkcjami
> object.__getattr__(self, name) a   def __get__(self, obj, cls):
> object.__setattr__(self, name, value) a def __set__(self, obj, value):
>
> czytając http://docs.python.org/reference/datamodel.html jakoś nie
> bardzo pojąłem wielkość różnicy tych funkcji.

Jeśli masz ambicję zrozumienia deskryptorów, to musisz przeczytać opis 
(najlepszy w sieci, napisany przez twórcę tej koncepcji), 
do którego link już Ci został podany:
http://users.rcn.com/python/download/Descriptor.htm

Gdy tam zajrzysz, to znajdziesz kluczową definicję dla zrozumienia
tego mechanizmu:

def __getattribute__(self, key):
    "Emulate type_getattro() in Objects/typeobject.c"
    v = object.__getattribute__(self, key)
    if hasattr(v, '__get__'):
       return v.__get__(None, self)
    return v

Kluczowa zasada to kolejność wywołań:
1. __getattribute__ jest wołana zawsze jako pierwsza
2. __get__ jest wołana tylko dla atrybutów, które mają
   zdefiniowaną taką metodę, czyli są deskryptorami
3. __getattr__ jest wołana zawsze jako ostatnia
  i tylko wtedy, gdy powyższy mechanizm nie był w stanie
  odnaleźć składowej


Czyli gdy mamy zdefiniowaną tylko `__getattribute__`,
to wołana jest zawsze:

class A(object):
    def __init__(self):
        self.x = 99
        
    def __getattribute__(self, name):
        print '__getattribute__: %r' % name
        v = object.__getattribute__(self, name)
        if hasattr(v, '__get__'):
            return v.__get__(None, self)
        return v

>>> a = A()
>>> a.x
__getattribute__: 'x'
99
>>> a.y
__getattribute__: 'y'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/python-4766uIx.py", line 7, in __getattribute__
AttributeError: 'A' object has no attribute 'y'


Gdy mamy zdefiniowaną `__getattr__`, to wołana jest tylko
przy odwołaniu do *niezdefiniowanej* składowej:


class B(object):
    def __init__(self):
        self.x = 99
        
    def __getattr__(self, name):
        print '__getattr__: %r' % name
        if name == 'y':
            return self.x * 10  # odwolanie do self.x nie powoduje zapetlenia
        raise AttributeError('%r object has no attribute %r'
                             % (self.__class__.__name__, name))

>>> b = B()
>>> b.x
99
>>> b.y
__getattr__: 'y'
990
>>> b.z
__getattr__: 'z'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/python-4766gSA.py", line 10, in __getattr__
AttributeError: 'B' object has no attribute 'z'


Gdy mamy zdefiniowane zarówno `__getattribute__` jak i `__getattr__`,
to wywoływane są w z góry przyjętej kolejności, czyli na początku
`__getattribute__`, a na końcu `__getattr__` (gdy jest odwołanie 
do *niezdefiniowanej* składowej):

class C(object):
    def __init__(self):
        self.x = 99

    def __getattribute__(self, name):
        print '__getattribute__: %r' % name
        v = object.__getattribute__(self, name)
        if hasattr(v, '__get__'):
            return v.__get__(None, self)
        return v
        
    def __getattr__(self, name):
        print '__getattr__: %r' % name
        if name == 'y':
            return self.x * 10  # odwolanie do self.x nie powoduje zapetlenia
        raise AttributeError('%r object has no attribute %r'
                             % (self.__class__.__name__, name))

>>> c = C()
>>> c.x
__getattribute__: 'x'
99
>>> c.y
__getattribute__: 'y'
__getattr__: 'y'
__getattribute__: 'x'
990
>>> c.z
__getattribute__: 'z'
__getattr__: 'z'
__getattribute__: '__class__'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/python-4766tcG.py", line 17, in __getattr__
AttributeError: 'C' object has no attribute 'z'


I teraz, gdy do tej klasy dorzucimy definicję `__get__`, to co się stanie?
Mogą się dziać dziwne rzeczy, gdyż `__get__` w takim przypadku nie ma
najmniejszego sensu:


class D(object):
    def __init__(self):
        self.x = 99

    def __getattribute__(self, name):
        print '__getattribute__: %r' % name
        v = object.__getattribute__(self, name)
        if hasattr(v, '__get__'):
            return v.__get__(None, self)
        return v

    def __get__(self, instance, owner):
        print '__get__: %r' % owner
        return self.x
        
    def __getattr__(self, name):
        print '__getattr__: %r' % name
        if name == 'y':
            return self.x * 10  # odwolanie do self.x nie powoduje zapetlenia
        raise AttributeError('%r object has no attribute %r'
                             % (self.__class__.__name__, name))


>>> d = D()
>>> d.x
__getattribute__: 'x'
99
>>> d.y
__getattribute__: 'y'
__getattr__: 'y'
__getattribute__: 'x'
990
>>> d.z
__getattribute__: 'z'
__getattr__: 'z'
__getattribute__: '__class__'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/python-4766U7Y.py", line 21, in __getattr__
  File "/tmp/python-4766U7Y.py", line 9, in __getattribute__
TypeError: unbound method __get__() must be called with D instance as first argument (got NoneType instance instead)

Zgodnie z dokumentacją [1]_ ma sens tylko dla:
"""
The following methods (przyp. tlum. __get__, __set__, __delete__) only 
apply when an instance of the class containing the method 
(a so-called descriptor class) appears in the class dictionary of
another new-style class, known as the owner class.
"""

czyli po naszemu:

"""
Metody __get__, __set__ i __delete__ mają zastosowanie wtedy i tylko wtedy,
gdy instancja klasy zawierająca te metody (tzw. deskryptor) stanowi składową
innej klasy nowego stylu.
"""

W związku z tym deskryptor ma sens w takim przypadku jak ten, gdzie mamy
oddzielną klasę definiującą składową i jakąś klasę zawierjącą 
tę składową:

class E(object):
    def __init__(self, val):
        self.val = val

    def __get__(self, instance, owner):
        print '__get__: %r' % owner.__class__.__name__
        return self.val


class F(object):
    def __init__(self):
        self.x = E(99)

    def __getattribute__(self, name):
        print '__getattribute__: %r' % name
        v = object.__getattribute__(self, name)
        if hasattr(v, '__get__'):
            return v.__get__(None, self)
        return v

    def __getattr__(self, name):
        print '__getattr__: %r' % name
        if name == 'y':
            return self.x * 10  # odwolanie do self.x nie powoduje zapetlenia
        raise AttributeError('%r object has no attribute %r'
                             % (self.__class__.__name__, name))


>>> f = F()
>>> f.x
__getattribute__: 'x'
__getattribute__: '__class__'
__get__: 'F'
99
>>> f.y
__getattribute__: 'y'
__getattr__: 'y'
__getattribute__: 'x'
__getattribute__: '__class__'
__get__: 'F'
990
>>> f.z
__getattribute__: 'z'
__getattr__: 'z'
__getattribute__: '__class__'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/tmp/python-4766uPl.py", line 26, in __getattr__
AttributeError: 'F' object has no attribute 'z'


A teraz poćwicz ten temat tak z tydzień w różnych wariantach 
i jest pewna szansa, że coś się zacznie klarować. ;)

.. [1] http://docs.python.org/reference/datamodel.html?highlight=__getattr__#object.__getattribute__

RW
Technical Details
Message-ID: <87d431o8dk.fsf@smsnet.pl>
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.gazeta.pl!not-for-mail
References: <slrnhh0li5.ljo.beorn@pino.cyberdeck.pl> <slrnhh0pgf.llt.beorn@pino.cyberdeck.pl> <her5pc$22d$1@inews.gazeta.pl> <62601031-a50a-49d3-b704-4969ea607794@k4g2000yqb.googlegroups.com>

Show Full Headers
Complete Article Headers
Path: mbox-mo1.pugleaf.net!news-archive.icm.edu.pl!news.gazeta.pl!not-for-mail
From: Rob Wolfe <rw@smsnet.pl>
Newsgroups: pl.comp.lang.python
Subject: Re: dyskryptory
Date: Sun, 29 Nov 2009 12:52:55 +0100
Organization: Debian GNU/Linux site
Lines: 258
Message-ID: <87d431o8dk.fsf@smsnet.pl>
References: <slrnhh0li5.ljo.beorn@pino.cyberdeck.pl> <slrnhh0pgf.llt.beorn@pino.cyberdeck.pl> <her5pc$22d$1@inews.gazeta.pl> <62601031-a50a-49d3-b704-4969ea607794@k4g2000yqb.googlegroups.com>
NNTP-Posting-Host: ip-79-175-248-34.cable.smsnet.pl
Mime-Version: 1.0
X-Trace: inews.gazeta.pl 1259495568 8036 79.175.248.34 (29 Nov 2009 11:52:48 GMT)
X-Complaints-To: usenet@agora.pl
NNTP-Posting-Date: Sun, 29 Nov 2009 11:52:48 +0000 (UTC)
Cancel-Lock: sha1:2Nv7Aarx5uE4eMLujpjC9d3odJg=
X-User: rw8899
User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/23.1.50 (gnu/linux)
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit