Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #72904 > unrolled thread

Uniform Function Call Syntax (UFCS)

Started byjongiddy <jongiddy@gmail.com>
First post2014-06-06 23:45 -0700
Last post2014-06-08 11:00 -0600
Articles 17 on this page of 37 — 8 participants

Back to article view | Back to comp.lang.python


Contents

  Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-06 23:45 -0700
    Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-07 13:20 -0600
      Re: Uniform Function Call Syntax (UFCS) Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2014-06-08 13:27 +1200
        Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 01:26 -0700
          Re: Uniform Function Call Syntax (UFCS) Paul Sokolovsky <pmiscml@gmail.com> - 2014-06-08 15:06 +0300
            Re: Uniform Function Call Syntax (UFCS) Marko Rauhamaa <marko@pacujo.net> - 2014-06-08 18:56 +0300
              Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-08 10:38 -0600
              Re: Uniform Function Call Syntax (UFCS) Paul Sokolovsky <pmiscml@gmail.com> - 2014-06-08 19:40 +0300
              Re: Uniform Function Call Syntax (UFCS) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-06-09 04:33 +0000
                Re: Uniform Function Call Syntax (UFCS) Marko Rauhamaa <marko@pacujo.net> - 2014-06-09 09:25 +0300
                  Re: Uniform Function Call Syntax (UFCS) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-06-09 09:09 +0000
                    Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 19:13 +1000
                      Re: Uniform Function Call Syntax (UFCS) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-06-09 13:37 +0000
                        Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-10 01:08 +1000
            Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 09:24 -0700
              Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 09:34 -0700
              Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-08 10:54 -0600
              Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 03:10 +1000
                Re: Uniform Function Call Syntax (UFCS) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-06-09 03:20 +0000
                  Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 13:44 +1000
                    Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 23:38 -0700
                  Re: Uniform Function Call Syntax (UFCS) Roy Smith <roy@panix.com> - 2014-06-08 23:45 -0400
        Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 02:25 -0700
          Re: Uniform Function Call Syntax (UFCS) Roy Smith <roy@panix.com> - 2014-06-08 10:59 -0400
            Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 08:39 -0700
              Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 02:48 +1000
                Re: Uniform Function Call Syntax (UFCS) Steven D'Aprano <steve+comp.lang.python@pearwood.info> - 2014-06-09 03:53 +0000
                  Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 14:53 +1000
                  Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-09 08:24 -0600
                    Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-09 23:43 -0700
              Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-08 11:08 -0600
              Re: Uniform Function Call Syntax (UFCS) Chris Angelico <rosuav@gmail.com> - 2014-06-09 03:13 +1000
              Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-08 11:24 -0600
                Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 13:35 -0700
      Re: Uniform Function Call Syntax (UFCS) jongiddy <jongiddy@gmail.com> - 2014-06-08 01:15 -0700
        Re: Uniform Function Call Syntax (UFCS) Paul Sokolovsky <pmiscml@gmail.com> - 2014-06-08 14:52 +0300
        Re: Uniform Function Call Syntax (UFCS) Ian Kelly <ian.g.kelly@gmail.com> - 2014-06-08 11:00 -0600

Page 2 of 2 — ← Prev page 1 [2]


#73010

Fromjongiddy <jongiddy@gmail.com>
Date2014-06-08 23:38 -0700
Message-ID<c5540e34-dd73-444e-b6a5-a418b9085df5@googlegroups.com>
In reply to#73001
On Monday, 9 June 2014 04:44:22 UTC+1, Chris Angelico  wrote:
> This could be solved, though, by having a completely different symbol
> that means "the thing on my left is actually the first positional
> parameter in the function call on my right", such as in your example:
> 
> > plus(1, 2) | divide(2)
> 
> This would be absolutely identical to:
> 
> divide(plus(1, 2), 2)
> 
> Maybe you could even make it so that:
> 
> plus(1, 2) x=| divide(y=2)
> 
> is equivalent to
> 
> divide(x=plus(1, 2), y=2)
> 
> for the sake of consistency, and to allow the pipeline to inject
> something someplace other than the first argument.
> 
> I'm not sure whether it'd be as useful in practice, though. It would
> depend partly on the exact syntax used. Obviously the pipe itself
> can't be used as it already means bitwise or, and this needs to be
> really REALLY clear about what's going on. But a data-flow notation
> would be of value in theory, at least.

