Uniform Function Call Syntax (UFCS)

jongiddy jongiddy at gmail.com
Sun Jun 8 04:15:43 EDT 2014


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



More information about the Python-list mailing list