#internals-and-peps
1 messages · Page 147 of 1
the python layers would mostly be abstractions for communication between parts of the program, basically
it won't... that wouldn't be possible
I think I would still have a clear boundary between only allowing imports compatible with the current Python version, and allowing all imports which might need a subinterpreter
Oh
So subprocess it is then?
possible upside: we could use specialized interpreters like cython maybe?
still not sure about communication between the different interpreters, though
I’m more worried about how you’re going to emulate stuff like inheritance or imported decorators
is this to "use" a package that's incompatible with the python that's running?
yes
I think you would basically need a proxy to the module and all of its classes
and any data that wasn't pickleable would need proxied as well (like call args)
oh man
couldn't we just take @red solar's atomic buffer thingy and use that as proxy? 😄
Lol wut? How would that help? 😂
just generating crazy ideas 😄
I think it's possible - GDB has a 'libpython' that basically does it for any python you debug, but it's mainly meant for generating deep repr's of objects
I don't think this would be possible in most languages without code generation, but I think it would in python
time to write a new issue "research: find a way to communicate with incompatible python interpreters as subprocesses" label: "good first issue"
maybe someone like @paper echo takes the bait 😉
hope
@verbal escarp are you also hoping to support py2, or just py3?
I mean imo a really big use case is using python2 libraries that haven’t been updated for python3
wrapt has ObjectProxy
yeah.. well. ideally, you could have a python 3.12 main program and simply use() stuff from any decade without issues or backwards incompatibilities
you could move on with your main code and dependencies would just keep on working like they used to
albeit possibly with a performance penalty
thinking who has done inter-process object proxies ?
maybe we can find someone to help with that in #async-and-concurrency
possibly
have you guys ever tried using python to record electronic instruments that are connected to a laptop \ pc?
imagine if you were to connect an electric guitar or electronic drums into the pc.
Do you think theres a specific channel in the computer i could use to record the data sent in there?
How are you connecting them? There’s probably a library for whatever way you’re doing it
You don’t have the hardware to test it yourself?
i do, but i never tried it
i got an electronic set of a drumkit and guitar
i prolly lack the MIDI connectors
There’s a bunch of MIDI libraries for Python, but I would get the hardware set up before you try using the software
what kind of hardware do you mean? the electronic instruments?
or do i need a powerful Laptop?
you can definitely record audio with Python, i don't have a specific recommendation on what to use but this might be helpful https://wiki.python.org/moin/Audio
i will take a look at it, thanks!
Ah I was thinking more of pymidi
I mean connect your electric instruments to your computer, download some ready made midi software to check it works, and then try to write Python to communicate with your instrument
aite thanks
I was just wondering about these sizes
!e
from sys import getsizeof
print(getsizeof(2))
print(getsizeof([2]))
print(getsizeof([2, 3]))
print(getsizeof([2, 3, 4]))
print(getsizeof([2, 3, 4, 5]))
print(getsizeof([2, 3, 4, 5, 6]))```
@uncut pebble :white_check_mark: Your eval job has completed with return code 0.
001 | 28
002 | 64
003 | 72
004 | 120
005 | 120
006 | 120
found something relevant, going through it now https://towardsdatascience.com/the-strange-size-of-python-objects-in-memory-ce87bdfbb97f
!e
from sys import getsizeof
print(getsizeof([]), getsizeof([2]), getsizeof([2, 3]))
@uncut pebble :white_check_mark: Your eval job has completed with return code 0.
56 64 72
8 bytes for each
!e
from sys import getsizeof as g
print(g([1, 2, 3, 4, 5, 6]), g([1, 2, 3, 4, 5, 6, 7]))```
@uncut pebble :white_check_mark: Your eval job has completed with return code 0.
152 120
???
Is it possible somehow in Windows to only send the SIGINT signal to MainProcess on KeyboardInterrupt? It works fine in Docker as it only sends the a SIGTERM to MainProcess when the container receives a stop signal, but since Windows doesn't handle processes well I have to handle signals in each process when I'm testing locally. Which is fine just bloats the code for local testing
If all child processes receive signals on windows, idk how to change that, but maybe set your own signal handler for SIGINT that checks which process it’s in and only raises KeyboardInterruptException in the main process?
thanks, i just searched mentions on my name to see if somebody answered....
this isnt the right room, perhaps try ot rooms.
yeah, i figured that, sry
!e
!eval [code]
Can also use: e
*Run Python code and get the results.
This command supports multiple lines of code, including code wrapped inside a formatted code block. Code can be re-evaluated by editing the original message within 10 seconds and clicking the reaction that subsequently appears.
We've done our best to make this sandboxed, but do let us know if you manage to find an issue with it!*
!e print("hi")
@serene grail :white_check_mark: Your eval job has completed with return code 0.
hi
why does b'\x9a\xe6\xa8' - a value produced by hypothesis - mess with sha256 and my JACK-encoding roundtrip?
That's because of how lists work. Internally, they are backed by an array of pointers to PyObjects. On a 64 bit system, a memory pointer would take 8 bytes, which is why they grow in increments of 8 up to a certain point. But because the backing array has to be relocated in order to grow, it would be incredibly inefficient to do it for every single append. What python does is, instead of just keeping track of the backing array's length, it also keeps track of the amount of items the list has, which can (and mostly will) be lower than the capacity. This is called amortization, which is done via overallocation, and several other languages do it as well - C#'s List does this, C++'s vector too, so on and so forth
Plus the size includes some bytes of overhead for the actual list object itself (because it too is a PyObject)
doesn't explain why a list of 6 ints would take more memory than a list of 7 ints tho 
python internalizes some values, the list isn't a list of values but of pointers, iirc
they're the same values tho...
it would still need to point to those values
That part comes from the semantics of BUILD_LIST, which is different from just calling a constructor
i think the internalized objects don't count towards the size of objects, it's complicated
It sometimes takes a list that was recently garbage collected, for example
sizeof doesn't give you the size of the objects stored by the list
It only gives you the size of the list object itself, excluding whatever is inside
elaborate on how this means the second list with an extra element takes less memory?
!e
from sys import getsizeof as g
print(g([1, 2, 3, 4, 5, 6, 7]), g([1, 2, 3, 4, 5, 6]))
@sturdy timber :white_check_mark: Your eval job has completed with return code 0.
120 152
funny
I don't know all the semantics and rules, we'd have to look at the source for that. This wouldn't be the case for calling list, it's just the specifics of how that particular case is treated
if python was smart, the answer could be "because it realizes that the small list is part of the longer list and points to the smaller list as part of the longer list" but i doubt python is that smart
can LIST_EXTEND also make the list smaller? like vector::resize() in C++?
!e from sys import getsizeof as g; print(g(list(i for i in range(1, 8))), g(list(i for i in range(1, 7)))
@spice pecan :x: Your eval job has completed with return code 1.
001 | File "<string>", line 1
002 | from sys import getsizeof as g; print(g(list(i for i in range(1, 8))), g(list(i for i in range(1, 7)))
003 | ^
004 | SyntaxError: '(' was never closed
lol
lol
I'm not sure actually
I know dicts can shrink on an insert
/* Guess a result list size. */
n = PyObject_LengthHint(iterable, 8);
if (n < 0) {
Py_DECREF(it);
return NULL;
}
why is it guessing 😂
/* Cut back result list if initial guess was too large. */
if (Py_SIZE(self) < self->allocated) {
if (list_resize(self, Py_SIZE(self)) < 0)
goto error;
}
ah it can be smaller though
Lists probably either can't, or have a strict rule about it so it doesn't keep shrinking and growing on pop-append pairs
Because not every Iterable knows it's length, and some have an estimate, but not the exact length
length_hint dunder, I don't think there's a top level function for that like there is for length
...
lol fun on phone?
Yes
ok that's pretty cool tho, never knew __length_hint__ existed
I believe map is one example of an Iterable with a hint
but map supports len?
"in case it lied"
Nope, it doesn't
if (m > PY_SSIZE_T_MAX - n) {
/* m + n overflowed; on the chance that n lied, and there really
* is enough room, ignore it. If n was telling the truth, we'll
* eventually run out of memory during the loop.
*/
In [130]: operator.length_hint(map(str, "ab"))
Out[130]: 0
In [131]: len(map(str, "ab"))
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-131-7ee12642acc8> in <module>
----> 1 len(map(str, "ab"))
TypeError: object of type 'map' has no len()
Nevermind, no hint dunder I'm questioning things now 
oh map, not dict - idk what map is
oh lmao
i missed that
map(function, iterable, ...)```
Return an iterator that applies *function* to every item of *iterable*, yielding the results. If additional *iterable* arguments are passed, *function* must take that many arguments and is applied to the items from all iterables in parallel. With multiple iterables, the iterator stops when the shortest iterable is exhausted. For cases where the function inputs are already arranged into argument tuples, see [`itertools.starmap()`](https://docs.python.org/3/library/itertools.html#itertools.starmap "itertools.starmap").
it could have a default then (as 0 doesn't really fit even as a hint)
oh that map (i was thinking of a class somewhere)
I think a range iterator should have a normal hint
>>> help(length_hint)
Help on built-in function length_hint in module _operator:
length_hint(obj, default=0, /)
Return an estimate of the number of items in obj.
This is useful for presizing containers when building from an iterable.
If the object supports len(), the result will be exact.
Otherwise, it may over- or under-estimate by an arbitrary amount.
The result will be an integer >= 0.```
Yeah, that's the case
Oh, right
!e ```py
l = ['a', 'b', 'c', 'd', 'e']
print(next(l))
@white nexus :x: Your eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 2, in <module>
003 | TypeError: 'list' object is not an iterator
Iterator and iterables are different???
iterables create iterators
slightly off topic, but i just discovered pycharm has presentation mode, so that's cool 🙂
How?
i can't believe you tried to do this on mobile...
I will throw my phone out the window at this rate
Iterators keep track of the iteration state, the iterable creates that so it's given to a for loop or the user to go through manually
e.g. you can have 5 iterators at different positions created from a single iterable list
Interesting
!e py from sys import getsizeof as g print('BUILD_LIST:', f'{g([1, 2, 3, 4, 5, 6]) = }', f'{g([1, 2, 3, 4, 5, 6, 7]) = }') print('Unpack range:', f'{g([*range(1, 7)]) = }', f'{g([*range(1, 8)]) = }') print('Constructor call:', f'{g(list(range(1, 7))) = }', f'{g(list(range(1, 8))) = }') print('Constructor call with hints stripped:', f'{g(list(i for i in range(1, 7))) = }', f'{g(list(i for i in range(1, 8))) = }')
@spice pecan :white_check_mark: Your eval job has completed with return code 0.
001 | BUILD_LIST: g([1, 2, 3, 4, 5, 6]) = 152 g([1, 2, 3, 4, 5, 6, 7]) = 120
002 | Unpack range: g([*range(1, 7)]) = 152 g([*range(1, 8)]) = 120
003 | Constructor call: g(list(range(1, 7))) = 104 g(list(range(1, 8))) = 112
004 | Constructor call with hints stripped: g(list(i for i in range(1, 7))) = 120 g(list(i for i in range(1, 8))) = 120
@white nexus :white_check_mark: Your eval job has completed with return code 0.
<list_iterator object at 0x7f3805629090>
@white nexus :white_check_mark: Your eval job has completed with return code 0.
a
Well that's cool
this is... a lot to take in lol
!d iter
iter(object[, sentinel])```
Return an [iterator](https://docs.python.org/3/glossary.html#term-iterator) object. The first argument is interpreted very differently depending on the presence of the second argument. Without a second argument, *object* must be a collection object which supports the iteration protocol (the `__iter__()` method), or it must support the sequence protocol (the `__getitem__()` method with integer arguments starting at `0`). If it does not support either of those protocols, [`TypeError`](https://docs.python.org/3/library/exceptions.html#TypeError "TypeError") is raised. If the second argument, *sentinel*, is given, then *object* must be a callable object. The iterator created in this case will call *object* with no arguments for each call to its [`__next__()`](https://docs.python.org/3/library/stdtypes.html#iterator.__next__ "iterator.__next__") method; if the value returned is equal to *sentinel*, [`StopIteration`](https://docs.python.org/3/library/exceptions.html#StopIteration "StopIteration") will be raised, otherwise the value will be returned.
Basically:
- If you use a literal (including unpacking), dark magic happens, I still need to check the source code
- If you create a new list from something with a (correct) hint, there are no surprises, it allocates less for a smaller one
- If you strip the hints, it follows standard amortization rules, so in this case they turned out to be the same size
Stripping the hints (in my case, via using a generator expression) should technically make it equivalent to l = []; l.extend(...)
unpacking counts as using a literal?
LOAD_CONST 2 ((1, 2, 3, 4, 5, 6, 7))
tbh i'm surprised Python can LOAD_CONST a whole ass tuple (which is what's generated from the literal)
I'd put them in a similar category
You have a list literal, that just happens to contain an unpacked iterable
hmm
>>> from sys import getsizeof as g
>>> l = []; l.extend(range(1, 7))
>>> g(l)
152
>>> l = []; l.extend(range(1, 8))
>>> g(l)
120```
Ooooookay, I have more questions
the literal should be equivalent to an extend
yeah the literal is BUILD_LIST, LOAD_CONST, and LIST_EXTEND
Hmm, it would make sense for both LIST_EXTEND and list.extend to be completely equivalent
if I had to guess I'd say it grabbed a free list out of somewhere, although I'm not sure if that'd be so consistent
Are these integers cached? I’m also kinda curious
Integers -5 through 256 are cached, I believe
Them being cached has absolutely no influence on the list's size though
Ah
Objects/listobject.c lines 2881 to 2889
static PyObject *
list___sizeof___impl(PyListObject *self)
/*[clinic end generated code: output=3417541f95f9a53e input=b8030a5d5ce8a187]*/
{
Py_ssize_t res;
res = _PyObject_SIZE(Py_TYPE(self)) + self->allocated * sizeof(void*);
return PyLong_FromSsize_t(res);
}```
This is the code responsible for getting the size of a list, if anyone's interested
So it's simply its own size + the amount of pointers it can store
I keep forgetting the syntax for that :P
I don't know if you can do it from GH directly, and if you were around to see my previous attempts to use this command, you could definitely have a laugh or two
you can select a range with Shift+clicking on lines
huh dope
how were you doing it?
Getting a permalink to a line and manually adding -L...
lmao
Having to remember that they're separated by a - specifically, and the second num also has L prepended, was a chore, and not one I was good at
i wanna see how you're gonna do shift+click on mobile :p
if i really wanted to I could just connect my bluetooth keyboard and break the system
but that would involve getting out of bed and I don't support that
This is getting a bit off-topic, we should probably move this convo
https://github.com/doodspav/atomics/blob/devel-ffi/ARCHITECTURE.md any feedback? (more on the design of the project as described in this file, but feedback on the file itself is also welcome)
also gotta say, the formatting looked nicer when rendered by PyCharm :/
(bear in mind this is ARCHITECTURE, not README, and that README isn't up to date yet)
(it's a single small file, i promise it won't bite)
if classes aren't meant to be constructed by the user, that's not something that should need to be documented
make them private (prefix with _) or make them ABC's
self documenting
i want them for type hinting tho :/
and nothing will break if they do construct the class
maybe i should clarify that they're not intended to be constructed in the sense that they're just no useful, not that they will necessarily do damage
although lemme rewrite the README first
can an ABC define full instance methods?
I didn't get past the first paragraph because I am trying to figure out the point of patomic..
But all I can find are huge files with hundred layer macros https://github.com/doodspav/patomic/blob/devel/src/impl/msvc/msvc.c
ah :/ was hoping one wouldn't need to delve into that :/
lemme finish the readme, and then see if you still need to go to patomic
Can I get a Tl;Dr of what the point of them are?
Both, I don't understand why it exists basically
from threading import Thread
i: int = 0
def fn(n: int) -> None:
global i
for _ in range(n):
i += 1
if __name__ == "__main__":
total = 10_000_000
# run threads to completion
t1 = Thread(target=fn, args=(total // 2,))
t2 = Thread(target=fn, args=(total // 2,))
t1.start(), t2.start()
t1.join(), t2.join()
# print results
print(f"a[{i}] == total[{total}]")
this isn't correct - a will not equal total
import atomics
from threading import Thread
def fn(ai: atomics.INTEGRAL, n: int) -> None:
for _ in range(n):
ai.inc()
if __name__ == "__main__":
# setup
a = atomics.atomic(width=4, atype=atomics.INT)
total = 1_000_000
# run threads to completion
t1 = Thread(target=fn, args=(a, total // 2))
t2 = Thread(target=fn, args=(a, total // 2))
t1.start(), t2.start()
t1.join(), t2.join()
# print results
print(f"a[{a.load()}] == total[{total}]")
this code will work correctly 🙂
@elder blade
(and then also a version that accepts an external buffer, so you can use it in shared memory and stuff, i have a multiprocessing example if you want)
How does that work? Doesn't the GIL ensure that the first codeblock should work?
no - the GIL can switch threads between any 2 bytecodes
and += on an int is like 4 bytecodes
I see alright this is cool
ty 
@red solar I've read it but I am still confused. See I don't know the developer reasoning behind the 3 different classes and that probably meant that nothing really stuck with me.
For the file tree, it seems like it's wrong? There is no atomics.atomics folder?
hmm the file tree reads as if you had installed it, so atomics.src.atomics.atomic -> atomics.atomic
ig i should also make that clear
But even then that doesn't exist
Did you mean atomics._impl.atomic?
yeah lmao
is there a warning that's the opposite of deprecated? like "it's implemented but don't use it yet"?
Usually you'd just not include that in a release, or through documentation say that it's in alpha/beta/pre-release.
By default,
.is_validcalls.is_valid_recommended. The classAlignment
also exposes.is_valid_minimum. Currently, no atomic class makes use of the
minimum alignment, so checking for it is pointless. Support for it will be
added in a future release.
is this good enough documentation for that?
I'd say that nicely encapsulates that it's not really meant to be used
hmm... do you know what alignment is?
For structs?
yeah
Yeah
https://github.com/doodspav/patomic/blob/devel/include/patomic/types/align.h can you tell me if this comment makes sense to you you? (it's a comment on a 3 element struct at the bottom)
Oh then I don't know alignment. I thought you meant alignment as in how bits are aligned in a struct for minimum memory wasted.
this is that, but complicated :/
but ok ig i can't just reference this in my atomics documentation :/
https://github.com/doodspav/atomics/tree/devel-ffi#readme I updated the readme (if anyone's still on)
;-;
How can one profile a python program to determine most common Optcodes and Branches for computer architecture testing?
Well, the primary hook is sys.settrace() - you provide a function, and it'll be called for every line of code that's run. You can then write whatever statistic logic, or do debugging stuff. Of course there are lots of tools already written that do so.
https://docs.python.org/3/library/sys.html#sys.settrace
You might find this helpful https://github.com/faster-cpython/tools/blob/main/scripts/count_opcodes.py
you could use do warnings.warn("This function is in active development and may be changed without notice.", FutureWarning)
or maybe UserWarning
Changed in version 3.7: Previously DeprecationWarning and FutureWarning were distinguished based on whether a feature was being removed entirely or changing its behaviour. They are now distinguished based on their intended audience and the way they’re handled by the default warnings filters.
i guess that's the question you need to ask - who's your audience
if you want to warn other devs, you could subclass DeprecationWarning maybe
btw, i was thinking about the idea of using subprocess-interpreters to fully isolate certain packages - it reminds me of Eiffel or other approaches to component-based software engineering. it could allow for crash-resilient architectures where an attack could only crash a portion of the software but it would automatically reload the part that got killed
also it could allow for snapshots of critical parts and resume operation from those (thinking of numpy encountering division by zero or buffer overflows) without affecting the proper functioning of the whole
you could take a snapshot of the whole interpreter, write it to disk, if it crashed, load it from disk as if nothing happened
the rest of the program wouldn't need to care
of course it doesn't absolve bad software design and proper testing, but it would make software much more resilient against scenarios that couldn't be anticipated
When would you take the snapshot though?
the package would have to signal "i'm in a safe state" to the main program somehow
I can't think of any case that would be helpful, surely you're just saving a state that's about to crash, so if you 'resume' it from the snapshot it would just crash again?
not really, just think of a server that has a component that might be vulnerable (to certain inputs or just random bad luck) so you isolate it, the whole thing is stored while it is in a known stable state (for example before any actual input is handled at all)
and then it could be restored at any time if any combination of inputs caused a crash
ah
if modules were picklable, and you could pickle all of them including the __main__ module, that might be possible
couldn't you just write the memory of the interpreter to disk as a whole?
that would work if you weren't holding external resources
which you are likely to be
and, well... objects hold references to other objects. that is, memory addresses. how are you going to reconstruct them?
well, that's possible if they are all pointers to python types. But given an arbitrary pointer made with ctypes, it is indeed impossible
it's just that - how do you know what 8-byte chunks of this heap are pointers? if it's some kind of c extension or some tricky stdlib type, you can't be sure
hmm, yeah, IG the interpreter is more complex than just a graph of objects
i'd imagine if the package is basically self-contained and references are only accessed via the module-attributes, they could be effectively proxied and isolated that way
in justuse we take a similar approach to swap out the implementation of single modules for reloading, but packages are way more complex
still wonder how to communicate with those isolated objects in a totally different interpreter..
i've never done anything like that before
Not all of the resources owned by an application are held in the application's memory. Things like which files and sockets are open and what physical memory pages are mapped to each virtual memory page are held in kernel memory. So, restoring the process's memory to an earlier snapshot would only restore some parts of the program's state, not all of it.
If after you save and before you restore the interpreter memory the program runs os.close(0), then calling input() after you restore will fail. If it runs munmap, then accessing memory after the restore might fail. If it runs sock.close() on a socket, then calling any method on that socket would fail after a restore. Etc, etc, etc.
that would need to be accounted for by the serialization of the types holding resources, I think
like FileIO objects would need to store the path and position in the file, then they could reopen that file and seek to the same position upon deserialization, perhaps
the file descriptor would be different but hopefully that's just an implementation detail
that's not possible with sockets.
not even with files, once you close a file, that file could no longer exist, have different permissions etc
Yeah. It's not really possible with files, but it's very not possible with sockets. Once a server closes a socket, the client gets a message saying "no more data is coming", and vice versa. You can't undo that.
well, you're just SOL in either case 😄
It's not possible with anonymous memory mappings, either. The "anonymous" part is that they have no identifier referring to them, so there's no way to re-open the mapping once it's closed, because you have no way of identifying which one. (Not to mention that if you close the last reference to it, the kernel just destroys it entirely)
how often do simple interactive sessions use anonymous mappings and long-lived sockets
very often. Anonymous mappings are one of the main ways that memory gets allocated - large chunks of memory are almost always allocated by anonymous memory mappings under the hood by malloc
still, that would be a problem for any process snapshotting / migration mechanism
and stdin/stdout/stderr are examples of long lived pipes, which have exactly the same characteristics as long lived sockets.
well we're talking at the python level, aren't we
there might be some complexities to be worked atound, but they are absolutely solveable with a little cleverness
you could reopen the socket / mapping / whatever, depending on the specific need (or use some other alternative that is cleanly serisalizable)
That's not true - I just explained that you cannot, and why not.
when you close a pipe or socket, the other end is immediately notified. You cannot "undo" that operation. You could delay that - don't really close, and instead do a fake close which you could undo later - but if you do that, it changes the behavior of the program in an observable way. Likewise for writing to a file or socket or pipe. Or reading from a socket or pipe.
Basically, your idea does not work with any type of external resource.
wrong, you can reopen the socket
So, your application is a server, and it has accepted a connection from the client. You close that socket, and the client moves on with its life. Later you decide you want to reopen that socket. How do you do that? How does the client side get reattached?
depends on what the point of the socket is
if it's for an HTTP request, you could just reissue
that only works for idempotent requests.
reissuing a request for a POST, let's say, isn't safe.
but also - you're flipping things around. in my case, I was saying that your application - the one you wanted to suspend - was the server, not the client.
oh, well that's even easier
we don't care about it anymore in your scenario
if the client is disconnected anyway, there's npo point bothering with the socket at all
but then you haven't restored the state
just make it a closed "dummy socket" 😁
the state before was "We're in the middle of responding to this client". The state after the restore is "oops, client gone"
the same case happens with HTTP requests on the client side, though. The state when you snapshot might be "we're in the middle of POSTing a file". The state after the restore would be "we have a brand new connection to this server". Not the same thing.
so... maybe I don't understand your goal at all, then. If you're OK with the idea that after "restoring", every externally held resource, from sockets to pipes to files to memory maps to locks and semaphores, might be in a different state than they were in when you snapshotted, then - what's the point of the snapshot?
when you restore the system, you're not restoring anything that was running
just the "static state" was what I mean, like what's available to you when python is waiting for a new input... that's what I was contemplating
like if you wanted to restore a jupyter notebook for example so yoi could add code to it without losing everything
there's no such thing as "static state", because the parts that you're considering static state - like which objects exist - refer to parts that are externally held dynamic resources. A python socket.socket object is "static state" in the sense that the interpreter owns that object completely, but that object owns a file descriptor, and the kernel holds a table mapping that file descriptor to an open TCP connection. If you restore half of that but not the other half, the program is referring to kernel resources it no longer owns, and is no longer in a stable, correct state
in some cases, sure ... then you leave that to lower level inplementation and restore objects at a higher level
there'd be some things that can't be serialized, I'm sure, but a lot can be... enough to consider it useful
when you say serialise... are you relying on pickle?
well, I'm quite convinced I'm right, but if you're convinced you're right, build a POC. If I'm right, I should be able to segfault that POC pretty easily.
not pickle as it exists today, but one that can do many more things
i still don't see how you're going to serialise extension classes whose implementation is completely opaque
I can segfault python easily already... what does that proove?
if I can segfault Python by using normal objects in ways that their contracts are supposed to allow after the restore, then it proves that your restore - didn't.
alright, I'm not one to give up on things just because you insist it can't be done
I expect, for instance, that it would be pretty trivial to get into a state where iterating over a Python bytes object crashes the interpreter. That would happen because the memory owned by the bytes object was part of an anonymous mapping, and if the mapping is freed by the garbage collector after the snapshot and before the restore, then the restore would result in a bytes object that refers to memory that has been freed, and iterating over it will segfault.
either that, or you pay a pretty serious tradeoff: not allowing any object to be garbage collected after the snapshot and before the restore.
is flask good to learn?
not really on topic for this channel, but, yes - it's a good microframework for making web services and web sites.
Aight soz, ty
well, i think it's very similar to how hot-reloading of a module is safe and nice as long as you only call functions and not reference state from it
if you restrict componentization to parts of the software that don't need access to external resources, i don't see a problem
you could even decouple message queuing from managing the sockets or files
so even if an attacker was killing the message queue by some exploit, it wouldn't require to drop all connections, just reload the queue from a safe state
looking forward to it!
is there a way to catch sys.exit()
using runpy to run a script i wrote from another script
Exit from Python. This is implemented by raising the SystemExit exception, so cleanup actions specified by finally clauses of try statements are honored, and it is possible to intercept the exit attempt at an outer level.
so except SystemExit:?
The atexit module defines functions to register and unregister cleanup functions. Functions thus registered are automatically executed upon normal interpreter termination. atexit runs these functions in the reverse order in which they were registered; if you register A, B, and C, at interpreter termination time they will be run in the order C, B, A.
Note: The functions registered via this module are not called when the program is killed by a signal not handled by Python, when a Python fatal internal error is detected, or when os._exit() is called.
Changed in version 3.7: When used with C-API subinterpreters, registered functions are local to the interpreter they were registered in.
interesting
atexit is more for libraries, while except SystemExit is something an application might do
though if the reason for catching it is to do some sort of cleanup, a try / finally or a context manager are better options.
ngl I don't have a reason for it at the moment, but I may have
tldr I'm making a script which wraps a script i wrote ;-;
well i don't even know what is real anymore
What you should do is change that script you want to wrap so it has a function that accepts all the options required and returns the result - the command line processing and sys.exit call should then be in a seperate function, or just in the if __name__ == '__main__' block.
Then you can call it directly.
Basically every script I write has if name == main main()
i'm using runpy and want it to be named __main__
tldr i'm writing a script to wrap my scripts so i have a launcher for my scripts ¯_(ツ)_/¯
And main is a function that just parses arguments and calls a real function
That way you can use the real function programmatically if needed
ye
i added my parse arguments under the if __name__ == ... thing
and then my main function does what the script does
so i can just provide different args to main() and not have to worry about argparse/click
I made this work, but now I want to rewrite it into argparse
I stopped doing the parse arguments and exception catching there because it's technically all global scope
Every variable you declare in that if name == main block is a global
And triggers warnings about shadowing quite often
😳
where do i get those warnings
!ban 645500518617317386 racism
:incoming_envelope: :ok_hand: applied ban to @stable lintel permanently.
@white nexus pycharm
ah
But yeah even if it is just part of a script, it should feel pretty gross that every local you create there is actually a global
And there's no downside to have a main function, it's two extra lines
@halcyon trail discord-modmail/modmail#111 this what I just made
Don't think I really understand the purpose
as of right now, its just a launcher for whatever scripts i put in it
using setuptools (the standard library) can we have a monorepo?
in the js ecosystem, projects like gatsby have multiple packages per repo:
https://github.com/gatsbyjs/gatsby
is it possible to do something similar in Python? I know we have namespace
packages, but that's not what I'm looking for!
Yes, see from the packager's point of view your projects folder that houses several projects with their own git repository is no different from a single project and repository.
The same way you need a package.json for each package, for Python using setuptools you need a setup.py for each package.
okay, then how can I upload all packages at once?
There's also one downside with this approach. For someone to install your repository directly using pip they also need to specify the specific setup.py to install
That on the other hand I don't know, it would be supported for whatever software you use to upload packages
how can i make in py sound human voice using speechrecognition module
this is the wrong channel for your question, unfortunately
so can you help me pls @deft pagoda
i already offered as much help as i can, i said i've never used speech recognition before
this is really convenient, I also do scripts this way
also if there are no command line arguments and name is __main__ i add an argument for "/dev/stdin" to sys.argv
that way it can act as a simple filter in a shell pipeline if it's a text/blob processing script
hey guys ! i'm wondering if there is an inverse dunder method for __contains__ 
You want a "not contains" that is not the output from __contains__ inverted?
na i want a "is contained in"
sorry for being unprecise, by "inverse" i was meaning something like __pos__ to __add__
no, that's not possible - unless both those types were defined by you and you make their dunder contains check the inverse case
...which is just really unexpected behaviour
ye, and in my usecase it'll absolutely never happen
i guess i'm gonna use another operator then
or a .isin method i guess
you mean like a __ncontains__ ?
not "doesn't contain" but "is contained in"
like if i do A in B it triggers A.something instead of B.__contains__
please give some example how you imagine it should work
well i just did
what's the expected result?
exact same as __contains__
it's just that i have a custom class A and B isn't custom
class InEverything:
def __rcontains__(self, container):
return True
assert InEverything() in [1, 2, 3]
is this the idea?
yes exactly
depending on the usecase, if you can make yourself compare equal to one of the elements, it should work, but it is not as powerful as a dunder would be
but to my knowledge, no such thing exists
the only way would be to overwrite __contains__ in builtins, with stuff such as forbiddenfruit.curse
but i think that's as worse as a practice it can be
couldn't you subclass list and add the method and use it like InEverything in L(1,2,3)?
I could do that too ye but that would become weird versus just making a isin method
the context is i'm making a data object package for an API
you could abandon methods and simply do it functional
that's probably the most straightforward solution
well the whole concept of it is to be object oriented
sigh
i like functionnal stuff dw, i just think it doesn't apply well to my usecase
you could hide the functional style by making a dummy 😉
like class Matcher: @staticmethod def does_contain(a, b)
🤣
i just found a problem with Enum
E = Enum("asdf", "1 2")
doesn't raise a WTFError as it should
but instead simply won't work in any way
>>> E._member_map_
{'1': <asdf.1: 1>, '2': <asdf.2: 2>}
simply no way to access the members
you can still produce them with the getitem and iter impl on an enum class.
In [10]: E['1'] in E, list(E)
Out[10]: (True, [<asdf.1: 1>, <asdf.2: 2>])```
I believe getattr should work too
another nice way to make coworkers furious
setattr also allows non-identifiers, that's funky
shouldn't that be banned or something?
it would be a major nuisance to properly sanitize
they could call str.isidentifier on the argument but that could break older code
but that's kind of a half measure considering you could still trivially create such attributes my manipulating dict
I can imagine that there's also a performance cost to checking it
at least in Enum it wouldn't - is there any legit usecase where you'd want a non-identifier as an enum member?
for me dotattributes are the one big reason to even use enums
is there a meaningful reason to forbid this though?
you can't really do it by accident
you could typo and accidentally have a number as first char
you could also typo and just have the wrong name as the enum member
i think it's worse for chars that are not as obvious as numbers
some unicode chars aren't allowed in identifiers but are also not really special
or something like ```python
§ = 23
File "<pyshell>", line 1
§ = 23
^
SyntaxError: invalid character '§' (U+00A7)
shrug
advanced unable to understand anything
you'll get used to it
i hope so!
so, i was just revisiting the buffet table in justuse again, looked at it through the patma lense
behold!
def buffet_table(case, kwargs):
case_func = {
(0, 0, 0, 0): lambda: ImportError(Message.cant_import(**kwargs)),
(0, 0, 0, 1): lambda: pimp._pebkac_no_version_no_hash(**kwargs),
(0, 0, 1, 0): lambda: pimp._import_public_no_install(**kwargs),
(0, 1, 0, 0): lambda: ImportError(Message.cant_import(name)),
(1, 0, 0, 0): lambda: ImportError(Message.cant_import(name)),
(0, 0, 1, 1): lambda: pimp._pebkac_no_version_no_hash(**kwargs),
(0, 1, 1, 0): lambda: pimp._import_public_no_install(**kwargs),
(1, 1, 0, 0): lambda: ImportError(Message.cant_import(name)),
(1, 0, 0, 1): lambda: pimp._pebkac_no_hash(**kwargs),
(1, 0, 1, 0): lambda: pimp._ensure_version(pimp._import_public_no_install(**kwargs), **kwargs),
(0, 1, 0, 1): lambda: pimp._pebkac_no_version(**kwargs),
(0, 1, 1, 1): lambda: pimp._pebkac_no_version(**kwargs),
(1, 0, 1, 1): lambda: pimp._pebkac_no_hash(**kwargs),
(1, 1, 0, 1): lambda: pimp._auto_install(**kwargs),
(1, 1, 1, 0): lambda: pimp._ensure_version(pimp._import_public_no_install(**kwargs), **kwargs),
(1, 1, 1, 1): lambda: pimp._auto_install(
func=lambda: pimp._ensure_version(pimp._import_public_no_install(**kwargs), **kwargs), **kwargs
),
}[case]
return result
this is the thing for python <3.10
this is the same thing - with pipes, since it's possible outside of lambdas:
def buffet_table(case, kwargs):
match case:
case _, _, 0, 0: return ImportError(Message.cant_import(**kwargs))
case 0, _, 1, 0: return _import_public_no_install(**kwargs)
case 1, _, 1, 0: return _import_public_no_install(**kwargs) >> _ensure_version(**kwargs)
case 0, 0, 0, 1: return _pebkac_no_version_no_hash(**kwargs)
case 0, 0, 1, 1: return _import_public_no_install(**kwargs) >>_auto_install(**kwargs)
case 1, 0, _, 1: return _pebkac_no_hash(**kwargs)
case 0, 1, _, 1: return _pebkac_no_version(**kwargs)
case 1, 1, 0, 1: return _auto_install(**kwargs)
case 1, 1, 1, 1: return _import_public_no_install(**kwargs) >> _ensure_version(**kwargs) >>_auto_install(**kwargs)
i never expected that patma and pipes could make this thing so much nicer
i've been doing this all over as well, it looks a lot cleaner
agree or disagree, any nested .get on a dictionary should include a default of an empty dict until the final get in the chain
❎ foo.get('bar').get('baz')
✔️ foo.get('bar', {}).get('baz')
cleaner? wtf is that?
if you're talking about @verbal escarp 's message, i think it's just swapping from a dict mapping inputs to functions to using the new match case feature
➡️ 👂 ➡️ 🧠 = 
well... if you're expecting some of the gets to return None, you kinda have no choice
it doesn't really make any sense to do nested get without a default {}
although... why do you have deeply nested dicts?
however it does make sense to do nested []
so really, that's the choice; nested [] or nested .get(..., dict())
i wouldnt call two or three chained gets terribly deeply nested, fairly common for response jsons to be that complex
but yeah i needed the sanity check, it feels like we shouldn't ever have a bare .get
in that case
What are some good books on:
The Theory of Programming (traditional)
The Theory of Programming (modern, 21st century)
How Programming Languages Work
How Python Works
Other
First on my list is the dragon book. Any others?
To be honest I think it should be a separate function anyway
are you... returning exceptions?
_MISSING = object()
def get_nested(top_dict, *keys, default=_MISSING):
d = top_dict
for k in keys:
if k in d:
d = d[k]
continue
if default is _MISSING:
raise KeyError(f"Error, nested key {k} not in nested dict!")
return default
nested_get(foo, 'bar', 'baz')
i hav a function like this in my codebase and I"d imagine many people do
yeah agreed, good call. thanks for sharing!
That's how lots of modern languages deal with errors. Just return them as a value. A type checker will make sure you always handle them.
Right, but in Python it's a very strange thing to do most of the time. Language with this style of error handling usually have tools for working with these error values.
For example, Haskell has do notation.
And Go... well, let's not talk about Go
rust
The only place I think that this pattern is reasonable in Python is moving exceptions from one thread to another.
Rust has:
OptionandResultbuilt-in- methods like
map,map_err,and_then,or,or_elseetc. onOptionandResult matchas an expression?-notation- tagged unions which are good for defining custom error types
I mean, we have dry-python stuff
but it looks a bit foreign tbh, and only works with mypy
you can get away with just pattern matching and unwrap
Forcing users to do that in a language that idiomatically uses exceptions is unwise
they can use it internally
I can't imagine how this would look with match
fn deserialize_seq<V>(self, visitor: V) -> Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
self.skip_spaces();
if self.next_char()? != '[' && self.next_char()? != ' ' {
return Err(CmdError::ExpectedLeftBracketBeforeList);
}
let value = visitor.visit_seq(ManyValues::new(self))?;
self.skip_spaces();
if self.next_char()? == ']' {
Ok(value)
} else {
Err(CmdError::ExpectedRightBracketAfterList)
}
}
indeed i am
Sure. Forcing un-idiomatic interfaces on people is bad. If someone keeps the un-idiomatic stuff to the guts of their library, that's their call, but putting it into their library's interface is hostile to users.
I guess you can implement this with generators, something like this: ```py
@result
def deserialize_seq(self, visitor):
self.skip_spaces()
if (yield self.next_char) != '[' and (yield self.next_char()) != ' ':
return Err(CmdError.ExpectedLeftBracketBeforeList())
value = yield visitor.visit_seq(ManyValues(self))
self.skip_spaces()
if (yield self.next_char()) == ']':
return Ok(value)
else:
return Err(CmdError.ExpectedRightBracketAfterList())
actually the generator version doesn't look bad
so if you're ok without typing it is a way
@grave jolt we use a function ```python
def _fail_or_default(exception: BaseException, default: Any):
if default is not Modes.fastfail:
return default # TODO: write test for default
else:
raise exception
to catch any exceptions during installation/import and return a default if the user specified one or raise the exception if no default was given
maybe:
match self.next_char():
case ']':
...
case char if char not in '[ ':
...
case _:
...
mainly to simplify optional imports
if you say so
so the user doesn't have to deal with the exceptions if they don't want to
this would work nicely with match
?
you can do something like graphing = use("some_optional_graph_lib", default=use("matplotlib")) or somesuch and it will first attempt to import the optional one and if that fails for any reason, fall back to one you know will work
if it's returning an Err or some non-Err, you would have all the pieces you'd need without wrapping in a try/except
just looking at the function, not knowing the way mypy's API works
I don't think I understand you. Can you perhaps show how the function would look like with match?
oh, I mean if one were to call your function, like
match deserialize_seq(...):
case Err(...): ...
case Ok(...): ...
calling is easy, I meant the body itself
def deserialize_seq(self, visitor):
self.skip_spaces()
match self.next_char():
case Err(e):
return Err(e)
case Ok('['):
match self.next_char():
case Err(e):
return Err(e)
case Ok(' '):
return return Err(CmdError.ExpectedLeftBracketBeforeList())
case _:
pass
case _:
return Err(CmdError.ExpectedLeftBracketBeforeList())
match visitor.visit_seq(ManyValues(self)):
case Err(e):
return Err(e)
case Ok(value):
self.skip_spaces()
match self.next_char():
case Ok(']'):
return Ok(value)
case Err(e):
return Err(e)
case _:
return Err(CmdError.ExpectedRightBracketAfterList())
beautiful innit
I guess you could simplify it to ```py
def deserialize_seq(self, visitor):
self.skip_spaces()
match (self.next_char(), self.next_char()):
case (Ok('['), Ok(' ')):
pass
case Ok(_):
return Err(CmdError.ExpectedLeftBracketBeforeList())
case Err(e):
return Err(e)
match visitor.visit_seq(ManyValues(self)):
case Err(e):
return Err(e)
case Ok(value):
self.skip_spaces()
match self.next_char():
case Ok(']'):
return Ok(value)
case Err(e):
return Err(e)
case _:
return Err(CmdError.ExpectedRightBracketAfterList())
all parsers should strive to immitate that
no it's not really sarcasm 😄
it's a lot more readable than most parser generators would give you
I think the yield version is much better
well... you're not supposed to read the generated code, are you?
it's supposed to be fast, not readable
maybe not, but i end up reading it anyways if i have to debug a parse error
true
is there a reason this would be slower than what's usually done
so uh, what happens if you don't have an __init__.py but there is a __main__.py
i have noticed that i can execute it as a module, with python -m
but not sure of anything it cannot do
in fact, i don't see a problem with not having an __init__.py
it seems like if i import it without an init py it ends up being a namespace
!pep 420
yeah, per pep 420 you dont necessarily need init.py but there are some differences worth noting. Portions of namespace packages need not all come from the same directory structure, or even from the same loader. Regular packages are self-contained: all parts live in the same directory hierarchy. is the most notable i'd think.
not really sure how namespace packages handle __main__.py e.g. if you had multiple. probably just use the first one found?
i kind of wish they would have picked a standard "marker" along the lines of import pkg_resources ; pkg_resources.declare_namespace instead of making them entirely implicit
but i guess they had their reasons, maybe related to the concerns about linux distros:
However, Linux distribution vendors (amongst others) prefer to combine the separate portions and install them all into the same file system directory.
the spec in the pep suggests that it's whatever comes up first in the search
If not, but
<directory>/foo.{py,pyc,so,pyd}is found, a module is imported and returned. The exact list of extension varies by platform and whether the-Oflag is specified. The list here is representative.
i assume __main__ qualifies as one such foo
i never really considered that you could have __main__.so
this could be "implicit namespace packages" -- I'm not sure if every tool understands them vs. just lookimg for an __init__.py
I have seen python looks for those, if you do strace -e trace=file python ....
serious question: does the help method in the repl have a python implementation?
!d help
help([object])```
Invoke the built-in help system. (This function is intended for interactive use.) If no argument is given, the interactive help system starts on the interpreter console. If the argument is a string, then the string is looked up as the name of a module, function, class, method, keyword, or documentation topic, and a help page is printed on the console. If the argument is any other kind of object, a help page on the object is generated.
Note that if a slash(/) appears in the parameter list of a function when invoking [`help()`](https://docs.python.org/3/library/functions.html#help "help"), it means that the parameters prior to the slash are positional-only. For more info, see [the FAQ entry on positional-only parameters](https://docs.python.org/3/faq/programming.html#faq-positional-only-arguments).
This function is added to the built-in namespace by the [`site`](https://docs.python.org/3/library/site.html#module-site "site: Module responsible for site-specific configuration.") module.
i searched this on the website and it didn't come up...
bro wtf
pydoc 
i am now going to explore the wide world of pydoc
help(help) works in repl 😄
huh
iirc
@paper echo it was you who said, that dunder variables defined that aren't defined by a pep could be overwritten
Lib/pydoc.py lines 277 to 280
if name in {'__author__', '__builtins__', '__cached__', '__credits__',
'__date__', '__doc__', '__file__', '__spec__',
'__loader__', '__module__', '__name__', '__package__',
'__path__', '__qualname__', '__slots__', '__version__'}:```
probably not a great idea tho, unless the documentation says that you can
__version__ is referred to in the stdlib internally
dunders are to be used (and defined) by the implementation and the stdlib, doesn't really have to be defined by a pep
def __init__(self, dist):
"""Create and initialize a new Command object. Most importantly,
invokes the 'initialize_options()' method, which is the real
initializer and depends on the actual command being
instantiated.
"""
# late import because of mutual dependence between these classes
from distutils.dist import Distribution
if not isinstance(dist, Distribution):
raise TypeError("dist must be a Distribution instance")
if self.__class__ is Command:
raise RuntimeError("Command is an abstract class")
self.distribution = dist
self.initialize_options()
self._dry_run = None
self.verbose = dist.verbose
self.force = None
self.help = 0
self.finalized = 0
def __getattr__(self, attr):
if attr == 'dry_run':
myval = getattr(self, "_" + attr)
if myval is None:
return getattr(self.distribution, attr)
else:
return myval
else:
raise AttributeError(attr)
how would i access self.distribution? i'm assuming __getattr__ will block me?
If it's used (even though not officially) it makes sense for a documentation module to try and show it
true..
ah getattr is a last resort, thank god
That's how things worked before PEP 420 - you needed to have an __init__.py, and the __init__.py needed to call pkgutil.extend_path(). The problem with that was that you needed to make a package for every namespace. If you wanted to make two packages called company.team.lib1 and company.team.lib2, you would need to make a company package containing a company/__init__.py file that called pkgutil.extend_path, and a company.team package that provided a company/team/__init__.py file that called pkgutil.extend_path, and company.team would need to depend on company, and company.team.lib1 and company.team.lib2 would need to depend on company.team. It worked, but it was really hard to explain to people and maintain. And it was easy to get wrong: if you instead made both company.team.lib1 and company.team.lib2 install the company/__init__.py and company/team/__init__.py, then installing both and then uninstalling one would leave things in the wrong state, for instance.
looking in the source of pydoc
and this is kinda cursed
welp can't say i hate it lmao
@white nexus why are you looking at the pydoc source?
just made a history method in my repl that will use less to show my history
well, help() uses the terminal pager, which is implemented from pydoc.help. So that means that in pydoc, there's a way to use the pager, and has already been written and I don't need to write it again.
That means that with readline, I can set up a history() method in my repl to use less to show my history. It works, too
i see, makes sense
right, that's explained in the pep. but i guess instead of a "plain" __init__.py file, i have in mind something more like py.typed - it's empty and its contents are ignored/irrelevant, but its presence marks a namespace package. and if there is an __init__.py in the same directory, it has no effect. so that would be safe to combine with other directories + non-namespace packages
obviously this is all said and done a long time ago
but i think there is enough confusion with beginners around accidentally using namespace packages that in hindsight maybe a "marker file" like what i described might have been easier to learn and explain
so that would be safe to combine with other directories + non-namespace packages
it wouldn't - you'd still have exactly the same problem about which package provides that file.
they all would
I don't anticipate it to break anytime soon, but if it does, this file is just for me so ¯_(ツ)_/¯
actually, I got a lot of inspiration for my scripts from coverage and you, nedbat
the reason i don't like totally implicit namespace packages is that they further blur the distinction between modules and filesystem directories, which is confusing even for programmers experienced in other languages
then you have the same problem as I said - uninstall would be broken. And Linux distro maintainers would need to do a whole lot of work to try to make that work, because systems like Debian enforce that any given file only be provided by a single package on a particular system - you can't install two packages at once if they both contain the same file.
wow, nice 🙂
!pypi coverage
hmm... isn't that currently an issue if you have one distribution that defines a namespace package, and another that defines a non-namespace package?
which i guess is slightly more pathological, but not impossible
with the same name? Yes.
but that case is already broken.
everything needs to agree on whether a package is a namespace or not.
https://github.com/nedbat/coveragepy/blob/master/__main__.py this file inspired me to make a launcher for my scripts, so I can use python -m scripts <memorable script alias that doesn't match the file name>
right, so if you have 10 different marker files from 10 different distro packages, who cares if they all overwrite each other? uninstalling i guess is a matter of only removing the marker file if it's the only file left in the hierarchy, which sure is more annoying
but if you're already in the business of merging file trees from different packages, surely python isn't the only place where an issue like that might arise
You could design a package manager like that - but I'm not aware of any that exists. That's definitely not how dpkg works, which is the one I'm most familiar with.
dpkg rejects installing two packages if they would both install the same file, rather than try to define semantics for it. I mean, sure, it could do something like say "well, if they're both empty", or "well, if they both have the same exact contents", then it's OK - but if it did that, they'd also need to define semantics for upgrades - what happens if a newer version of one of the packages gets a new version of that file, and it's no longer identical? Etc.
id, I feel like 99% of my command line runs are probably by doing Ctr-R to find the last invocation, and editing what needs to be edited
so a shorter script name doesn't really matter much to me, personally. One thing that would actually be much more helpful IMHO would be shell auto-completion for python -m, which I'm sure somebody has implemented, somewhere
I've just never bothered to look for or setup
https://pypi.org/project/genzshcomp/ this is probably also fun, probably just can't be bothered because you'd need to do it for each script
that's fair enough
i love this idea. would be great to have it for click or clikit as well as argparse/optparse
i'm pretty sure click has completion...
very very useful, ty
The click stuff is afaik for arguments to the script
I'm taking about completing the module path itself
has anyone else here worked with build backends?
I just had a ride of a time trying to add support PEP 660 to setuptools and I'm not even done yet, only most of it.
(fyi if this would be better in an OT channel or #tools-and-devops lemme know)
i'm trying to work with setuptools now if that's relevant (it's not going well)
honestly same thing here lol
it's even worse working within setuptools.build_meta because there isn't that much information available to you to start with, you have to get it yourself by calling setup.py commands ...
a little bit...
well this is the madness I'm head deep in right now :)
I both regret and also don't regret my decision to use pep 660 as a way to jump into the setuptools codebase for the first time
heh
I have no idea what I'm doing :P
pretty fun though learning how pep 660 works ins and outs by trying to implement it
I've done some build backend stuff, but haven't messed with pep 660 at all yet.
it's interesting how they piggy backed off the wheel standard as the "communication method"
well, not that surprising - it's a whole lot easier than making a brand new standard that includes some of what wheel does, when all the build backends already know about wheels
yeah it's not surprising as it's definitely the "lower development effort" option
interesting.. https://github.com/pyodide/pyodide
Pyodide may be used in any context where you want to run Python inside a web browser.
This is a discussion channel, not a help channel. See #❓|how-to-get-help
that looks neat
hmm this takes a bit of time to start now
hmmm
from the repl, is there a way that a repr method can be detected how its called
what does that even mean
i'm guessing it means "can my object be made aware of the context its __repr__ is called?"
i'd say it's the same answer how to make any function aware of its caller
use inspect and go up the stack
curious what the relationship is between making a zip archove and an editable install?
what's the goal? if it's to prevent infinite recursion, there's probably a simpler way
another option is for the caller of the repr method to provide whatever infornation __repr__ itself needs instead of the other way around
Not that I’m aware of, I would recommend inspect.stack()[-1].code_context but that only works with code in files
my guess this is for some kind of depth-limiting
Or you implement special hooks like ipython does aka _repr_html_, _repr_png_ etc.
Using a "worker" function to process an async queue in a fully async program. Is there any reason to run this "worker" from a thread instead of a just an endless task, as in:
while True:
await asyncio.sleep(...)
work...
Both can be cancelled, watched, etc, so I wonder if there clear-cut reason to prefer one over the other.
#async-and-concurrency but in general, async is easier to debug than threads
what does this error mean
i've gotten it a bunch
cyclegen is like itertools.cycle except with better syntax for what i'm doing so i made it a class
nvm just my linter being stupid
it runs
that's how you're supposed to annotate functions anyway right
something: Callable[[<arg1_type>, <arg2_type>, ...], [<returntype>]] = ...
You've got an extra pair of braces there. There shouldn't be braces around the return type
You can't annotate attribute assignments. Only simple single-name assignments
Also we have #type-hinting
The type checker can't really do anything with these dynamic assignments anyway.
What is TIME?
class TimeState:
def __init__(self, name: str, repr: str, tideLevel: TideLevel) -> None:
self.name = name
self.repr = repr
self.tideLevel = tideLevel
__str__ = __repr__ = lambda s, _='': f'{s.name} time ({s.repr})'
TIME: Collection[TimeState] = Collection(
Afternoon = TimeState('Afternoon', '3:00 PM', 3),
Evening = TimeState('Evening', '6:00 PM', 4),
Sunset = TimeState('Sunset', '9:00 PM', 3),
Midnight = TimeState('Midnight', '12:00 AM', 2),
Night = TimeState('Night', '3:00 AM', 1),
Sunrise = TimeState('Sunrise', '6:00 AM', 0),
Morning = TimeState('Morning', '9:00 AM', 1),
Noon = TimeState('Noon', '12:00 PM', 2),
)
collection is just a dict/mapping made with kwargs
and uses getattr instead of getitem
basically an enum in this case
with a set amount of values
Right, type checkers have no idea which attributes are present.
You can use enum.Enum
Or something
...so when you do TIME.foo: int = 42, the type checker can't get any new information. It's somewhat of a bummer, but type checkers are already very complex.
got it
what is the difference between a fully connected NN and a CNN?
That's off topic for this channel, but #data-science-and-ml should be able to help answer
@white nexus using windows is an error
was asking for a real answer smh
windows specific modules like winreg could've used it in the past
Changed in version 3.3: Several functions in this module used to raise a WindowsError, which is now an alias of OSError.
hmm I found this ```c
/* Compatibility typedefs */
typedef PyOSErrorObject PyEnvironmentErrorObject;
#ifdef MS_WINDOWS
typedef PyOSErrorObject PyWindowsErrorObject;
#endif
although i don't disagree
just opened python on windows....
>>> WindowsError is OSError
True
Things that interface with Windows libraries like sockets or DLLs can still raise WindowsError IIRC
although it'll be OSError so ¯_(ツ)_/¯
they are the same thing, although that isn't documented too great
yeah, hence why they kept the name there for compatibility
ye
lua is better
@magic hawk what is the point of this?
honestly, idk
anyway cya
does this mean i can get a free trial and get them to solve my setuptools issue for me?
speaking of this, does anyone understand what the numbers in the right column of the dis.dis mean? i read these answers https://stackoverflow.com/questions/12673074/how-should-i-understand-the-output-of-dis-dis/12673195 and i still dont really understand
oh i see, thank you that makes sense
is there a way to stream notifications from a website using selenium ?
what i have right now prints the whole list over and over, i need to find a way that shows the message only if its from like the last few seconds
you cant advertise here and your page is unreachable
!warn 433548397337640960 We do not allow recruitment here, as is clearly stated in our #rules. Please don't do so in future.
:incoming_envelope: :ok_hand: applied warning to @tacit wing.
No worries. Won't happen again!
Yeah my hosting provider is having issues with their Sydney location
Can i return a tuple from __iter__? i'm trying to think of a reason why that might not work
nvm i can't call next() on it
there go my hopes for nice type hinting :/
Iter should return an iterator, not a sequence. Why do you want to make it return a tuple
check #type-hinting
Iterator[tuple[...]] might work
Actually, now that I think about it, it's really weird
wouldnt Iterator[tuple[...]] mean each element is a tuple

what even is the annotation for tuple's iter?
A union of all possible args?
Otherwise probably special-cased
that's the only one i can come up with, but that wouldn't give you the actual types on unpacking
since union doesn't preserve order
tupleiterator[...] when
what about just making a separate method that returned a tuple
In [7]: type(iter(()))()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-7-925ed1e942ec> in <module>
----> 1 type(iter(()))()
TypeError: cannot create 'tuple_iterator' instances

wonder if you can actually typehint with this lol
but i wanna splat 😦
ok, exp = CmpxchgResult(True, 5).method()
😒

this isn't that important, i'm just sad there's no solution
import atomics
def atomic_mul(a: atomics.INTEGRAL, operand: int):
res = atomics.CmpxchgResult(success=False, expected=a.load())
while not res:
desired = res.expected * operand
res = a.cmpxchg_weak(expected=res.expected, desired=desired)
like this is how i'd use it
Could anyone help me wrap my head around what the ask is, and then what's the issue with the ask?
With the caveat that I don't really understand type hints much, having a hard time understanding what the problem itself is.
If i do x, y = (True, 2), type checkers can deduce that x is bool and y is int
to get that behaviour with a custom type, you need __iter__
which can't really be type hinted like that, since it just returns an iterator (seems to be the verdict)
Makes sense. How do type checkers deduce this part then. Do they still deduce this if it's defined elsewhere and unpacked after?
i'm guessing they have a special rule for tuple :/
Yeah, they must have been doing a static analysis on tuples, and even more specifically, just tuple literals.
t = tuple((3, "hi"))
return t
if __name__ == "__main__":
a = atomics.atomic(4, atomics.INT)
a.store(5)
x, y = atomic_mul(a, 10)
even from that it deduces it
(i chopped off the function start)
Impressive, though that's still ultimately a literal that's visible
I half suspect even this won't work if you read the tuple from elsewhere where the original contents aren't visible in code. Just a hunch, don't really use type hinting much.
I guess bottom line: this is a capability of the type checkers. Even tuples never decorated their internal iter to expose the types of contents, that's just what you thought is the approach, but it's not. Ultimately I suppose it's the type checkers that are failing to connect the dots for your custom object
hmm... you make a good point
which is why i hope type checkers have something special for this
and that people in #type-hinting find it for me, because i couldn't 
Yeah, it's also quite possible such capability doesn't exist yet, in which case this might be a feature request. I just don't know whether it's feasible to generalize or not
To me, inferring types during iteration and unpacking of a custom object sounds like a decently tricky task statically*
i am hoping that with variadic generics we can make tuples less of a special case, for exactly this kind of use
is that something in the pipeline for future python versions?
found it. https://www.python.org/dev/peps/pep-0646/ looks like a proposal atm
Assuming you provide a typehint elsewhere (function argument, typing.cast, etc), it should still typecheck properly, IIRC tuples really are special-cased, which is a little sad tbh
This would be a very welcome change
never got my head around the definition of "variadic"
In essence, "with a variable amount of parameters"
A functions is variadic if it can be called with an arbitrary amount of arguments, like print, for example
so meta-parameters?
not meta, but "any number of them"
That would be considered overloads ig
got it
with the example of print, it takes any number of strings as positional parameters, how would you describe that?
print() is a variadic function
thats a variadic
Receiving *args (or params <type> args in, say, C#) would be variadic
how do you describe it as a type?
I think the current way of doing this is Callable[[object, ...], None]
also, variadic only applies to positional arguments, not to kwargs?
in principle **kwargs could be variadic. in practice people usually have in mind positional arguments
but kwargs.. ah. hu? okay..
That's a tricky one, tbh, as I can't really come up with an example of **kwargs outside of python and maybe some equally dynamic languages
why not? it's an arbitrary number of arguments
to consider this, might be worth even simplifying it further. if you have a single keyword argument, ultimately what are you typehinting
still the data that needs to be passed, right
so it makes sense that being positional or keyword shouldnt be a concern for the type of the thing passed
i would probably use **kwargs in that case anyway and then type it as Dict[str, str] or somesuch.. but you're right
Hey, guys!
I'm trying to create a basic local mail system without any external tools but Python, and would like someone to try my tool.
And to discuss, refine, etc.
It's for a private network of buddies.
Assuming no access to internet or experts.
It's functional in its current state, but there's certainly more that could be done.
local mail system? o.O
Yup.
So this is my thought process:
- I'm not a lv. 10 programmer. More like lv 0.5. Same for networking. But I could get cable and wifi repeaters from walmart and set up a local network with a share drive.
With me so far? Basic stuff like that I can do.
But how could we communicate?
So what if we create folders on a share drive for each person, and those are our inboxes.
But then users can accidentally delete their only copies of stuff and the chance they'll mess with others' things are too high.
We're not necessarily hardening against insider threats, because that's tough, but we want to limit user input to healthy stuff.
okay, just.. why?
So with Python (and with Powershell, as a separate version currently being ported to Python), we have the following options: compose a message (creates, writes, and drops file in recipient's inbox),
read your message,
download your messages to your local machine,
move your messages between folders (inbox, archive, and trash), clear trash,
create public pages (anyone can view these), and download public pages on the network.
The why is weird, but I build a lot of networks from scratch on a regular basis and we don't have access to the internet when we make them. I do this for work.
Also, I've realized something. I don't have good over-the-internet networking skills, or even bad ones. But if we build out a good tool for messaging and adding locally-accessible content for everyone to see assuming access to a shared folder, distribution of the network can be done over the internet with any cloud syncing application. And if we were to stop trusting a company like mega, we could just move the files over to G Drive, or Dropbox, without losing any data.
And right now it's standard features only for Python.
Here's an example console output during usage:
Enter username: don
Account for don created/found.
Press enter to continue.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
INBOX:
['20211111-0047-sta-t.txt', '20211110-1923-don-hey.txt']
ARCHIVE:
['20211110-2233-sta-fwd.txt', '20211110-1743-sta-which.txt']
TRASH:
[]
PUBLIC PAGES:
['test-don.txt']
OPTIONS:
[1] SEND A MESSAGE
[2] READ MESSAGE FROM INBOX
[3] DOWNLOAD INBOX, FOR WINDOWS
[4] MOVE MESSAGES FROM TRASH TO INBOX
[5] MOVE MESSAGES FROM ARCHIVE TO INBOX
[6] READ MESSAGE FROM TRASH
[7] MOVE MESSAGES FROM INBOX TO TRASH
[8] MOVE MESSAGES FROM ARCHIVE TO TRASH
[9] EMPTY TRASH
[10] READ MESSAGE FROM ARCHIVE
[11] MOVE MESSAGES FROM INBOX TO ARCHIVE
[12] MOVE MESSAGES FROM TRASH TO ARCHIVE
[13] MAKE/EDIT PUBLIC PAGE
[14] READ PUBLIC PAGES
[15] DOWNLOAD PUBLIC PAGES, FOR WINDOWS
[16] SHOW DOWNLOADED FILES, FOR WINDOWS
[16] LOG OUT
READY, don. Option:
The worst things about this right now are that I have to type in the file name to specify what message I want to manipulate. I could use tkinter on Windows, but it breaks compatibility with Android. But like I said, I might just not be doing a good job using that feature. Also, it shows the location of the source files (the originals), which exposes other users to accidental deletions.
compatibility with android?
Yeah, it's an additional goal. We can get on the network with a synced cloud drive from mobile. I didn't create an APK, I just downloaded Pydroid 3 and run the script using that.
With the script located in the cloud synced folder.
I know this is very weird, and very specific, but the machines that form the bulk of the userbase are locked down and can't access most internet site, cannot run executables (we have no admin rights).
maybe you should move this discussion else where? this is very offtopic for this channel
sites*
Sorry, I'm new here. Don't know what topics constitute advanced discussion. If you don't mind, Amogorkon, I'd like to message you.
check the channel topic
Discussion on the use cases, implementation and future of the Python programming language including PEPs, advanced language concepts, new releases, the standard library, and the overall design of the language.
Hey, does anyone know a fun Python website with exercises?
perhaps ask that in #python-discussion
It depends right? Maybe i ment advanced but fun exercises ^^. But alright, thank you.
this channel is for
Discussion on the use cases, implementation and future of the Python programming language including PEPs, advanced language concepts, new releases, the standard library, and the overall design of the language.
trying to come up with a good name that captures what this channel does is the single biggest pain point we have had, but yes, it's not just about "any" advanced topic, but specifically discussion about python language itself.
#internals-and-implementation
that might be worth suggesting to #community-meta
#python-internals would be maybe good
also moving it to the topic section? idk
a few different names have been tried
Yeah, advanced would be writing in C right, Jk.
unironically, a lot of python is written in C, so in a way you'd be right 😛
youve posted in #data-science-and-ml , this channel isnt the right channel for this. please see room topic.
So sorry !!
#python-internals sounds like core-dev-only
btw, i was just wondering, is there a good way to pre-process python code before parsing it? i could then switch generics from 3.10 to List, based on the version of the interpreter
You're probably looking for the ast module, parse to that, implement a tree visitor and you can go through the whole syntax.
that won't help if the code is syntactically incorrect for that version
True, though the generics aren't new syntax.
patma is
you can use a custom encoding IIRC
how would that work?
https://aroberge.github.io/ideas/docs/html/ - this shows a bunch of ways to do funky tricks with Python's syntax, including the coding comment approach.
you would have a launcher which registers the encoding and then if you do
# coding: your_coding
```you will get the entire file as a stream of bytes which you can arbitrarily transform
Ooh, I love this package. Gave me inspiration to try out import hooks
hello, i was just going through PEP 593 (https://www.python.org/dev/peps/pep-0593/) which is on the Annotated typing. could someone clarify for me what's meant by "metadata" in this PEP?
This PEP introduces a mechanism to extend the type annotations from PEP 484 with arbitrary metadata.
information about a variable other than its type
oh, i see, thanks
I have it bookmarked for whenever someone says "I want to make Python do $crazy_thing"
Speaking of, a talk I really liked on this was https://youtu.be/xLc5xPYGGnQ
The examples were some pretty interesting stuff, like something called rwatch that will “watch” your variables or values and apply a callable to it with some black magic
So you've heard that the Python interpreter you use is called "CPython." It's written in C! But who cares? Why should you even bother to learn more? Could this ever be helpful? Could it help you debug your code or answer questions about how the language works? Thirty minutes is an awful lot of time to spend saying "no, just forget about it"...
Which in reality the black magic is just messing with frames
ooh, that sounds like a good talk.
Yeah, it’s a really good watch tbh
Does PyPI just ignore requests to remove malware?
it definitely seems like so
I think I've asked this here before but, well, you know
yeah pypi-support just keeps on growing and growing
quite disappointing but I guess I understand their perspective (as a maintainer of psf/black) even if the security risk element makes it hard to accept
Holy! 700MB package?! https://github.com/pypa/pypi-support/issues/1458
Project URL https://pypi.org/project/addrmatcher/ Does this project already exist? Yes Which limit do you want increased? total project size New limit 700MB Which indexes PyPI, TestPyPI Reasons for...
What size limit does PyPA have on PyPI so far?
warehouse/forklift/legacy.py lines 70 to 72
MAX_FILESIZE = 100 * ONE_MB
MAX_SIGSIZE = 8 * 1024
MAX_PROJECT_SIZE = 10 * ONE_GB```
Addrmatcher is an open-source Python software for matching input string addresses to the most similar street addresses and the geo coordinates inputs to the nearest street addresses. The result provides not only the matched addresses, but also the respective country’s different levels of regions for instance - in Australia, government administrative regions, statistical areas and suburb in which the address belongs to. <- that sounds like it could be even bigger
eh.. wrong msg replied to
I was confused for a sec there 😄
sorry
couldn't pypa have a seperate data hosting service and python integrate it on import?
doesn't sound very economical to include the data with the code
iirc nltk had a rather elegant solution
last time i checked, they fired up a tkinter gui on first import or somesuch to download all the texts, depending on user need
@raven ridge that page you linked got me thinking. could an import hook also make variable/attribute docstrings possible?
the problem with docstrings for variables is that there's nowhere to put them. You can't store them as an attribute of the variable itself, because given something like x = 1, there's no way to set an attribute on 1 - and even if there was, that same 1 is shared for every variable whose value is 1, and they might all want different docstrings.
you could store it on the module, instead, somehow, perhaps - the closest thing to that that you're likely to be able to build as a module level dict mapping name to docs
what about __attribute_docs__ as {qual_name: docstr}
sure. It limits it to only things that have a qualname, and you'll need to know what module a name came from (since that's not tracked automatically anywhere) - but, yeah, that's doable.
the things that don't have a qualname are hard to address either way, i suppose
just thinking of vars in closures
VSCode supports this for variables and attributes, but yeah, you can't inspect this at runtime
TIL
Sphinx supports the same, which allows getting it into generated docs
TIL
I don't believe pydoc supports it, though, and help() certainly doesn't.
yeah, help inspects an object, not a symbol
Sphinx has 2 ways of doing it, actually - either a docstring after the assignment, or a special doc comment after it - a comment starting with #:
couldn't help() refer to sphinx-generated docs? ^^
help receives an object, not a symbol
For module data members and class attributes, documentation can either be put into a comment with special formatting (using a #: to start the comment instead of just #), or in a docstring after the definition. Comments need to be either on a line of their own before the definition, or immediately after the assignment on the same line. The latter form is restricted to one line only.
like, it can receive 42 and what can it do with it?
sorry for taking so long to respond, the answer is that the editable standard uses the wheel format as the communication method between the frontend and backend so they don't have to invent a new way to pass files and data to each other
the backend is responsible for setting up a wheel with all of the files necessary for the editable install (except for console scripts) in particular with a .pth file (which powers the whole editable install mechanism) and the calling frontend receives this wheel, unpacks it, and installs it
this wheel is special tho as it's not meant to be distributed, in most cases it's literally just the ${name}.dist-info metadata + the .pth file(s)
But that's not what we're talking about. We were talking about stuff like ```py
FROBNICATION_FACTOR = 42
"Factor to adjust the frobnication metafoobarization, in percents"
if somehow help could know the alias you're calling it with
You're agreeing with fix and salt about why what amogorkon wants isn't possible, @unkempt rock
we could have some kind of Symbol object (like inspect.Symbol("re.MULTILINE")) that would act like a pointer to something?..
actually i'm fairly happy with vscode and sphinx supporting those attribute docs, don't care that much about help()
but mostly for functions there, which is covered
i use ? a lot
?? is also convenient to get the source quickly
ngl that would be cool, you should write a pep for this
or a string object, and then matches that up internally
!e import pydoc ; pydoc.help('42')
@white nexus :white_check_mark: Your eval job has completed with return code 0.
001 | No Python documentation found for '42'.
002 | Use help() to get the interactive help utility.
003 | Use help(str) for help on the str class.
@white nexus :white_check_mark: Your eval job has completed with return code 0.
001 | Help on class str in module builtins:
002 |
003 | class str(object)
004 | | str(object='') -> str
005 | | str(bytes_or_buffer[, encoding[, errors]]) -> str
006 | |
007 | | Create a new string object from the given object. If encoding or
008 | | errors is specified, then the object must expose a data buffer
009 | | that will be decoded using the given encoding and error handler.
010 | | Otherwise, returns the result of object.__str__() (if defined)
011 | | or repr(object).
... (truncated - too many lines)
Full output: too long to upload
eg, it'll take a str and match it up in the globals, or import it, and do it that way
but it'd be nice to have a way that defines it to be able to inspect it for a variable, even if its not stored with said variable
attrs does something like this with its classes
every class it makes has one attribute that it uses internally __attrs_attrs__
iirc that stores a class or dict of the fields on the object, with their metadata of their field object, and annotations
the way to access from it, is to use attrs methods
iirc its something like attr.fields(instance_of_attrs_class)
thoughts on doing this?
import sys
class SomeClass:
if sys.version_info[:2] < (3, 9):
def method(self):
raise Exception("This method is only supported on python versions 3.9 and up.")
else:
def method(self):
# original functionality
also what exception type would be appropriate?
why not the more obvious
import sys
class SomeClass:
def method(self):
if sys.version_info[:2] < (3, 9):
raise Exception(...)
# original functionality
NotImplementedError
you need to check sys.version_info, not sys.version, for the way you're using it, also.
yep, fixed the version_info already
idk, it just occurred to me as a different way of doing what you showed in your snippet.
it doesnt seem to have any obvious downsides
but maybe im missing something?
your way has the pro of being slightly less overhead per call, but the con of being more difficult for others to read, and causing the entire body of the function to need to be indented further, and the signature needing to be duplicated
cant say i really agree with being more difficult to read but the other stuff is fair enough
most people are very surprised to learn that an if can go into a class body the first time they see it.
the first time users see your pattern, it will surprise them.
If you were OK with the exception being an AttributeError, you could also just do:
import sys
class SomeClass:
pass
if sys.version_info[:2] >= (3, 9):
def method(self):
# original functionality
SomeClass.method = method
i think this wont provide the correct context for method, e.g. itll be missing the class cell and super calls wont work as a result
that's true, if you did this you'd need to use the two-argument super() call, not the zero-argument super() call.
guess the only real place it could make more sense is if the function definition itself was version dependent, like if you had something in the signature that would need a newer stdlib version
(i mean the general idea of using an if to a seperate version-gated definition of the same function, not specifically the AttributeError solution)
well, in that case, you probably couldn't use this technique, because both the true and false branches of the if need to be parsed and compiled
instead, you'd put the code that requires a new version into a separate module, and conditionally import it only if you're on the new Python version. Or you'd put it in a string literal and use exec(), perhaps.
thinking of something like this:
def some_function(arg=some_stdlib_function_or_property_that_doesnt_exist_in_all_versions()):
...
hm, sure, for a default value, I suppose.
I was thinking more like * or / for keyword-only or positional-only args.
or annotations using types that arent in all versions
for annotations I'd just from __future__ import annotations
or that, yeah 🙂
My favorite thing to abuse 👀
looks like a job for a decorator
decorator cant handle the case of something version dependent in the signature
its broken because of legacy imports
since 3.2
wait
why did i get a deprecation warning
ah, something i had was not using the latest version of bottle lmao
They all work
Most of the big well known libs support 3.10 now atleast all your big data science libs e.g. Scipy, pandas, numpy, etc...
The error libs run into is distutils.utils not being a thing anymore
oooh
well thank god i don't use distutils 🙂 setuptools ftw
although i do run into issues with only recent versions of pip knowing about pyproject.toml and thus failing to install from source
not sure how to deal with that
seems like there's been a skull and crossbones around distutils for a while 😄
Hey!
it just occurred to me that if python followed semver, we'd be on version 5 or 6 by now ^^"
with all those backwards-incompatible syntax changes over the last 10 "minor" versions
it's not backwards incompatibility though. it's about breaking things that were valid before
hm.. okay, got a point
Yeah python tries very hard to stay backwards compatible. Ie it won't break existing code when it adds new things
I wouldnt say very hard. There is an effort made, but C, C++, PHP etc only break far less code than python does
JavaScript is probably the best
Because it can literally not have brewing changes in any shape or form
Ñ
well, if/when we realize subprocess-interpreters in justuse, maybe it'll become possible to run python2 code with a python3 main program
then python has to worry about backwards compatibility even less
Where did people put build dependencies before pyproject.toml?