delegation pattern via descriptor

K

kedra marbun

hello, friendliest prog lang community on earth ;)

i'm feeling that
(0) delegation pattern thru descriptor encourages dedicated delegate
for each task, if feeling: print(benefits)
(1) the delegate is designed to be blind about the class on which the
delegate is attached to

isn't that the two strengthen the coupling between delegator &
delegate
obviously __getattribute__ & his 2 friends of 'object' & 'type' have
what it takes to make it not like that, it's just that they're not
designed to pass it. thus i think it is by design

yes i know there's a way around it, the most obvious to me is defining
your own hooks (which is not trival, at least to me) , but like i
said, it's encouraged

wow, it's almost time for brazil to beat the dutch, sorry Guido ;)
if fifa['wc']['2010'].winner is not brazil: raise SystemError
 
S

Steven D'Aprano

hello, friendliest prog lang community on earth ;)

i'm feeling that
(0) delegation pattern thru descriptor encourages dedicated delegate for
each task, if feeling: print(benefits) (1) the delegate is designed to
be blind about the class on which the delegate is attached to

isn't that the two strengthen the coupling between delegator & delegate
obviously __getattribute__ & his 2 friends of 'object' & 'type' have
what it takes to make it not like that, it's just that they're not
designed to pass it. thus i think it is by design

yes i know there's a way around it, the most obvious to me is defining
your own hooks (which is not trival, at least to me) , but like i said,
it's encouraged

I'm sorry, I don't understand a thing you're saying there. Can you
explain what you mean?
 
K

kedra marbun

if we limit our discussion to py:
why __{get|set|delete}__ don't receive the 'name' & 'class' from
__{getattribute|{set|del}attr}__
'name' is the name that is searched
'class' is the class whose __dict__ has 'name' bound to descriptor

delegator & delegator are terms from delegation pattern (oop) which is
a specialized area of IoC
delegate refers to helper obj, which is descriptor obj in the case of
descriptor
delegator is the obj that delegates task to helper obj

oh crap, brazil lost, but i admit effort of the dutch is awesome
if fifa['wc']['2010'].winner is not netherlands: raise SystemError
#bugfix
 
M

Mark Lawrence

hello, friendliest prog lang community on earth ;)
Flattery will get you everywhere.
[snip]

wow, it's almost time for brazil to beat the dutch, sorry Guido ;)
if fifa['wc']['2010'].winner is not brazil: raise SystemError
Have you run this and get your SystemError yet? :)

Kindest regards.

Mark Lawrence
 
B

Bruno Desthuilliers

kedra marbun a écrit :
if we limit our discussion to py:
why __{get|set|delete}__ don't receive the 'name' & 'class' from
__{getattribute|{set|del}attr}__
'name' is the name that is searched

While it would have been technically possible, I fail to imagine any use
case for this.
 
T

Thomas Jollans

if we limit our discussion to py:
why __{get|set|delete}__ don't receive the 'name' & 'class' from
__{getattribute|{set|del}attr}__
'name' is the name that is searched
'class' is the class whose __dict__ has 'name' bound to descriptor

http://users.rcn.com/python/download/Descriptor.htm#descriptor-protocol

descr.__get__(self, obj, type=None) --> value

descr.__set__(self, obj, value) --> None

descr.__delete__(self, obj) --> None

These methods are passed the object they're operating on. If the name is
in any way important, which is almost certainly is not, it can either be
passed in to the constructor of the descriptor or determined by
introspection.
delegator & delegator are terms from delegation pattern (oop) which is
a specialized area of IoC
delegate refers to helper obj, which is descriptor obj in the case of
descriptor
delegator is the obj that delegates task to helper obj

you are making no sense.
oh crap, brazil lost, but i admit effort of the dutch is awesome
if fifa['wc']['2010'].winner is not netherlands: raise SystemError
#bugfix
 
K

kedra marbun

i'm confused which part that doesn't make sense?
this is my 2nd attempt to py, the 1st was on april this year, it was
just a month, i'm afraid i haven't got the fundamentals right yet. so
i'm gonna lay out how i got to this conclusion, CMIIW