Perhaps a pipeline symbol plus an insertion marker would work better in Python:

plus(1, 2) ~ divide(x=^, y=2)

f.readlines() ~ map(int, ^) ~ min(^, key=lambda n: n % 10).str() ~ base64.b64encode(^, b'?-') ~ print(^)

Stdio.read_file("foo.jpg") ~ Image.JPEG_decode(^).autocrop().rotate(0.5).grey() ~ Image.PNG_encode(^) ~ Stdio.write_file("foo.png", ^)

[toc] | [prev] | [next] | [standalone]


#73002

FromRoy Smith <roy@panix.com>
Date2014-06-08 23:45 -0400
Message-ID<roy-644CE9.23454208062014@news.panix.com>
In reply to#73000
In article <53952807$0$29988$c3e8da3$5496439d@news.astraweb.com>,
 Steven D'Aprano <steve+comp.lang.python@pearwood.info> wrote:

> (Note that Forth is brilliant here, as it exposes the argument stack and 
> gives you a rich set of stack manipulation commands.)

As does PostScript (which, despite its reputation as a printer format, 
is really a full-fledged programming language).  I suspect that people 
who didn't grow up with RPN (i.e. H/P calculators) find it amazingly 
obtuse.  In much the same way I find Objective-C amazingly obtuse.  Oh, 
wait, that's the other thread.

[toc] | [prev] | [next] | [standalone]


#72959

Fromjongiddy <jongiddy@gmail.com>
Date2014-06-08 02:25 -0700
Message-ID<1dd863ba-09e5-439b-8669-db65f3e999eb@googlegroups.com>
In reply to#72947
On Sunday, 8 June 2014 02:27:42 UTC+1, Gregory Ewing  wrote:
> 
> Also it doesn't sit well with Python's "one obvious
> way to do it" guideline, because it means there are
> *two* equally obvious ways to call a function.

Actually, one of the best arguments against introducing UFCS is that Python currently provides two equivalent ways to check if an instance has an attribute: ask-permission using hasattr and ask-forgiveness using AttributeError.

On the negative side, these currently equivalent (aside from performance) techniques could give different results using UFCS, potentially breaking some code.

On the positive side, that means the proposal would add one "two ways to do something" and eliminate another "two ways to do something", giving a net Zen of Python effect of zero.

[toc] | [prev] | [next] | [standalone]


#72964

FromRoy Smith <roy@panix.com>
Date2014-06-08 10:59 -0400
Message-ID<roy-68FCA5.10591408062014@news.panix.com>
In reply to#72959
In article <1dd863ba-09e5-439b-8669-db65f3e999eb@googlegroups.com>,
 jongiddy <jongiddy@gmail.com> wrote:

> On Sunday, 8 June 2014 02:27:42 UTC+1, Gregory Ewing  wrote:
> > 
> > Also it doesn't sit well with Python's "one obvious
> > way to do it" guideline, because it means there are
> > *two* equally obvious ways to call a function.
> 
> Actually, one of the best arguments against introducing UFCS is that Python 
> currently provides two equivalent ways to check if an instance has an 
> attribute: ask-permission using hasattr and ask-forgiveness using 
> AttributeError.
> 
> On the negative side, these currently equivalent (aside from performance) 
> techniques could give different results using UFCS, potentially breaking some 
> code.

Why?  I assume a language which promoted the global namespace to be in 
the attribute search path (which, as far as I can tell, is what we're 
talking about here) would implement hasattr and raising AttributeError 
in a consistent way.

[toc] | [prev] | [next] | [standalone]


#72965

Fromjongiddy <jongiddy@gmail.com>
Date2014-06-08 08:39 -0700
Message-ID<927afb61-be0e-43a1-8aab-107e77a013fc@googlegroups.com>
In reply to#72964
On Sunday, 8 June 2014 15:59:14 UTC+1, Roy Smith  wrote:
 
