Asterisks in Python
treyhunner.com
treyhunner.com
It's not contradictory. You should never use args and kwargs unless you need them. Usually they're only needed in relatively rare situations, like A) when you're building a dictionary dynamically without knowing what the key names will be in advance B) when building a library where you want to allow people to subclass a parent class, but also reserve the right to change method signatures in the parent class without breaking the user code.
If developers are just using args and kwargs when calling one normal function from another then you should absolutely should not merge their pull requests until this gets removed.
It makes this big noise about being a super-friendly form of executable pseudocode, but then you open any code example and the first thing you see is two asterisks and the mysterious word "kwargs" (a Swedish dessert perhaps?).
I wouldn't mind except for all the haughty pretense about Python being a language that doesn't do this sort of thing. Dear Python, get a grip, you're just another ASCII-abusing interpreted monstrosity like Perl, you just decided to make programmer life suck with whitespace instead.
It’s kind of a penny wise and pound foolish approach. Arguably it’s not python’s fault, but that’s a poor consolation for folks who have to deal with these messes.
I haven't written numerical code in Go but I am skeptical it will be as maintainable without operators. The lack of a particular feature does not in itself make code more maintainable.
I can't speak to metaclasses, since I have never had to maintain code using them. In my experience they are pretty rare.
I've been writing Python and Go extensively for the last 10 and 5 years respectively. I really recommend giving Go a shot; you can pick it up in an afternoon and it really complements Python well since it excels at a lot of Python's weaknesses (parallelism, performance, static typing, static compilation, dependency managmenet, etc). For example, if I have to write a CLI tool, I almost always use Go since it's so much easier for coworkers to install a single binary than a Python program + dependencies + make sure you have the right version of the interpreter installed.
If Add(x, y) is just as maintainable as x + y, why does Go use operators for the regular numeric types? Why have + at all?
> (...) Although practicality beats purity.
You cannot prevent people from doing stupid things anyway. What you can do is encourage good style, and making the "right way" the path of least resistance. You can write readable code in any language, even Perl, if you use enough effort and discipline, but Python tries to make readable code the easy default.
The "quote" you made up is not representative of the Python culture or the article here, although I'm sure you can find some blogger somewhere with that attitude. They seem to have mostly migrated to other more hip languages though.
I guess Pandas, Matplotlib, SQLAlchemy, etc, etc didn’t get the memo.
> "here's this cool feature for doing something you could do a different way that looks like hieroglyphics and nobody understands but you should totally use it because it's awesome!"
not to
> "explicit is better than implicit" and "there should be one and only one good way"
Generating HTML always felt awkward for me in many languages. From PHP where you sprinkle PHP between HTML or HTML between PHP, to frameworks in various languages where you populate variables and HTML is magically generated.
Somewhere in between there are template languages.
"Dominate" uses Python itself as the template language - this means there never is that "disconnect" where you have to validate your output, neither is it that disconnect coming from generators, nor do you have to learn and use a separate template language. Dominate can look like this:
return hr(), p(
a(
'Start',
href = '/start'
),
style = 'text-align: center;'
)
If the Python is valid, so is the HTML.For me it hit a sweet spot. Dominate uses kwargs. I discovered this and (a?)bused kwargs when I wanted to add some tags of my own.
And regarding the kwargs I had this feeling first of oh no "what is this!?"
I have been programming Python since 2000 and I never used kwargs before. I knew they were there somewhere but I never bothered with them.
I was at first a bit annoyed, but I really wanted to add my own tags and discovered it was actually quite nice if I could intercept arguments in my own tags. You don't have to use kwargs to add your own tags, but you can make the tags more powerful and composable that way.
But kwargs are really only worth it to me if there is a strong component of, "write once, use many times" to it.
You don't have to use kwargs to add your own tags to Dominate, you can just return tuples of building stone tags if you want to.
How much Python experience do you have? I find that in places where it is applicable, this syntax is clear, unambiguous, easy to read, overall much nicer than alternatives.
There should be one-- and preferably only one --obvious way to do it.
I think that "obvious" is the key word here -- it says nothing about the non-obvious ways, nor about which ones are better.The problem here is not with Python itself, but with people looking for a silver bullet which will solve all their problems. In Python, this is presently very obvious with async programming, and people insisting on using it for everything; however, async is useful for only a certain subset of problems one might encounter (and in a few cases it's incredibly useful) while in most situations it adds avoidable complexity. However, this approach is definitely not limited to Python, and every language has its own examples (classes vs prototypes in Javascript is one example).
My advice is: use what you're comfortable with; don't be afraid of learning new techniques, but don't feel obligated to use them at all costs; ignore the Kool Kidz™ pushing fads.
plot(*zip(*L))
What do you usually do? (Probably creating two intermediary lists with a loop?) def decorator(f):
def new_f(*args, **kwargs):
do_something()
return f(*args, **kwargs)
return new_f l = [f(x, y) for x in X for y in Y if x > y]
is the same as this: l = []
for x in X:
for y in Y:
if x > y:
l.append(f(x,y))
If a list comprehension doesn't fit on one line I try to highlight this to any future reader by using one line per thing that would get nested: l = [
f(x, y)
for x in X
for y in Y
if x > y
]
Needless to say, there are still list comprehensions that are complex enough that they ought to be broken out into loops. But being nested isn't enough by itself.That said, I usually won't use deeply nested comprehensions, but sometimes it actually is the clearest way to parse something (e.g. extracting a field from nested JSON.)
citiesByState = {
'WA': ['Seattle', 'Spokane', 'Olympia'],
'OR': ['Portland', 'Salem'],
'CA': ['San Francisco', 'Los Angeles', 'San Diego'],
}
cityToState = {city: state
for state, cities in citiesByState.items()
for city in cities}
I would generally avoid multiple levels in a comprehension but there are some very simple two-level cases like this that I use occasionally. I feel that the code is easy to read, at least. cityToState = Map.fromList
[ (city, state)
| (state, cities) <- Map.toList citiesByState
, city <- cities
]
cityToState = Map.fromList $ do
(state, cities) <- Map.toList citiesByState
city <- cities
pure (city, state)
For multiple “nested loops”, I generally prefer do-notation over both list comprehensions and combinators such as concatMap, unless the structure is simple enough that the combinator version is much shorter.(>>=) = flip concatMap
These days though, you can split it in generator expressions and make it run at the end inside a comprehension, and you get best of both worlds: splitting helps readability, running it all at once in a single comprehension at the end keeps it performing.
[f(a, b) for a in some_list for b in some_list]
or even [f(a, b) for a in some_list for b in some_list if a is not b] import itertools as it
[f(a, b) for a, b in it.product(some_list, some_list) if a is not b] [f(a, b) for a, b in it.permutations(some_list, r=2)]
And then you could also use itertools.starmap instead of using a comprehension
at all, if you wanted.Smthg[i++] and smthg[++i] have two different behaviors. If you tell me that this is clear I probably don't want to read your code.
This alone makes me appreciate print as a function in py3.
f1 = partial(f, 1, 2)
f2 = partial(f1, 3, 4)
f2(5, 6) # equivalent to f(1, 2, 3, 4, 5, 6)
It also supports kwargs. def apply(f, a):
return f(*a) apply(l, print)
is the same as for el in l:
print(el)
which is a number of print statements, rather than 1 print statement with a number of arguments. Help on built-in function sorted in module builtins:
sorted(iterable, /, *, key=None, reverse=False)That is, you cannot say sorted(iterable=range(10)) because 'iterable' is a position-only parameter.
Some pointers. https://www.python.org/dev/peps/pep-0436/
> / Establishes that all the proceeding arguments are positional-only. For now, Argument Clinic does not support functions with both positional-only and non-positional-only arguments. ... (The semantics of / follow a syntax for positional-only parameters in Python once proposed by Guido. [5] )
https://www.python.org/dev/peps/pep-0457/ ("PEP 457 -- Syntax For Positional-Only Parameters" - draft)
> All parameters before the / are positional-only. If / is not specified in a function signature, that function does not accept any positional-only parameters.
This is only for documenting API signatures. There is no way to implement directly in Python code. Eg, that PEP goes on to say:
> This PEP does not propose we implement positional-only parameters in Python. The goal of this PEP is simply to define the syntax, so that:
> - Documentation can clearly, unambiguously, and consistently express exactly how the arguments for a function will be interpreted.
> - The syntax is reserved for future use, in case the community decides someday to add positional-only parameters to the language.
See also https://bugs.python.org/issue21314 , "Add support for partial keyword arguments in extension functions"
Basically, at the C level you can define positional-only parameters such that sorted can not be called as `sorted(iterable=xxx)`, and this was specified using a "/" in argument clinic (https://www.python.org/dev/peps/pep-0436/#special-syntax-for...) and now appears in some docstrings. This is pretty common in builtins e.g. you can't give a name to the first argument of `max`, it doesn't have one (except informally).
Anything before the / is positional-only (the name is informational but not definitional), between / and * it's both positional and keyword, and after * (or *args) it's keyword-only.
def f(a, b):
return a+b
can be called as `f(1, 2)` or `f(b=2, a=1)`, although I'd look at you funny if you did the second.Certain functions implemented in C also have positional only args, so the function of signature
weird(p_only, /, pos, kw=None, * , kw_only=None)
can be called in the following ways (1 is p_only, 2, is pos, 3 is kw, 4 is kw_only) weird(1, 2, 3)
weird(1, 2, 3, kw_only=4)
weird(1, pos=2, kw=3)
weird(1, pos=2)
weird(1, 2, kw_only=4)
...
but not weird(p_only=1, 2)
weird(1,2,3,4)
...https://github.com/python/cpython/blob/9dfa0fe587eae3626ffc9...
def example_cheater(color, flavor, age):
template = 'I am {age} years old and I like to wear {color} hats and eat {flavor} icecream'
return template.format(**locals())
Obviously a contrived example, and it can be argued that using locals() is not very pythonic, but I think it makes the code look much nicer. def example_cheater(color, flavor, age):
return f'I am {age} years old and I like to wear {color} hats and eat {flavor} icecream'
Even nicer! >>> pi = 3.14159; two = 2
>>> f"{two:02d} pi is {2*pi:.2f}"
'02 pi is 6.28'It'd be interesting to see if there are any older languages that supported it too.
https://rosettacode.org/wiki/String_interpolation_(included)...
not really: use f-strings when you're passing in variables as-is (or nearly no work), and format() when you need to do work on them before stringifying them; f-strings are naturally (and obviously) more difficult to read when the variables are big, as it obscures the actual text they're being fit into, and where.
The only natural area for preference to apply is whether to do the work before the format() call, name the variables, and change it to an f-string.. or stick with a multi-line format()
I'm not sure theres any real situation where the choice isn't obvious. Maybe if you're doing something like f"list1: {sorted(a)}\nlist2{sorted(b)}", where the work is rather small, but even then f"list1:{}\nlist2:{}".format(sorted(a), sorted(b)) is just as nice, or rather unsatisfying, as the f-string.
Yes it does. You could construct the string using a for-loop. Thank you for proving my point and then down-voting me.
But practically, there remains (close to) one obvious way to do things, with the choice boiling down to whether or not to name your outputs before string construction (and the choice of f-string vs format naturally falling out). But thats always been a choice.
On a side note: I don’t use comment voting systems, for up or down votes, on pretty much any site including HN. Even if I did, I don’t see why you’d care
I suppose one could do that, philosophically if not practically. What I had in mind was the various syntax sugars used for the same thing, particularly in Ruby. While some ways of doing things in Ruby were convenient, there was a host of other "shortcuts" that added to the confusion, and it was evident that it was the design of the language itself - not an accident - that allowed these shortcuts, at least imo.
> On a side note: I don’t use comment voting systems
I had a feeling you might say that, but I'd already clicked the "reply" button. Sorry for being presumptuous.
Not that I care about the voting system either, but people tend to use it as a cowardly way of showing disapproval.
>>> matrix = [(1,2,3),(4,5,6),(7,8,9)]
>>> list(zip(*matrix))
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
Not as readable as a real maths library, but pretty cool and educational.
Think I came across it in Python in a nut shell, by Alex Martelli
Edit: formatting
An interesting observation is that while python has separate operators for lists ( * ) and dictionaries ( * * )—javascript has only `...`.
> You need to be careful when using multiple times though. Functions in Python can’t have the same keyword argument specified multiple times, so the keys in each dictionary used with must be distinct or an exception will be raised.
This is not an issue in javascript, the unpacked object with repeated keys takes the value of the last one.
{...{a: 1}, ...{a: 2}}
// => {a: 2}
edit: formattingIn JavaScript, the surrounding syntatical context solely decides if it's an object or array splat. But since Python has 2 kinds of parameters, to preserve obviousness when reading code using splats, they went with 2 operators for each parameter kind, and then separated list and dictionary splat analogously.
The other reason to distinguish * and double-* is because * works with any iterable, and dicts are themselves iterables (of their keys). So you can actually use * on dicts, it just does something different:
>>> d = {'a': 1, 'b' : 2}
>>> [*d]
['a', 'b']
You could arguably say that only * makes sense here since it's a list context. But then, as you say, function calls would still be ambiguous because they support both positional and named arguments. This keeps it all unambiguously consistent in all contexts.>>> {{'a': 1}, {'a': 2}} {'a': 2}
So the multiple keyword argument problem isn't an inherent issue with so much as it's a problem with keyword arguments being specified twice (which has been a restriction since before they allowed multiple to be used).
A := []int{1,2}
B := append(A, A...)
fmt.Println(B)
> [1, 2, 1, 2]Side note: I typically bold many sentences in my articles, but this is the first article in a while where I actually bolded nearly no words at all. I wonder what my other articles look like in your browser.
It looks like this: https://i.imgur.com/AfEnDf0.png
And this is what it would look like if was bold. I fiddled in the devtools to make this: https://i.imgur.com/zQQR1gA.png