**explanation of feeling (0) on my 1st post**
to me, descriptor is a particular kind of delegation, it takes the job
of coding the delegation by having a contract with programmers that
the tree meta operations (get, set, del) on attr are delegated to the
obj that is bound to the attr
are we agree that descriptor is a kind of delegation?

the mechanism that makes descriptor works is in __getattribute__,
__setattr__, __delattr__ of 'object' & 'type'

now, if i want a single descriptor obj to be delegated to multiple
tasks, i can't do it since __get__ doesn't get info that can be used
to determine which task to do
i must have diff descriptor obj for each task

class Helper:
def __init__(self, name):
self.name = name
def __get__(self, ins, cls):
if self.name == 'task0': ...
elif self.name == 'task1': ...
else: ...

class a:
task0 = Helper('task0')
task1 = Helper('task1')

if __get__ receives the name, then i could do

class Helper:
def __get__(self, ins, cls, name):
...

class a:
task0 = task1 = Helper()

but after explaining this, i have more doubt on my own stmt in the 1st
post "not passing name strengthens the coupling between delegator &
delegate". now i think it's more likely the opposite, it weakens the
coupling at the expense of more objs. moreover, is it wrong to justify
that descriptor is a kind of delegation?

my ego speaks:
- i think, if the name is passed on, programmers can choose to use it
or not (i don't know if it brokes any py principle, i only know KISS)
- i realize that using name as the info that descriptors use to
determine which task to do, is going to strongly couple the descriptor
& the classes that use it, meaning that the descriptor & the user
classes must agree on attr names to be used. a simple solution to it
is by mapping attr names & names used in the descriptor

class Helper:
def __init__(self, name_map=None):
self.name_map = name_map
def __get__(self, ins, cls, name):
if self.name_map is None:
if name == 'task0': ...
...
else:
if self.name_map[name] == 'task0': ...
...


class a:
do_this = do_that = Helper({'do_this':'task0', 'do_that':'task1'})

finally, like all shared things, shared descriptor objs require more
effort for synchronization
 
K

kedra marbun

thanks Greg,
you get most of what i meant
like i said before, i suspect descriptor encourages dedicated / not
shared descriptor obj. this encouragement is expressed in the design,
and the reasons behind the design were the ones that i was asking
about, not how to get around it

now, i'm asking another favor, what about the 2nd point in my 1st post?
 
G

Gregory Ewing

kedra said:
now, i'm asking another favor, what about the 2nd point in my 1st post?

Your original post has dropped off my newsscope, so
you'll have to remind me what the 2nd point was.
 
B

Bruno Desthuilliers

kedra marbun a écrit :
i'm confused which part that doesn't make sense?
this is my 2nd attempt to py, the 1st was on april this year, it was
just a month, i'm afraid i haven't got the fundamentals right yet. so
i'm gonna lay out how i got to this conclusion, CMIIW

**explanation of feeling (0) on my 1st post**
to me, descriptor is a particular kind of delegation, it takes the job
of coding the delegation by having a contract with programmers that
the tree meta operations (get, set, del) on attr are delegated to the
obj that is bound to the attr
are we agree that descriptor is a kind of delegation?

the mechanism that makes descriptor works is in __getattribute__,
__setattr__, __delattr__ of 'object' & 'type'

now, if i want a single descriptor obj to be delegated to multiple
tasks, i can't do it since __get__ doesn't get info that can be used
to determine which task to do
i must have diff descriptor obj for each task

class Helper:
def __init__(self, name):
self.name = name
def __get__(self, ins, cls):
if self.name == 'task0': ...
elif self.name == 'task1': ...
else: ...


Replacing such "big switch" code with polymorphic dispatch is one of the
goals (and feature) of OO. This should be:


class Task0(object):
def __get__(self, obj, cls):
# code here


class Task1(object):
def __get__(self, obj, cls):
# code here


class A(object):
task0 = Task0()
task1 = Task1()


If you have common code to share between TaskO and Task1 then factor it
out into a base class.

if __get__ receives the name, then i could do

class Helper:
def __get__(self, ins, cls, name):
...

class a:
task0 = task1 = Helper()


Yuck.
 
K

kedra marbun

kedra marbun a écrit :








Replacing such "big switch" code with polymorphic dispatch is one of the
  goals (and feature) of OO. This should be:

class Task0(object):
     def __get__(self, obj, cls):
         # code here

class Task1(object):
     def __get__(self, obj, cls):
         # code here

class A(object):
     task0 = Task0()
     task1 = Task1()

If you have common code to share between TaskO and Task1 then factor it
out into a base class.




Yuck.

what's so 'Yuck' about it? ;)
i guess i need a strong stmt: is descriptor a kind of delegation? or
is it not?
* if it is a kind of delegation, then the code that you labeled as
'Yuck' is just a result of applying delegation
what's wrong with delegating multiple tasks to a single obj?

that code is similar to this

class Helper:
def do_this(self, ins): ...
def do_that(self, ins): ...

class a:
delegate = Helper()
def task0(self): self.delegate.do_that(self)
def task1(self): self.delegate.do_this(self)

the diff is that this code manually code the delegation, that's why it
can branches to 2 funcs. while descriptor takes all to __get__,
because it works on more meta lv

* if it's not, then there's nothing to be argued, the name
'descriptor' is perfectly fit: descriptor obj describes attr of class,
with 'describe' translates to: . = del, in py vocabularies. then, to
think a single descriptor obj describing a single attr is acceptable,
it's a common sense
 
K

kedra marbun

Your original post has dropped off my newsscope, so
you'll have to remind me what the 2nd point was.

it's like 'name', it's about info that i think should be passed to
descriptor's __{get|set|delete}__. i wonder what are the reasons for
not passing the class on which the descriptor is attached to, what
pattern is encouraged by this?
 
S

Steven D'Aprano

it's like 'name', it's about info that i think should be passed to
descriptor's __{get|set|delete}__. i wonder what are the reasons for not
passing the class on which the descriptor is attached to, what pattern
is encouraged by this?



Perhaps I'm missing the context, but since the descriptor is passed the
instance, you can easily get the class with type(self) or self.__class__.
There's no need to pass the class as a separate argument.
 
R

Rami Chowdhury

what's so 'Yuck' about it? ;)
i guess i need a strong stmt: is descriptor a kind of delegation? or
is it not?

Thanks for posting the code sample -- it makes your meaning a great deal
clearer. No, descriptors are not delegation as in your sample**, although they
are very flexible and could be used to implement that if you wanted.
* if it's not, then there's nothing to be argued, the name
'descriptor' is perfectly fit: descriptor obj describes attr of class,
with 'describe' translates to: . = del, in py vocabularies. then, to
think a single descriptor obj describing a single attr is acceptable,
it's a common sense

** As I understand it, anyway -- someone please correct me if I'm wrong.
 
B

Bruno Desthuilliers

kedra marbun a écrit :
what's so 'Yuck' about it? ;)

It's an implicit, obfuscated and overcomplicated way to do a very simple
thing. To be true, I just don't see the point of this pattern - Python
has better solutions for delegation (cf the __getattr__ method), and
that's not what descriptors are for.

i guess i need a strong stmt: is descriptor a kind of delegation? or
is it not?

The descriptor protocol is the primary support for computed attributes.
As such it can be used as a support for delegation, but it's not "a kind
of delegation" by itself.
* if it is a kind of delegation, then the code that you labeled as
'Yuck' is just a result of applying delegation
what's wrong with delegating multiple tasks to a single obj?

Nothing wrong with delegating multiple tasks to a single object - but
not that way. If you want to use descriptors to specify which tasks
should be delegated, you'd be better doing it more explicitely - that
is, only use the descriptor as a gateway, and use one descriptor per task.

Else just use __getattr__ !-)