> Why?  I assume a language which promoted the global namespace to be in 
> the attribute search path (which, as far as I can tell, is what we're 
> talking about here) would implement hasattr and raising AttributeError 
> in a consistent way.

It's slightly different. Although I used len() as an example, the idea is to allow any function to be used in this way, including local symbols.

e.g. I could define:

def squared(x):
    return x * x

i = 3
i.squared() => 9

j = AClassThatImplements__mul__()
j.squared() => whatever j * j returns

but also:
class AnotherClass:
    def __mul__(self, other):
        ...
    def squared(self):
        return specialised_method_for_calculating_squares()

k = AnotherClass()
k.squared() => calls method, not function

In this case, there is a problem with letting hasattr('squared') return True for these first two instances.  See Ian's post for a description of the problem.

[toc] | [prev] | [next] | [standalone]


#72974

FromChris Angelico <rosuav@gmail.com>
Date2014-06-09 02:48 +1000
Message-ID<mailman.10882.1402246096.18130.python-list@python.org>
In reply to#72965
On Mon, Jun 9, 2014 at 1:39 AM, jongiddy <jongiddy@gmail.com> wrote:
> e.g. I could define:
>
> def squared(x):
>     return x * x
>
> i = 3
> i.squared() => 9
>
> j = AClassThatImplements__mul__()
> j.squared() => whatever j * j returns
>
> but also:
> class AnotherClass:
>     def __mul__(self, other):
>         ...
>     def squared(self):
>         return specialised_method_for_calculating_squares()
>
> k = AnotherClass()
> k.squared() => calls method, not function
>
> In this case, there is a problem with letting hasattr('squared') return True for these first two instances.  See Ian's post for a description of the problem.

class Circle:
    def squared(self):
        raise NotImplementedError("Proven impossible in 1882")

The trouble is that logically Circle does have a 'squared' attribute,
while 3 doesn't; and yet Python guarantees this:

foo.squared()
# is equivalent [1] to
func = foo.squared
func()

Which means that for (3).squared() to be 9, it has to be possible to
evaluate (3).squared, which means that hasattr (which is defined by
attempting to get the attribute and seeing if an exception is thrown)
has to return True.

Except that it's even more complicated than that, because hasattr
wasn't defined in your module, so it has a different set of globals.
In fact, this would mean that hasattr would become quite useless.
(Hmm, PEP 463 might become a prerequisite of your proposal...) It also
means that attribute lookup becomes extremely surprising any time the
globals change; currently, "x.y" means exactly the same thing for any
given object x and attribute y, no matter where you do it.

The only way I can think of for all this to make sense is actually
doing it the other way around. Instead of having x.y() fall back on
y(x), have y(x) attempt x.y() first. To pull this off, you'd need a
special bouncer around every global or builtin... which may be tricky.

class MagicDict(dict):
    def __getitem__(self, item):
        # If this throws, let the exception propagate
        obj = super().__getitem__(item)
        if not callable(obj): return obj
        def bouncer(*a, **kw):
            if len(a)==1 and not kw:
            try: return getattr(a[0], item)()
            except AttributeError: pass
            return obj(*a, **kw)
        return bouncer
import __main__
# Except that this bit doesn't work.
__main__.__dict__ = MagicDict(__main__.__dict__)

It's theoretically possible, along these lines, I think. Whether it's
actually any good or not is another question, though!

ChrisA

[1] Modulo performance. CPython, AFAIK, does this exactly as written,
but other Pythons may and do optimize the actual "foo.squared()" form
to reduce heap usage. But in terms of visible effects, equivalent.

[toc] | [prev] | [next] | [standalone]


#73003

FromSteven D'Aprano <steve+comp.lang.python@pearwood.info>
Date2014-06-09 03:53 +0000
Message-ID<53952fa9$0$29988$c3e8da3$5496439d@news.astraweb.com>
In reply to#72974
On Mon, 09 Jun 2014 02:48:13 +1000, Chris Angelico wrote:

> class Circle:
>     def squared(self):
>         raise NotImplementedError("Proven impossible in 1882")
> 
> The trouble is that logically Circle does have a 'squared' attribute,
> while 3 doesn't; and yet Python guarantees this:
> 
> foo.squared()
> # is equivalent [1] to
> func = foo.squared
> func()
> 
> Which means that for (3).squared() to be 9, it has to be possible to
> evaluate (3).squared, 

Given UFCS, that ought to return the global squared function, curried 
with 3 as its first (and only) argument.

UFCS would be a pretty big design change to Python, but I don't think it 
would be a *problem* as such. It just means that x.y, hasattr(x, y) etc. 
would mean something different to what they currently mean.


> which means that hasattr (which is defined by
> attempting to get the attribute and seeing if an exception is thrown)
> has to return True.

Yes. And this is a problem why?

Obviously it would mean that the semantics of hasattr will be different 
than they are now, but it's still a coherent set of semantics. 

In fact, one can already give a class a __getattr__ method which provides 
UFCS functionality. (Hmmm, you need a way to get the caller's globals. 
You know, this keeps coming up. I think it's high-time Python offered 
this as a supported function.) That's no more a problem than any other 
dynamically generated attribute.

Stick that __getattr__ in object itself, and UFCS is now language wide. 
That would make an awesome hack for anyone wanting to experiment with 
this!



> Except that it's even more complicated than that, because hasattr wasn't
> defined in your module, so it has a different set of globals.

hasattr doesn't care about globals, nor does it need to. hasattr behaves 
like the equivalent to:

def hasattr(obj, name):
    try:
        obj.name
    except AttributeError:
        return False
    return True

give or take. And yes, if accessing your attribute has side effects, 
using hasattr does too:

py> class Spam(object):
...     @property
...     def spam(self):
...             print("Spam spam spam spam LOVERLY SPAAAAM!!!!")
...             return "spam"
...
py> x = Spam()
py> hasattr(x, "spam")
Spam spam spam spam LOVERLY SPAAAAM!!!!
True

If that's a worry to you, you can try inspect.getattr_static.


> In fact,
> this would mean that hasattr would become quite useless. (Hmm, PEP 463
> might become a prerequisite of your proposal...) It also means that
> attribute lookup becomes extremely surprising any time the globals
> change; currently, "x.y" means exactly the same thing for any given
> object x and attribute y, no matter where you do it.

*cough*

class Example:
    def __getattr__(self, name):
        if name == 'module_name':
            if __name__ == '__main__':
                return "NOBODY expects the Spanish Inquisition!"
            else:
                return __name__
        raise AttributeError("no attribute %r" % name)


:-)


-- 
Steven D'Aprano
http://import-that.dreamwidth.org/

[toc] | [prev] | [next] | [standalone]


#73007

FromChris Angelico <rosuav@gmail.com>
Date2014-06-09 14:53 +1000
Message-ID<mailman.10905.1402289625.18130.python-list@python.org>
In reply to#73003
On Mon, Jun 9, 2014 at 1:53 PM, Steven D'Aprano
<steve+comp.lang.python@pearwood.info> wrote:
>> which means that hasattr (which is defined by
>> attempting to get the attribute and seeing if an exception is thrown)
>> has to return True.
>
> Yes. And this is a problem why?
>
> Obviously it would mean that the semantics of hasattr will be different
> than they are now, but it's still a coherent set of semantics.

Coherent perhaps, but in direct opposition to the OP's statement about
how hasattr should return False even if there's a global to be found.

A coherent meaning for this kind of thing would almost certainly not
be possible within the OP's requirements, although it's entirely
possible something sensible could be put together.

(By the way, would (3).squared return a curried reference to squared
as of when you looked it up, or would it return something that
late-binds to whatever 'squared' is in scope as of when you call it?
If the latter, then hasattr would have to always return True, and
getattr would have to return something that does the late-bind lookup
and turns NameError into AttributeError.)

ChrisA

[toc] | [prev] | [next] | [standalone]


