Python built-ins worth learning (2019)
treyhunner.com
treyhunner.com
This reveals something important that is easy to overlook: while you’re actively working in an ecosystem, you may hear about new things that come along, but if you spend some time away, things may well happen that you didn’t hear about, and so in some cases beginners may even surpass you in current idioms. It’s worthwhile, in such cases, at least skimming over some materials and especially the release notes since you were last an expert—if I were going back to working with Python/Django extensively, I’d read at the very least the headings of the release notes for Python 3.6–3.10 and Django 1.8–.
The only thing I really wish for is an ecosystem "what's now" guide, it would list things such as where the community has moved for common libraries. Off the top of my head it would have covered things like nose to pytest and such. Obvious problem being I just can't imagine how you'd get buy-in to make such a thing work, especially without it getting overrun with grifters.
However, taking "Command-line Applications"¹ as an example it lists six alternatives. I think in the version I'd like to see a good few of them would disappear. For example, I'll presume plac/fire/cement aren't in common use given that only two are packaged in Debian and the two that are don't have reverse dependencies. Cliff looks largely uninteresting too, as outside of OpenStack nothing seems to use it.
I think my ideal implementation would probe recent releases of popular packages for dependencies to see what the community has settled on or moved to. I also suspect this comment that somewhat moves the goalposts also points to the difficulties of maintaining such a document.
That said, when I started out using Python professionally, I got a lot of mileage out of this guide.
And btw, such Wiki exists, even officially: https://wiki.python.org/moin/FrontPage
Although, as you might expect, it is a mixture of outdated, more recent, messy, detailed, lists or guides and everything.
What are the current common libraries also doesn't really have a simple answer. Maybe this should be auto-generated based on Google or GitHub trends.
I followed the Python and PSF community very closely for about 12 years through 2018. I haven't kept up for personal reasons. I suspect they are uncomfortable with doing this because they don't want to feel like they are picking winners. Whatever they list will be "blessed" no matter how many disclaimers are attached. In a way, I observe a similar problem impacting how they've approached ecosystem problems like packaging. There is a clear issue and a central authority was needed to sort it out and that never emerged when it was originally required.
Right now PyPI highlights some trends (https://pypi.org), but there is more they could do. Python success stories at https://www.python.org/success-stories/ could probably be evolved to highlight some of the libraries and tools which get serious usage. Anything that's autogenerated from other sources probably has value, but would only be something that would be compatible if it's detached from python.org. Expanding PyPI trend information would be more compatible with python.org.
The only thing I can think of that I had to really read up on was string handling. Everything else I could just google as I came across it. And even after 6 or 7 years, it all comes back really quickly.
Packaging stuff... weren't we using virtualenv 6 or 7 years ago? And even that - it's hardly difficult to get working with.
There are some cases where it can be nice, but often it feels like a somewhat unnatural bolt-on to the language. I guess OO isn't that widely used in JS anyway.
Stuff like nullish coalescing and spread operators however... those things are great - they can save a lot of code.
Input with history and editing:
import readline
while True:
print(input('text>'))
https://docs.python.org/3.8/library/readline.htmlFor instance:
def myfunc(**kwargs):
# we need to be sure kwargs has ['foo'] set, with 'bar' as the default:
kwargs['foo'] = kwargs.get('foo', 'bar')
We could write that this way: def myfunc(**kwargs):
kwargs.setdefault('foo', 'bar') Def myfunc(foo=“Bar”):
passedit
Nevermind, I see what you mean. Yes, it would be equivalent, but it can be used in other scenarios. This was just the one I thought of.
grouped = {}
for (key, value) in ungrouped:
group = new.setdefault(key, [])
group.append(value)
defaultdict(list) would make it even shorter, but setdefault() is more general, as it makes it possible to have a "default" value that depends on the key itself.defaultdict combined with late-binding closures also allows to define nested dictionaries :
nested_dict = lambda: defaultdict(nested)
nested = nested_dict()
nested[0][1][2] = "value" foo = 123
def myfunc(foo="bar"):
global foo # problem! foo = 123
def lol(foo="bar"):
print(globals()["foo"], foo)
That will output "123 bar". But it's terrible!Your example is a SyntaxError, it doesn't even compile:
In [1]: def myfunc(foo="bar"):
...: global foo
Input In [1]
global foo
^
SyntaxError: name 'foo' is parameter and globalThat's why I said nonlocal or global ;)
is_foo_enabled = config.setdefault('foo', False)I just wish there were nicer ways to do these in the global namespace, but poluting builtins is probably not a great idea either. Maybe `slice.itemgetter` or `object.attrgetter`.
print (f’some_var: {some_var}’)
one can just do print (f’{some_var=}’)
What’s the next cool thing I should know about f-strings? some_var='abcd'
Great find! f'{f(x, y)=}' -> 'f(x, y)=5'https://docs.python.org/3/whatsnew/3.8.html#f-strings-suppor...
Now that 3.7 is the minimum supported version, it's safe to use.
f”Total Cost: {round(x / 1000000, 1):,}M”
# Instead of doing this
if len(numbers) == 0:
print("The numbers list is empty")
# Many of us do this
if not numbers:
print("The numbers list is empty")
I tend to write the first form because it is how I think, and I'm writing what I think. "If this list is empty, then..." I suppose a list is only falsy if it is empty. I used to exploit Python's truthyness / falsyness a lot more, but now I like to be more explicit. Ultimately it doesn't matter much, making a single line a tiny bit longer is rarely the most important issue in a project. if numbers == []:
print ("The numbers list is empty")
if your intent is to express "is the list empty?" in the condition?It will fail for non-list inputs, which is another reason it can be handy if you want clarify that you’re enforcing a list.
It will fail for non-list inputs, which is another reason it can be handy if you want clarify that you’re enforcing a list.
len will work for strings, tuples, dicts, sets, plus all kinds of custom classes that implement the __len__ magic method.
Type checking via mypy, pyright, etc., is great, but in library (as opposed to an app) you can't count on consumers doing that, and isinstance() checks don't support duck-typing.
I just want to enforce that the object acts like a list, mainly versus a float, int, or boolean. Tuples are definitely 100% ok, and if someone wants to give me a set, that's almost certainly OK too, in the use cases I'm considering. And so is a numpy vector!
Often these objects are held in a container (a dict or class variable), so type annotations would be clumsy as far as I know. Wouldn't I need to create a separate object to enforce the annotation?
And the type annotation would need to allow tuples and all flavors of lists or numpy arrays.
On the other end of the scale, I don't think either of us wants to use isinstance checks!
My primary intent isn't to be bulletproof, it's to show intent. The main fail of using len() in this way is that it accepts strings.
isinstance(items, Sequence)
Or perhaps: isinstance(items, Iterable) # works with numpy arrays
Or, equivalent to using len() for type checking, you could do: isinstance(items, Sized)It's a protocol (structural) runtime type check, not a class (nominal) runtime type check.
bool(0) -> False
bool(1) -> True
bool(str('0')) -> True
bool(str('')) -> False
bool(None) -> False
If you are checking that a variable is set bool(some_var) doesn't work for 0. def custom_sum(numbers: Optional[List[int]] = None) -> int:
if not numbers:
## something
return sum(numbers)
This can handle both cases - if numbers is None, or numbers is []¹ https://docs.python.org/3/library/itertools.html#itertools-r...
² https://more-itertools.readthedocs.io/en/stable/api.html#mor...
next((I for I in iter if is_thing(I)), default)
It’s slightly more work than any but is pretty much all I need when I need to handle whatever values are returned from the any.If exists(y){x : x % 2 == 0 for x in numbers}: Print y
There is also a “for all” statement which can return the first offender which does not satisfy the condition.
It’s an awkward but incredibly useful language choice - it would be interesting to see it explored more in mainstream languages.
But most Python programmers don't do reverse engineering or NLP work either.
Happy to be proven wrong and learn something though!
Do you have an example of "filling a data class from kwargs"?
def __init__(self, x=None, y=None):
self.x = x
self.y = y
You can do: def __init__(self, **kwargs):
self.__dict__.update(kwargs) # This may be **kwargs, I'm fuzzy on it
With this, the following code works with both: myObj = MyClass(x=1, y=2)
Now, you might say "but with the kwargs method you can add _ANY_ attribute not just x and y!"That's precisely the point. When you're still iterating on the ergonomics, you want the ability to add new things without repeating a bunch of boilerplate. Once you've figured out what those look like, you can and should go back and fix the names in stone.
That aside, I generally wouldn't use the kwargs approach shown in this example either. I'd use https://docs.python.org/3/library/dataclasses.html or https://www.attrs.org/ instead.
Maybe this is the use-case you're talking about, but when I have something super generic I usually have clear and careful error handling inside. It kind of defeats the terseness of using locals(), but the goal is more about flexibility.
# You can use local variables as the dictionary
title="..."
isbn="...."
cursor.execute(sql, locals())
In the pre-dataclass / pre-namedtuple days I would use: class Spam:
def __init__(a=1, b=2, c=3, d="four", .. lots args ..):
self.__dict__.update(locals())
del self.self
in prototype code. But that wouldn't go into production. (I wouldn't put the namedtuple version in production either.)> Determines whether any element of a sequence exists or satisfies a condition.
Since I work in data science I would love to read more “how to solve typical problems elegantly using base …” for both R and Julia.
Is it a community or language thing? E.g, Python comes with more tools to do clever solutions, or is it something else? (Obviously the large user base helps)
> What’s the best way to learn the functions we’ll need day-to-day like enumerate and range?
Day to day? I don’t think I’ve used either, or needed either, over the last several thousand lines of Python I’ve written. Why would those functions be particularly useful?
Enumerate will let you iterate anything iterable while also giving you your current index.
for idx, item in enumerate(foo):
if idx > 100:
break
otherwise()
I use the above example all the time to pluck the first few items from a generator when debugging, for instance. A generator is something that hasn’t been fully actualized, it might be an infinite list or streams for example.Logging is also fun:
for idx, item in enumerate(thing):
logger.info(f’item {idx}: {item!r}’)
yield item
This could be used to wrap an existing iterator to just log each element with a counter prefixed. Also a handy debug tool.range() is useful when you want an iterable of N size.
for i in range(num_tasks_to_create):
foo()
It can be used in this way to “do this thing N times”Or you can use it other ways like:
if year in range(2000, 2020):
tl;dr I can think of a half dozen ways to use either! for el in take(100, iter):
print(el)
Plus there’s a bunch of other things in there for windowing, finding out whether or where empty stuff in the generator are.They’re called next, islice, and islice.
more_itertools is nice but none of those 3 is a compelling argument for it.
>>> head, *tail = range(3)
>>> head
0
>>> tail
[1, 2]
Granted, unpacking also coalesces lazy iterables (like range in this case) into lists, but if you aren't working with large/infinite iterables you should be alright. for line_num, line in enumerate(open("filename.txt"), 1):
if not line.strip():
print(f"Empty line {line_num}.")Only thing is that unvisited links are stylized as purple so I was slightly confused by the first three links I encountered.
That distinction is a bit muddy:
>>> import os
>>> type(os.scandir)
<class 'builtin_function_or_method'>