Sorry, I don't have much time to elaborate on this now.
that code is similar to this

class Helper:
def do_this(self, ins): ...
def do_that(self, ins): ...

class a:
delegate = Helper()
def task0(self): self.delegate.do_that(self)
def task1(self): self.delegate.do_this(self)

It's not similar. This second example is explicit, and doesn't couple
Helper to a.


My 2 cents.
 
B

Bruno Desthuilliers

Gregory Ewing a écrit :
I think he wants to have generic descriptors that are
shared between multiple attributes, but have them do
different things based on the attribute name.

I already understood this, but thanks !-)

What I dont understand is what problem it could solve that couldn't be
solved more simply using the either _getattr__ hook or hand-coded
delegation, since such a descriptor would be so tightly coupled to the
host class that it just doesn't make sense writing a descriptor for this.
 
K

kedra marbun

Gregory Ewing a écrit :



I already understood this, but thanks !-)

What I dont understand is what problem it could solve that couldn't be
solved more simply using the either _getattr__ hook or hand-coded
delegation, since such a descriptor would be so tightly coupled to the
host class that it just doesn't make sense writing a descriptor for this.

yeah, i finally can agree descriptor isn't supposed to be used as
delegation in general, it should be the job of __getattr__

however i still think passing name would open up some other
possibilities of use (not necessarily on the basis of sharing
descriptor), surely some of them (all of it?) are bad. sure as hell,
my example is an example of bad use. for now, i conclude that passing
name is too risky, it easily allows bad practices particularly against
the law of Demeter

