Article View: pl.comp.lang.python
Article #26987Re: dyskryptory
From: Rob Wolfe
Date: Sun, 29 Nov 2009 11:52
Date: Sun, 29 Nov 2009 11:52
258 lines
8690 bytes
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-mailReferences:
<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