#73032

FromIan Kelly <ian.g.kelly@gmail.com>
Date2014-06-09 08:24 -0600
Message-ID<mailman.10914.1402324360.18130.python-list@python.org>
In reply to#73003

[Multipart message — attachments visible in raw view] — view raw

On Jun 8, 2014 9:56 PM, "Steven D'Aprano"
> > which means that hasattr (which is defined by
> > attempting to get the attribute and seeing if an exception is thrown)
> > has to return True.
>
> Yes. And this is a problem why?

Earlier in this thread I pointed out that returning True creates problems
for duck typing. But I'm now convinced that's preferable to making getattr
and hasattr inconsistent.

[toc] | [prev] | [next] | [standalone]


#73073

Fromjongiddy <jongiddy@gmail.com>
Date2014-06-09 23:43 -0700
Message-ID<d3c52137-534f-4f65-a398-c7f6cb5d38b6@googlegroups.com>
In reply to#73032
So, just to summarise the discussion:

There was some very mild support for readable pipelines, either using UFCS or an alternative syntax, but the Pythonic way to make combinations of function and method applications readable is to assign to variables over multiple lines.  Make the code read down, not across.

The idea that a class method could override a function using UFCS didn't get much traction. From Zen of Python, "explicit is better than implicit" means no differences in behaviour, depending on context. The fact that x.y and x.__getattr__ may behave differently under UFCS is also a problem. Since hasattr testing and AttributeError catching are both commonly used now, this could cause real problems, so could probably not be changed until Python 4.

Finally, Gilbert & Sullivan are definitely due a revival.

[toc] | [prev] | [next] | [standalone]


#72977

FromIan Kelly <ian.g.kelly@gmail.com>
Date2014-06-08 11:08 -0600
Message-ID<mailman.10885.1402247374.18130.python-list@python.org>
In reply to#72965
On Sun, Jun 8, 2014 at 10:48 AM, Chris Angelico <rosuav@gmail.com> wrote:
> Except that it's even more complicated than that, because hasattr
> wasn't defined in your module, so it has a different set of globals.
> In fact, this would mean that hasattr would become quite useless.

hasattr is a builtin, so it has no globals at all.  It would have to
use the calling scope for UFCS resolution as in my example
implementation.

[toc] | [prev] | [next] | [standalone]


#72979

FromChris Angelico <rosuav@gmail.com>
Date2014-06-09 03:13 +1000
Message-ID<mailman.10887.1402247607.18130.python-list@python.org>
In reply to#72965
On Mon, Jun 9, 2014 at 3:08 AM, Ian Kelly <ian.g.kelly@gmail.com> wrote:
> On Sun, Jun 8, 2014 at 10:48 AM, Chris Angelico <rosuav@gmail.com> wrote:
>> Except that it's even more complicated than that, because hasattr
>> wasn't defined in your module, so it has a different set of globals.
>> In fact, this would mean that hasattr would become quite useless.
>
> hasattr is a builtin, so it has no globals at all.  It would have to
> use the calling scope for UFCS resolution as in my example
> implementation.

Same difference. It can't simply look for the name in globals(), it
has to figure out based on the caller's globals.

ChrisA

[toc] | [prev] | [next] | [standalone]


#72982

FromIan Kelly <ian.g.kelly@gmail.com>
Date2014-06-08 11:24 -0600
Message-ID<mailman.10890.1402249956.18130.python-list@python.org>
In reply to#72965
On Sun, Jun 8, 2014 at 11:13 AM, Chris Angelico <rosuav@gmail.com> wrote:
> On Mon, Jun 9, 2014 at 3:08 AM, Ian Kelly <ian.g.kelly@gmail.com> wrote:
>> On Sun, Jun 8, 2014 at 10:48 AM, Chris Angelico <rosuav@gmail.com> wrote:
>>> Except that it's even more complicated than that, because hasattr
>>> wasn't defined in your module, so it has a different set of globals.
>>> In fact, this would mean that hasattr would become quite useless.
>>
>> hasattr is a builtin, so it has no globals at all.  It would have to
>> use the calling scope for UFCS resolution as in my example
>> implementation.
>
> Same difference. It can't simply look for the name in globals(), it
> has to figure out based on the caller's globals.

