Re: PyMyth: Global variables are evil... WRONG!
On Tuesday, November 12, 2013 2:06:09 AM UTC, Rick Johnson wrote: > > > > Justifying Global Variables: > > > > Globals are justified when they are used to communicate > > information between scopes that otherwise were meant to be > > mutually exclusive. One good example would be package sub- > > modules. > Can you please give an example where having a module provide a global variable would work better than any of: 1. providing a module function to change the operation of my module 2. providing a class with a method to change the operation of an instance 3. providing an additional parameter to module functions / instance methods to change operation 4. providing additional module functions / instance methods to perform different operations -- https://mail.python.org/mailman/listinfo/python-list
Re: PyMyth: Global variables are evil... WRONG!
On Tuesday, November 12, 2013 3:05:25 PM UTC, Rick Johnson wrote: > > > > When i type "math.pi", i "feel" as though i'm accessing an > > interface, BUT I'M NOT! I'm not sure where you get the feeling you're accessing an interface. If I typed this, I would feel like I was trying to change a fundamental constant. You have just demonstrated that going into other modules and changing their attributes (whether they are variables, functions or classes) is generally a BAD idea, and I don't think I've ever seen anyone recommend doing this, except possibly as a workaround or for debugging purposes. On the other hand, you initially suggested that modifying globals contained within namespaces (i.e. modules) is a good way to communicate between modules. That is, you suggested in your initial post that going into other modules and changing their attributes is a GOOD idea. This inconsistency is why I was asking for a good example (i.e. a realistic example where the use of global variables provides the best solution). Just because a tool allows you to do something does not make it a good idea. Try this paraphrase of your last post: Ladder designers act like standing on the top rung is SO evil, but then they give us ladders with a top rung, Are they just trying to fool themselves, or fool us? -- https://mail.python.org/mailman/listinfo/python-list
Re: PyMyth: Global variables are evil... WRONG!
On Tuesday, November 12, 2013 5:00:37 PM UTC, Rick Johnson wrote: > > >1. Accept that globals are useful, and make them > > available through a "real" global syntax, not > > some attribute of a module that "appears" to be > > local, but in reality is global. Then prevent > > external mutation of module attributes directly, > > and instead, require that mutation must follow a > > contract defined by internal module "setter" > > functions. > > > >2. Remain convinced that global mutation is evil > > and prevent mutation of all python module > > attributes. You can import modules, but you can't > > mutate their contents. >From your first post, I take it you're not keen on option #2. For #1, module globals are exactly the hierarchically namespaced globals that you desire in your first post, except they are variables, not get/set handlers (which is what I take you to mean by an "interface"). Why not create a PEP to provide handlers for module attributes? You could base it on PEP 213, which describes the same feature for classes. As a bonus, this would trivially support option #2 (e.g. non-mutable math.pi) by raising an exception for the set operator. -- https://mail.python.org/mailman/listinfo/python-list
Re: Classic OOP in Python
On Thursday, 18 June 2015 12:21:29 UTC+1, Jason P. wrote: > > I'm aware of duck typing. The point in using interfaces is to be explicit > about the boundaries of a system. > > Quite a red "Growing Object-Oriented Software, Guided by Tests", by the way. > In fact interfaces are key components in the style of building software they > propose, in good company with TDD. > > Thx! Late to the party, but I have added a package `jute` to PyPI which provides interface checking closer to the model used in Java (and other compiled OO languages). See https://github.com/jongiddy/jute -- https://mail.python.org/mailman/listinfo/python-list
Uniform Function Call Syntax (UFCS)
The language D has a feature called Uniform Function Call Syntax, which allows instance methods to be resolved using function calls. In Python terms, the call: x.len() would first check if 'x' has a method 'len', and would then look for a function 'len', passing 'x' as the first argument. The big wins are: - the ability to override functions with more optimal class-specific implementations. (Of course, len() is a bad example, since we already have a way to override it, but there are other functions that do not have a special method). - the readability of a.b().c().d() vs c(a.b()).d() Here's a few links discussing the feature in D: - First, a fairly gentle "this is cool" post: http://www.kr41.net/2013/08/27/uniform_function_call_syntax_in_d.html - Second, an article from the Walter Bright, the creator of D: http://www.drdobbs.com/cpp/uniform-function-call-syntax/232700394 Has this been discussed or proposed before? I found PEP's 443 and 3124, which provide a form of function overloading, but not reordering. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
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
--
https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
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. This provides a way to do something new (add class-optimized implementations for existing general-purpose functions). It also adds significant readability improvements by putting function-call chains in order. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
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. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
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.
--
https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sunday, 8 June 2014 13:06:08 UTC+1, Paul Sokolovsky wrote: > > Getting x.foo() to call foo(x) is what's bigger problem, which has > serious performance and scoping confusion implications, as discussed in > other mails. The performance hit will only occur when the attribute access is about to throw an AttributeError. Successful attribute accesses would be just as fast as before. And the cost of a symbol lookup is usually considered cheap compared to a thrown exception, so I don't believe there is a serious performance implication. As to the scoping confusion, I repeat that Python benefits from the fact that most modules will only have the builtins and local functions to worry about. This is a small enough space for users to manage. There's no surprises waiting to occur when the user adds or removes normal imports (a problem that can occur in D). > > It also adds > > significant readability improvements by putting function-call chains > > in order. > > Not sure what exactly you mean, but the order is usually pretty obvious > - Python follows mathematical notation for function calls, and OO > standard notation for method calls, one known from primary school, > another from secondary (hopefully). They can be reordered with > parentheses, which is also well-known basic math technique. A contrived example - which of these is easier to understand? from base64 import b64encode # works now print(b64encode(str(min(map(int, f.readlines()), key=lambda n: n % 10)), b'?-')) # would work with UFCS f.readlines().map(int).min(key=lambda n: n % 10).str().b64encode(b'?-').print() You can read the second form left to right, and arguments like b64encode's b'?-' are near the function call, making it a lot more obvious with which function this obscure argument is used. Note, I'm not suggesting either of these examples is good programming, but the same problem does occur in more reasonable scenarios - I just made this example a little extreme to emphasise the readability benefits. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
On Sunday, 8 June 2014 17:24:56 UTC+1, jongiddy wrote: > # would work with UFCS > f.readlines().map(int).min(key=lambda n: n % > 10).str().b64encode(b'?-').print() Ooops - map is the wrong way round to support UFCS in this case. However, with UFCS, I could fix this by changing it to smap, and defining: def smap(seq, func): return map(func, seq) -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
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. -- https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
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", ^)
--
https://mail.python.org/mailman/listinfo/python-list
Re: Uniform Function Call Syntax (UFCS)
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. -- https://mail.python.org/mailman/listinfo/python-list