thanks Bruno

btw, is there a common approach to let the interface of a class that
uses __getattr__, to include names that are delegated?

class A:
def do_this(self): ...

class B:
a = A()
def do_that(self): ...

def __getattr__(self, name):
try:
return types.MethodType(getattr(self.a, name), self)
except AttributeError:
raise AttributeError

how to make 'dir(B)' includes 'do_this', do i have to use __dir__? and
if i use __dir__, somewhat 'help(B)' doesn't work as usual, i haven't
check pydoc.py ;)
 
K

kedra marbun

Perhaps I'm missing the context, but since the descriptor is passed the
instance, you can easily get the class with type(self) or self.__class__.
There's no need to pass the class as a separate argument.

no, the class that i meant is the one that actually has the descriptor
in its __dict__, not instance.__class__

the class obj that you said unecessary-as-arg is what __get__ receives
as the 3rd arg

class Desc:
def __get__(*args): print(args)

class a:
v0 = Desc()

class b(a): pass

b().v0 #(desc, b(), b)
 
B

Bruno Desthuilliers

kedra marbun a écrit :
yeah, i finally can agree descriptor isn't supposed to be used as
delegation in general, it should be the job of __getattr__

however i still think passing name would open up some other
possibilities of use

Nothing prevents you to pass a "name" to the descriptor instance when
instanciating it, ie:

class Desc(object):
def __init__(self, name):
self.name = name
def __get__(self, inst, cls):
# ...
def __set__(self, inst, value):
# ...


class Foo(object):
bar = Desc("bar")
baaz = Desc("baaz")

Ok, this is not necessarily what you were looking for, but it's IMHO
less brittle than relying on which attribute name was looked up (which
is something the descriptor shouldn't have to care about).
btw, is there a common approach to let the interface of a class that
uses __getattr__, to include names that are delegated?

In Python 2.x, not that I know (but it may have passed under my radar).
If what you want it to automate delegation of a set of methods without
too much manual coding, you can use a custom metaclass that will add the
relevant methods to the class, based on (ie) a list (or mapping) of
methods names. But that might be a bit overkill.
class A:
def do_this(self): ...

class B:
a = A()

I don't see the point of using delegation on a class attribute. That's
typically what inheritance is for.
def do_that(self): ...

def __getattr__(self, name):
try:
return types.MethodType(getattr(self.a, name), self)

Err... Did you try the simple way ?
return getattr(self.a, name)
 
G

Gregory Ewing

kedra said:
i wonder what are the reasons for
not passing the class on which the descriptor is attached to, what
pattern is encouraged by this?

The same answer applies. It's assumed that you will be
writing a custom piece of code for each attribute of
each class, and giving each one its own descriptor.

By the time you get to the get or set method of a
descriptor, you've already dispatched on both the
class and attribute name. There is not usually any
point in funnelling things back into a single function
and then dispatching on the class or name again.
Passing the class or name would just add overhead that
was unnecessary in the majority of use cases.
 

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,598
Members
45,156
Latest member
KetoBurnSupplement
Top