But that would all be done in getattr, so I don't think it affects
hasattr's implementation at all.  Since hasattr doesn't push anything
onto the stack, getattr doesn't have to care whether it was called
directly from Python or indirectly via getattr; either way the scope
it needs is just the top frame of the stack.

Could be a different matter in other implementations, though.

[toc] | [prev] | [next] | [standalone]


#72993

Fromjongiddy <jongiddy@gmail.com>
Date2014-06-08 13:35 -0700
Message-ID<23f69b68-6efb-49b7-bd01-ada3dea027d3@googlegroups.com>
In reply to#72982
On Sunday, 8 June 2014 18:24:28 UTC+1, Ian  wrote:
> 
> But that would all be done in getattr, so I don't think it affects
> hasattr's implementation at all.  Since hasattr doesn't push anything
> onto the stack, getattr doesn't have to care whether it was called
> directly from Python or indirectly via getattr; either way the scope
> it needs is just the top frame of the stack.
> 
> Could be a different matter in other implementations, though.

In CPython, the UFCS would not be done in PyObject_GetAttr() as that would affect hasattr() as well. Instead, it would be implemented in the bytecode for LOAD_ATTR. If LOAD_ATTR was about to return an AttributeError, e.g. for [].len, it would perform the equivalent of a LOAD_NAME operation, with the difference that if the name is not found or is not callable, it returns AttributeError instead of NameError.

If the name is found, then it would return something: for [].len, it would return the len() function wrapped to know that it's first argument was the list, which might be done by creating a fake Method object, as shown in Ian's code.

But getattr([], 'len') and hasattr([], 'len') would both return False.

I'm beginning to think it is too un-Pythonic - too much implicitness, unless it can be spelt differently, something like [].len(_) or [].len(...) to explicitly indicate that it plans to call a function, but might call a method if one is available.

[toc] | [prev] | [next] | [standalone]


#72957

Fromjongiddy <jongiddy@gmail.com>
Date2014-06-08 01:15 -0700
Message-ID<7c2fd803-616d-4daa-b570-3c5de140a1da@googlegroups.com>
In reply to#72928
Thanks for the extensive feedback.  Here's my thoughts on how to address these issues.

On Saturday, 7 June 2014 20:20:48 UTC+1, Ian  wrote:
> 
> It's a nice feature in a statically typed language, but I'm not sure
> how well it would work in a language as dynamic as Python.  There are
> some questions that would need to be addressed.
> 
> 1) Where should the function (or perhaps callable) be looked for?  The
> most obvious place is the global scope.  I think it would be a bit too
> far-reaching and inconsistent with other language features to reach
> directly inside imported modules (not to mention that it could easily
> get to be far too slow in a module with lots of imports). As a result
> it would have to be imported using the "from module import function"
> syntax, rather than the somewhat cleaner "import module" syntax.
> 
> While there's nothing wrong with such imports, I'm not sure I like the
> thought of the language encouraging them any more than necessary.

It would only work on functions in scope. x.len() would only work if len(x) would work.  I actually think this would work better in Python than in D.  In D, "import module;" imports all the symbols from the module, so it is easier to invoke a function unexpectedly.  In Python, "import module" does not fill the namespace with lots of callable symbols, so UFCS would generally work with built-ins, local functions, or functions explicitly imported with "from module import...".   In this case, the need to use the "from module import fname" form can document that something unusual is happening.

> 2) What about getattr and hasattr?  If I call hasattr(x,
> "some_method"), and x has no such attribute, but there is a function
> in the global scope named "some_method", should it return True?  

> If we instead have hasattr return False though, and have getattr raise 
> an exception, then we have this very magical and confusing
> circumstance where getattr(x, 'method') raises an exception but
> x.method does not.  So I don't think that's really a good scenario
> either.

