Python and the Principle of Least Astonishment
lucumr.pocoo.org
lucumr.pocoo.org
That being said, python is my favorite language right now and the only complain I have is about underscores which I try to avoid.
Now, GO being a new language, I can't really understand why it does not implement methods for primitive types like "hello".ToUpper(), instead we have to import "strings" and call strings.ToUpper("hello").
Easier for the compiler but harder for the programmer has never been my mantra.
Go devs are of the opinion that what behaves like a function should be a function, with no exception. It's also the reason they don't do operator overloading, which I think is just dumb.
With your particular choice of ":", then what would "d={a:length()}" be? Currently it's a 1-element set. What would "words[middle:length()]" do?
Nop, that is exactly what I meant with decisions haunting you forever. You better start a new language than radically change the current implementation.
With regard to my particular choice of ':' there are three solutions:
1. pick any of the 100k unicode chars for internal methods but imperative is to differentiate them to freely use attributes and methods as pleased.
2. force the compiler and the coder to disambiguate the possible same use as internal method: first assign then use n=a:length(), d={n}
3. design the language to use .. as slicer and {a=b} as dictionary assignment.
Again, we are talking about design decisions for a new language, not to change the core of an existing language which is not an easy task. That is exactly why language design is not an exact science an very few make it to stardom.
I agree that seemingly minor decisions can have a big impact in the future of a language. What I disagree with is that this choice of a special syntax for built-in methods is "simple", and I believe that doing it yields a language not meant for stardom.
Sounds like you just have irrational hatred of underscore (or possibly love of colon?)
btw having two or more access operators '.', ':' and distinguishing between "internal/language" and "user" methods is a poor language design choice over having a standard but unenforced __ means "special" and "internal/language" and "user" all being coequal in semantics, syntax, rights and responsibilities.
There is no difference at all, just one char vs five if we count ending double-underscores too, and more visual noise. They both accomplish the same.
Being both the same, what difference do you think there is between, in one hand .init() and .__init__(), and on the other hand .init() and :init()
Just syntactic sugar, I rather pick my colon ;-)
Got it.
For example, if you write a class that quacks like a collection that has a length (it implements .__len__) Python can use that to get a value for bool() even if you didn't implement .__nonzero__.
Neither the article nor your comment has a good explanation of why that is so. The article only harps on about why it's good that it's not called length() or getLength() or size() or getSize(), implying that len() is somehow the god-given name for this feature. And the fallback mechanism you refer to could just as well be implemented if len() was a method of the root class (object) rather than a global method.
I like Python a lot but this is one that I never quite understood and I believe there isn't really a good reason for it, I think it was just a historical accident. Might as well admit it rather than trying to come up with half baked justifications after the fact.
tldr - He thought it more intuitive.
a = 5
def print_a():
print a
# Prints 5
print_a()
def print_and_assign_a():
print a
a = 2
# raises UnboundLocalError
print_and_assign_a()
class PrintOnInitSet(set):
def __init__(self, *args, **kwargs):
print "init!"
set.__init__(self, *args, **kwargs)
# Creates a PrintOnInitSet and prints "init!"
a = PrintOnInitSet([1,2])
# Creates a PrintOnInitSet and prints "init!"
b = PrintOnInitSet([3,4])
# Creates a PrintOnInitSet and prints nothing
c = a | b >>> a = 5
>>> def print_and_assign_a():
... global a
... print(a)
... a = 2
...
>>> print_and_assign_a()
5
>>> print_and_assign_a()
2
Your second example, however, seems to show an implementation detail of Python leaking out, and is probably a bug. Union starts off by making a copy of 'a' and adds the elements of 'b' to it, rather than creating a new object and adding the elements of both.On a hunch that PyPy's implementation would be less special-cased, I installed PyPy and and it actually does print "Init!" on "c = a | b". So, I vote bug in CPython rather than an inconsistency in the language.
One more question arises, however. Should "a = set([1,2])" be equivalent to "a = set(); a.add(1); a.add(2)"? I overloaded 'add' on your PrintOnInitSet and it's not:
>>>> a = PrintOnInitSet(); a.add(1); a.add(2)
init!
Called add with item: 1
Called add with item: 2
>>>> a = PrintOnInitSet([1,2])
init! a = [5]
def print_and_assign_a():
print(a[0])
a[0]=2
It's cool that the second case works in other implementations. I hadn't thought to test that.Why should it be? Adding elements one-by-one is slow and shouldn't be forced on the language model level.
They may be tricky to wrap your head around, but there's nothing too surprising about decorators.
I won't think so. He codes a lot, in general, and in Python. I have read some of his posts and code, and he has a deep understanding of Python and programming in general.
His complain was about @foo and @foo() requires different decorator implementations. If @foo by default meant @foo(), that makes introducing parameters at a later time a bit more straightforward.
I am not arguing about it being a valid expectation. I am just explaining what I think he meant.
@auth_required
def some_view():
pass
And later I decide to change that decorator to be parameterized: @auth_required("basic")
def some_view():
pass
I have now created a massive amount of pain for myself if I decide to define auth_required() such that it defaults its argument to "basic", unless I force people to call it as @auth_required(), because of the differing callable signatures.Some people feel @auth_required() is ugly. That's all.
The biggest issue here is backwards compatibility. Nowadays I just make a separate decorator that accepts arguments and keep the old one around unchanged. I learned my lesson :)
Even if it's inconvenient, python's behavior here is still not surprising. It's perfectly consistent, and more general than automatically adding parentheses.
I have to say though, that the thing that astonished me the most about Python is the Java-like "closures". I sort of thought it would be like Ruby, which I thought was closer to Scheme and JavaScript, and then I realized that Ruby isn't quite like them either. (I know Ruby is close with its lambdas and blocks, but it's not intuitive to me.)
This isn't necessarily to the detriment of Python (or Ruby) but just something I observed when trying to explain Java's lack of closures to someone who only knows Python.
I haven't used Java a lot either, but from what I have done Python's function seems more similar to JavaScript than to Java.
In pre Python 3, the closed over value isn't mutable unless it's a reference to a mutable object.
def counter(num):
def foo():
num += 1
return num
return foo
c = counter(5)
c()
This won't work because you can't mutate the closed variable `start`.This would work in languages with proper closures(Ruby, Perl, Scheme...).
Here is how you do it in Ruby:
def counter(n)
lambda { n += 1 }
end
c = counter(5)
c[] # returns 6
c.call() # Alternate syntax. returns 7
c[] # returns 8
The above python will work in Python 3 if the closed variable is declared `nonlocal`. def counter(num):
def foo():
nonlocal num
num += 1
return num
return foo
Or you can have workarounds in pre Python3. def counter(num):
def foo():
foo.num += 1
return foo.num
foo.num = num
return foo def func():
x = []
def func2():
x.append(1)
return x
return func2
closed = func(); closed(); assert closed() == [1,1]
closed = func(); closed(); assert closed() == [1,1] use strict; use warnings;
sub counter {
my $n = $_[0];
return sub { ++$n }
}
my $c = counter( 5 );
say $c->();
say $c->(); my $c = do {
my $n = 5;
sub { ++$n }
};Java needs the local variables to be final for it to close over them. Python doesn't but you can't directly mutate it. Won't you agree in that sense the statement holds some truth?
I am curious. How does Ruby's closure differ from Scheme's?
def foo (n)
lambda {|i| n += i } end
I expected to be able to do this: acc = foo(0)
acc(1)
But instead have to do acc.call(1)
Maybe there's another way...Edit: oops you pointed out this example in another comment, sorry! Is there no way for the returned lambda to "feel" like a regular function?
The actual surprises section is great.
Is this talk available anywhere?
http://ep2011.europython.eu/conference/speakers/raymond-hett...
Why every time there's a revamp of a website the new design is more astonishing and less clear? Why the search button in Google Calendar is now blue?
Why new design is usually a tease on our muscle memory?