AS you suggest, the preferable route is that hasattr should return False.  The object clearly does not have that attribute.  It is a property of the current module that the object can use "instance.fname".  While the behaviour that hasattr("fname") returns False, but instance.fname works is an exception, and a function could be added to test this quickly, so new code that cares could use:
if hasattr(instance, "fname") or inscopecallable('fname'):

The bigger problem I find is reading other code that uses UFCS and not realising that a "method" is not actually a method of the class, but requires importing a module.  That can cause confusion when trying to use it in your own code.  However, the need to use "from module import fname" would at least link the method name and the module.

> Also the idea makes me nervous in the thought that an incorrect
> attribute access could accidentally and somewhat randomly pick up some
> object from the environment.  

As before, I think the limited number of strange callable objects in most modules in Python protects against this.  Of course, "from module import *" might cause problems, but that is already true.  You need to be extra careful doing this, and should only do it for modules when you have a reasonable understanding of their exported names.

> But if you want to experiment with the idea, here's a (lightly tested)
> mixin that implements the behavior:

Thanks for the headstart! I'll need to read up on descriptors to understand that last bit fully (when a function has a __get__ method).

One problem with your untested code, the superclasses would need to be checked before using UFCS, so the structure is:

try:
    return super().__getattr__(attr)
except AttributeError:
    # resolve using UFCS

[toc] | [prev] | [next] | [standalone]


#72960

FromPaul Sokolovsky <pmiscml@gmail.com>
Date2014-06-08 14:52 +0300
Message-ID<mailman.10875.1402228342.18130.python-list@python.org>
In reply to#72957
Hello,

On Sun, 8 Jun 2014 01:15:43 -0700 (PDT)
jongiddy <jongiddy@gmail.com> wrote:

> Thanks for the extensive feedback.  Here's my thoughts on how to
> address these issues.
> 
> On Saturday, 7 June 2014 20:20:48 UTC+1, Ian  wrote:
> > 
> > It's a nice feature in a statically typed language, but I'm not sure
> > how well it would work in a language as dynamic as Python.  There
> > are some questions that would need to be addressed.
> > 
> > 1) Where should the function (or perhaps callable) be looked for?
> > The most obvious place is the global scope.  I think it would be a
> > bit too far-reaching and inconsistent with other language features
> > to reach directly inside imported modules (not to mention that it
> > could easily get to be far too slow in a module with lots of
> > imports). As a result it would have to be imported using the "from
> > module import function" syntax, rather than the somewhat cleaner
> > "import module" syntax.
> > 
> > While there's nothing wrong with such imports, I'm not sure I like
> > the thought of the language encouraging them any more than
> > necessary.
> 
> It would only work on functions in scope. x.len() would only work if
> len(x) would work.

In other words, you propose you add yet another check for each function
call. But what many people has to say about Python is that it's "slow".
There should be lookout for how to make it faster, not yet slower.


[]

> 
> The bigger problem I find is reading other code that uses UFCS and
> not realising that a "method" is not actually a method of the class,
> but requires importing a module.  That can cause confusion when
> trying to use it in your own code.

Indeed, this UFCS idea adds inefficiency and confusion, but doesn't
appear to solve any reasonable problem or add any firm benefit.



-- 
Best regards,
 Paul                          mailto:pmiscml@gmail.com

[toc] | [prev] | [next] | [standalone]


#72976

FromIan Kelly <ian.g.kelly@gmail.com>
Date2014-06-08 11:00 -0600
Message-ID<mailman.10884.1402246882.18130.python-list@python.org>
In reply to#72957
On Sun, Jun 8, 2014 at 2:15 AM, jongiddy <jongiddy@gmail.com> wrote:
> One problem with your untested code, the superclasses would need to be checked before using UFCS, so the structure is:
>
> try:
>     return super().__getattr__(attr)
> except AttributeError:
>     # resolve using UFCS

And then if UFCS finds nothing, make sure the AttributeError gets reraised.

[toc] | [prev] | [standalone]


Page 2 of 2 — ← Prev page 1 [2]

Back to top | Article view | comp.lang.python


csiph-web