#internals-and-peps
1 messages ยท Page 39 of 1
But it's objectively a less clear option than others
x = (y if condition) # -> x = None
(x = y) if condition # -> Nothing happens
no
it's ambiguous which of these is actually happening without parens
man yeah the logic needed to make up for it starts becoming annoying
See to me, I'd expect it to error out instead
statement if condition or expr if condition else
Because if it's not that condition, and it has no other option, it panics
you can determine which is which with simple look ahead
which, again, is adding unnecessary mental overhead to something we already have a one-line solution for
if condition: x = y
yes, but it would be consistent with generator expressions, which is why i want it
I just don't like it because it means I'd have to switch off how certain parts of my brain think logic should be
it would have to be like x = y if condition (else x = x or None)
it doesn't feel natural to me either
i'd also like it for lambdas
implied
(x + y for x, y)
There's a lot of potential what ifs in those
More implicit is more than implicit.
although I am of the opinion that lambdas should be banned
since I learned the single line x if condition else y syntax (which actually wasnt that long ago) I have tried to reduce unessary multiple-line statements and found that overall it makes the code less recognizable. Maybe thats just because I have only just started doing it
if you actually do omit else though I believe it would have to imply else x = x or none
so it actually gets defined ?
ye, python does not do long expressions well
but doesn't change the value if it is already defined
unlike kotlin , raku, and pure functional languages
so health = 100, health -= 10 if player.ishit would still work
for good reasons
I feel like that's a bad example
because realistically you'd have a player.hit()
you wouldn't be checking if they were hit
they either would or wouldn't be
Something else that checks for that would call the hit
that's really not the point
I suppose that's true
that's entirely up to what you choose for an architecture
@unkempt rock that is actually called ternary abuse, and you can be arrested for it
lol
haha
I wrote a short script for the context of omitting else on if expressions (and shortcircuiting to None), and looks like only in stdlib there are many examples. Will share full number
I have at least learned how its problematic since learning it
total of 72 occurences
I'd be OK with it if it did return None on not condition
that makes a lot more sense
Same
72 occurrences of it not liking the None or...
def currentframe():
"""Return the frame of the caller or None if this is not possible."""
return sys._getframe(1) if hasattr(sys, "_getframe") else None
72 x if y else None
Ah I getcha
Here's the weird thing, I don't mind that for returns
But for some reason with assignment it bothers me
I'm trying to find the words to articulate why
@full jay can you pass me an example on assignments
Well like the glob = you have right there
ah, I see.
For some reason, in my head if we excluded the else None, it instantly comes up with a lot of questions for me.
But for the return, it makes sense
I really don't know why I have that mental wall there
yea yea, I can see your point there
I mean, you'd notice very quickly that this is what it does I guess
return is spitting out a value, assignment is spitting out a value to the variable
But not instinctively. Certainly not at first
no, but just to satisfy all this common use tho hmm
ternary is a problem if you're actively replacing conditionals with it
idk
Actually, I think I know why
When is it appropriate to use a ternary? So far, it seems like in return values is the only time it works without me looking at the code with a squint to see what its doing.
So like Neon said, when it's part of a return, on the other end we're likely going to be checking if the variable is None anyway to dictate the flow of the code
inline assignments with fallback and concise logic
But as part of an assignment, it feels like it might not be the natural next step
they're commonly used for some sort of default values
I use ternarys when it makes semantical/rhetorical sense
ohhh
indeed
still want do-while
do while is when it does the thing at least once?
foo = rather x than y
imagine if that was your ternary syntax
i like it
No
spam = probably x i_guess y
@deft pagoda we already have do-while, https://github.com/isidentical-archive/pepgrave/blob/master/pepgrave/peps/pep315.py
Dude, your archive is a master work
eggs = kinda y perhaps x maybe z
There's so many interesting goodies in there
hahaha, thanks
I tend to create small projects and dump them there when I'm done and no longer interested
It serves as interesting thought experiments, that's for sure
That's a good habit. I have a lot of things like that just scattered around and they could at least serve some use if I was dumping them to github.
yea, it is mainly for future. If I apply a job, at least I can show some of my example projects from there
Absolutely
Yep, that is what Im trying to do myself right now, in less of a position than you surely but, Im trying to make it obvious that I think about Python everyday.
i've started dumping into gists more
i have a gist that converts numpy arrays to bach
To bach ?
coding is always better with some bach
How should you typehint *?
* alone, or *args ?
I'm guessing they were referring to *args, as in def something(func, *args, **kwargs). But if it was just *, as in the separator for keyword arguments, you definitely wouldn't want to type hint it.
it's still technically a tuple, even if it's just *. under the hood it's just a nameless varargs argument
it's just anonymous
but yeah, typehinting that would be insane
and for *args, you're supposed to typehint what you expect it to contain, not use Tuple
there's something in PEP484 about it iirc
yes
There's also another example where it's unspecified:
yeah, I think that's absolutely fine too
So I think either is probably fine, depending on if constraining the type is relevant
totally
actually for / i have no idea
that's probably just handled in the lexer since it's not valid in any other form.
how do i loop in Python?
cool
hey
@dry charm this is not a support channel.
- is an iterable unpacker
in that context
thx
yeah, I believe the implementation is that all the args you send it get unpacked exactly like with *args but never assigned a name.
Yeah I honestly have no idea how the / separator is implemented under the hood either, language parsing isn't an area that I've gotten into much.
But that would make sense
Welcome. :-)
I'm thinking about starting to publish some Python stuff this year
namely some stuff about profiling and microbenchmarking
didn't expect such a big one
neither did we, I think.
just found this server randomly because Discord just published a public server browser
yep, you and thousands of other people in the past few days.
when did you make this server?
anyway this channel is strictly on-topic python. but I can answer those questions over in #ot1-perplexing-regexing
mike oldfied? this server keeps getting better
: D
will head on to that channel then
what is / separator?
I mean I know * for unpacking or delimiting keywords args
neat stuff
but / has also some interesting thing in it?
** is the one for kwargs
/ is for delimiting positional args iirc
It's new in 3.8 for positional only args in func defs
- is for regular ones
it works in the same as when you intend to unpack a dictionary with **
oh really? do you have some quick example or link?
huh so for positional only you can't use the kwarg form
interesit
although I don't see of the bat much use for it
lot of builtins have positional only args but that's more of a limitation of their variable signatures afaik
yeah ok for built in but for us I mean
I like this feature
I had a nice example but can't think of it now, the pep ought to have something through
I see that in big method/function people often useargs as kwargs for clarity
especially when you develop and often can change function
it really helps that you can pass it with a name
typically you don't want functions/methods which accept a lot of arguments
If a caller of an API starts using a keyword argument, the library author cannot rename the parameter because it would be a breaking change. ok this makes sense indeed
hence that typical pattern of using structs with the arguments and pass that struct to a callable
@peak spoke I guess this would be one of the better examples
yeah but for instance for __init__ it is not uncommon to have many args
with bunch of defaults
just because it's common doesn't typically mean that it's a good idea
but yeah, I've encountered those quite a bit in the wild
especially in ML focused packages
๐คทโโ๏ธ I think for class inits it is not really bad idea either.
I've seen worse things : D
in the code I work with on the job we also have quite big inits
like 15 args or so, and many of them cannot be default
and they are already a kind of data struct (data frame, dict, list etc)
so putting them in another data struc would be likely not very nice/readable
like.... I could make a class to encompas it all and pass that class object. But would it really make sense?
you would have a cleaner and simpler interface, so in my book it would make sense
especially considering that number of arguments
yes but then you would have to write that class with its own init. Call it either way
and then what, unpack it in the final init? or refer to part as self.param_class.5tharg?
I'd say that would be the point.
might seem unnecessary over abstraction
but, to me, it would mean a simpler interface if we're talking about an init that accept >= 10 arguments
I would say that it makes sense if that class would be not a single-purpose thing
you could use a data class for this purpose I guess
then again
context would prevail here (as always) and it might make sense to use a configuration object or not
i.e. OK I go for extra code there but it makes it more modular and/or reusable elswhere. But if it would be just making a data class for the sake of reducing init args count, I think it goes kinda against zen hehe.
who knows how we end up with it ๐คทโโ๏ธ refactors happen
I do see that point on making things a bit less explicit through this sort of obfuscation with a configuration object
@somber halo I might however consider passing the simple numerical Params as dict or smth like that. My main issue with that however is that way if you forget one param instead of crash on init (if it has no default that is) it can jump out later. Or I need extra checks in init
So it's kind of a double edged thing
yeah, you could do some extra validation in the init
the advantage of using these type of configuration objects is that you could use these as a sort of strategy pattern
e.g. having different configuration objects in order to set different functional modes in an algorithm of sorts
hm.....
@oblique crystal
although I don't see of the bat much use for it
Positional-only parameters are very useful from an API design perspective. I recently used in for something I'm working on w/ asyncio to ensure there wasn't a conflict with an parameter called func and a user supplied kwarg of the same name. e.g.async def run(self, func, /, *args, **kwargs)
def func(ar1,ar2,ar3):
print(ar1)
print(ar2)
print(ar3)
d={'ar1':1,'ar3':3,'ar2':2}
func(**d)
``` this can be an option to btw @somber halo
yup, I usually use that one. ๐
could have mentioned using a dict in the first place, sorry
Positional-only parameters are very useful from an API design perspective. I recently used in for something I'm working on w/ asyncio to ensure there wasn't a conflict with an parameter called func and a user supplied kwarg of the same name. e.g.
async def run(self, func, /, *args, **kwargs)
@grizzled vigil yeah I saw that argument in the PEP lemon linked
it does make lot of sense I agree
and you give another good example there
we are at python 3.5 though at work so lots of neat stuff including async one I can't use ๐ฆ
that's an odd version
๐คทโโ๏ธ seem it's one they started with couple years ago and never migrated
but maybe I can try to eventually talk them into migrating, idk, afaik for 3.* when it is 3.5+ it should be quite painless
i mean my part locally I used 3.7 and had no issue
i didn't even know we had 3.5 in prodcution
been EOL for a bit now, and 3.6 + above is where things seem to really start to pick up
yeah. but I joined only recently so I am careful on voicing such things
No g-string tho hit me .
@oblique crystal A transition from 3.5 -> 3.x should be fairly painless, especially if you guys move one version at a time during the migration process. They would probably welcome the information that 3.5 has been EoL, especially if you link to the PEP that includes its release schedule: https://www.python.org/dev/peps/pep-0478/
Using an EoL version in production is rather hazardous, considering that it won't receive security fixes.
!warn 641288909241974785 your comments are not tolerated here. make sure to reread our code of conduct: https://pythondiscord.com/pages/code-of-conduct/ before speaking again
We're a large, friendly community focused around the Python programming language. Our community is open to those who wish to learn the language, as well as those looking to help others.
Let's try to keep this channel focused on the Python language itself, text editors and IDEs are preferably discussed in #tools-and-devops.
#python-discussion is typically fine as well though when it comes up.
Thanks aeros, that's a good idea, linking that pep.
can anyone tell how can i ask a question related to python i have a syntax error and i m unable to solve it plzz help
My coworker like to make a function that take *args for a list of value, and I like to have a list, is there any pep or recommendation supporting this
What do you mean by "want a list"? You can always do list(args) if you need a list instead of a tuple for the packed arguments
depends if you will be calling the variadic function like fun(a, b, c, d, e) or fun(*l)
if both, I would say it does not really matter
for exqmple i would define do do_something([0,10,2,3] and he would do_something(0,10,3,3)
in this case the latter
but if that list were a variable, you would want a list arg
it really makes little difference
okay, I just think it's more readable if you have do_something(value: list) than do_something(*values) because i know it will iterate or do something on a list
can you not
!tempmute 710775817302704178 1H investigating the random number drop
:incoming_envelope: :ok_hand: applied mute to @unkempt rock until 2020-05-15 10:13 (59 minutes and 59 seconds).
@wispy jacinth I think it mostly depends on how you want the end-user to use it
if you are doing something intensive with those values, I would rather use a sequence instead of *args
Why does that guy post phone numbers
if you are just going iterate, you do want an Iterable type hint
in order to allow for generators as an argument
mhm
you should generally not have a list type hint unless you are mutating it
Sequence if you need random access
ah didnt know
Ah btw, are we getting aiter and anext in std any time soon? ๐
Iterable if you do not
async def aiter(some_object):
return await some_object.__aiter__()
async def anext(async_iter):
return await async_iter.__anext__()
``` I think
as a simple comparison
def sum(*args):
s = 0
for x in args:
s += x
return s
def sum_(to_sum: Iterable[int]):
s = 0
for x in to_sum:
s+=x
return s
sum(*range(100000)) would use up more memory than
sum_(range( 100000))
I would get a recursion error
also, the point is a lot less clear when I use a builtin
ok
im curious why is it so rare to find users using boto3 and aioboto3
Ah btw, are we getting aiter and anext in std any time soon? ๐
@cloud crypt IIRC, Yury has been an advocate for it, but there's been some resistance about it being included as a builtin from the core development team due to the average use case being fairly limited. So, it's hard to say if it will happen any time in the near future.
You're still talking as if you're not a part of the core developers team ๐
I've missed it as well, those functions
I'd be in favour of adding them, although I can see what they mean by that the average use case is fairly limited
Still, async-support for the entire standard library is increasing
Haha, yeah. I still tend to talk in the third person out of habit :-)
im curious why is it so rare to find users using boto3 and aioboto3
@pallid igloo
Those are the libaries for AWS, right?
I can see it happening though within the next 5 or so years, it's just hard to say if it will be in the next few versions of Python.
yeah
@wide shuttle the library is giving me this error even following the documentation correctly "got future attached to a different loop", which is weird
It looks like something I'd be using if I were to use AWS.
try to get something to work
I misread your sentence, I think
Is there a way to safely get a dotted path attribute from a string, like "str.replace.__doc__" , without using eval or some unsafe operations like that? 
you could ast.parse it and traverse it manually, crashing if a node is incorrect
or just
current_item = builtins
for attr in some_str.split("."):
current_item = getattr(current_item, attr)
The string will be user-submitted, so I don't want them to be able to execute any code using this
Ah that's smart, thanks
This kind of sandboxing is bound to fail
ast traversal is pretty safe
and I do not think you can inject arbitrary code into getattr either
it is easy to screw this up though
yeah this isn't executing any code
I do wonder how much information you can get with just this though
what kind of path? what's the context
I'm making a python docs scrapper
So things like str.replace can be properly documented
Because they don't have a dedicated help page
i just used the bisect module for the first time
there's this continuous range dict library that i like a lot, so i've just tried my hand at implementing my own version
Can anyone think of a "better" way to achieve this:
items = []
for it in some_it:
items.extend(it)
You're not allowed to use star unpacking in list comps so [*it for it in some_it] doesn't work even though it would be neat, and
list(itertools.chain.from_iterable(some_it)) isn't that much better readability wise
[this is a thinly veiled complaint about not being allowed to unpack in comps]
[elem for sublist in some_it for elem in sublist]
or, if they are specifically lists
i use chain.from_iterable all the time
sum(nested_list, [])
...I don't know why I didn't think of the "nested" loop
sum for list is pretty questionable though 
I'd genuinely rather use reduce
it is, convenient though
def flatten(iterables):
for iterable in iterables:
yield from iterable
list(flatten(my_stuff))
yeah I guess a flatten function would also be a solution
Python needs a builtin flatmap to be honest
I mean, a nested listcomp is close
list(itertools.chain(*some_it))```
having something like [*(2, 4) for _ in range(10)] would be neat though
from more_itertools import always_iterable
def flatten(iterables):
for iterable in iterables:
yield from always_iterable(iterable)
list(flatten(my_stuff))
that won't break on some non-iterables
that doesn't recurse though
ye, you can use a full tree traversal algo for that
wouldn't you use more_itertools.flatten if you're using more_itertools anyways?
it breaks
(though there do seem to be some small differences)
In [117]: r
Out[117]: RangeDict{[0.0, 60.0): 'F', [60.0, 70.0): 'D', [70.0, 80.0): 'C', [80.0, 90.0): 'B', [90.0, 100.0]: 'A'}
In [118]: r[85]
Out[118]: 'B'
I made these
Python's iteration model doesn't really lend itself too-too nicely to composing operations
ah right, it breaks if it's not nested
that is nice
oh hey bisection
yeah i used bisection
class RangeDict:
def __init__(self, *args, **kwargs):
self._items = []
for arg in args:
self.add(arg)
for key, value in kwargs.items():
self.__setitem__(key, value)
def __setitem__(self, key, value):
key.value = value
self.add(key)
def add(self, key):
insort(self._items, key)
def __getitem__(self, key):
items = self._items
while items:
i = bisect_left(items, key)
if key in items[i]: return items[i].value
items = items[i + 1:]
if not items: break
i = bisect_right(items, key) - 1
if key in items[i]: return items[i].value
items = items[:i]
raise KeyError(key)
def __repr__(self):
return f'{self.__class__.__name__}{{{", ".join(f"{key}: {str_(key.value)}" for key in self._items)}}}'
that's the dict
i sort of hacked it though, added value directly as an attribute of the range
@dataclass(unsafe_hash=True)
class Range:
start: float
end: float
start_inc: bool = True
end_inc: bool = False
value: Any = field(default=None, repr=False, hash=None, compare=False)
def __lt__(self, other):
if isinstance(other, Range):
return (self.start, self.end) < (other.start, other.end)
return self.end < other
def __gt__(self, other):
if isinstance(other, Range):
return other < self
return self.start > other
def __contains__(self, value):
return (self.start < value < self.end
or self.start == value and self.start_inc
or self.end == value and self.end_inc)
def __repr__(self):
return f'{"(["[self.start_inc]}{self.start}, {self.end}{")]"[self.end_inc]}'
@classmethod
def from_string(cls, str_, value=None):
if not str_.startswith(('[', '(')) or not str_.endswith((']', ')')):
raise ValueError("Range should start with '[' or '(' and end with ']' or ')'")
start_inc = str_[0] == '['
end_inc = str_[-1] == ']'
start, end = str_[1:-1].split(',')
return Range(float(start), float(end), start_inc, end_inc, value)
_ = from_string # Shortcut to constructor
I have no idea what's going on in that __getitem__
its binary search basically, for finding the right range quickly given some value
i suppose i could just stick to one bisect type
or maybe i can't
it still needs work not to add the same range multiple times
and dunno how i should resolve intersecting ranges
This might help https://en.wikipedia.org/wiki/Interval_tree
i guess i just assume disjoint intervals here
Can anyone think of a "better" way to achieve this:
items = [] for it in some_it: items.extend(it)
what is wrong with extend though?
@molten onyx
Nothing it's just very verbose
if you were to replace extend with append it would be the go-to example of where list comprehensions are useful
what is wrong with extend though?
@molten onyx
@oblique crystal u see here extend can be only used to extend 2 lists but append can help u to join an element to a list...
yeah @potent shell I know what append does. But I assume that in that Ava's example some_it is nested structure so it will be list/tuple there
extend would raise if it were not iterable
yeah
I experienced that recently xD
I was extending with tuples, and I forgot that if you do t1=(elem) it won't be tuple but will just be elem unless you do t1=(elem,)
I never udertood why though as it has different behavior than with single element lists, so it can lead to confusions imo
so that (4 + 3) * 6 is not (7, 7, 7, 7, 7, 7)
oh, never though of it. Makes sense
comma makes a tuple, not the parens
the parens are only necessary in situations where the comma is ambiguous
but then you have () which makes a tuple and that makes me upset
don't think that makes sense
Parens are just a delimiter for the parser, they are thrown away and doesnโt end up in the parse tree iirc
but then you have
()which makes a tuple and that makes me upset
don't think that makes sense
not sure I get what you mean
I got about commas, right if we jsut assign single tuple we don't even need to put ()
so python print("Like this")
() makes an empty tuple
oh lol ok
which I hate
it is hard to find anything better though
you can't make a tuple with an empty tuple in it via (())
Thatโs weird, who wants an empty tuple?
that is what started the converstation ๐
singleton tuples are also a bit off
the wizard does not like to be messed with
I mean, we just looped back to the fact that it feels like there is some loss of consistency here
(8,) is weird
It makes sense though, and make parsing way easier
most languages that have tuples do not have a meaning for singleton tuples since they do something different from python, so you cannot take inspiration there
afaik in many sql libs when you insert values you need to have that kind of things (8,)
If you want to generalize, just always require a comma after an item
() -> empty, no item, no comma
(1,) -> one item, one comma
(1, 2,) -> two items two commas
I guess (,1) doesnโt work, does it? Nope
For convenience, though, we allow dropping the final comma if it leads to an unambiguous expression
So, we get
()
(1,)
(1, 2)
voila
It does make sense to have some trailing commas in some cases
@wide shuttle :white_check_mark: Your eval job has completed with return code 0.
(1, 2)
Like if you have one element per line and keep the trailing comma, git will generate better patches and less conflicts
personally, I do not like commas much for enumerations, I like the lisp way better. I do understand it is needed for parsing
Write a statement in Python to open a text file RETEST.TXT so that new content can be read or written from it.
any help?
but still
and it's nicer to work with in general with trailing commas and elements on individual lines
How does lisp enums works?
ah, no. I just meant enumeration as a list
you just do '(1 2 3 5 8 9 6) (quote optional depending on context)
lots of idiotic stupid parenthesis
Well, thatโs a bit better indeed
meh, with a good editor, parens are fine
If you want to generalize, just always require a comma after an item
yeah I see. it kinda makes sense, for the sake of consistencty at least
Raku kind of has something similar with angle brackets
<1 2 3 5 6> >>+>> 8
(9 10 11 13 14)
``` `>>+>>` being a hyper operator
There is some truth to that
We're just allowed to drop the final , if it's not necessary
The () is necessary with the empty one to indicate that there's something there at all
since you can't type "zero commas"
So, we have two exceptions:
- Need parentheses for empty tuple to indicate that there's an expression intended at all
- We can drop the final comma if dropping it doesn't lead to ambiguity
Since the two come together, as they interact at () and (1,), it looks very inconsistent
wait, when does dropping the trailing comma lead to ambiguity? only when there is exactly 1 element, right?
I think yes
I think so
1, -> 1
Maybe (,) would be better?
no cause one comma per actual elemtn
I'd just not have a literal for an empty tuple
That's two exceptions for a zero-length tuple
Or just use tuple(), and make () be.. I donโt know actually
I honestly go back and forth between hating () and thinking it's okay
You can see it in my code
if the empty set needs no literal, neither does the tuple
Maybe there's an inherent problem in using the same characters for specifying the order of operations and a tuple.
I think they added it because the tuple is very fundamental to Python
{*()}
Well, if the () doesnโt yield a tuple anymore, what would it be? SyntaxError?
yeah, but an empty one?
it's inconsistent with the idea of comma being what creates a tuple
creates a weird exception
for {}[()] in '...':
print('Hello Satan.')
No, not if you follow the rule I outlined above
If you take the rule item,, a zero-length tuple has no items and no commas
You just need the () to force that the "emptiness" is seen as an expression
And then add the convenience rule of allowing you to drop the final , if it leads to a non-ambiguous expression
how long it has been a thing?
Anyway, this is just framing the grammar
I think this was here for quite long
it probably had nothing to do with the original decision
Probably when the tuple was created
i think the 'comma creates a tuple' expression is kinda stupid
well comma is what tell interperter it's a tuple
We're talking about the grammar itself
Well, tuple() is actually slower than () ๐
but here you use constructor explicitly
It's not exactly a fair comparison
>>> dis.dis("tuple()")
1 0 LOAD_NAME 0 (tuple)
2 CALL_FUNCTION 0
4 RETURN_VALUE
>>> dis.dis("()")
1 0 LOAD_CONST 0 (())
2 RETURN_VALUE
Well, it is
it is what it is
so empty tuple is always in memory?
but it's interesting to see what's going on under the hood
Yup
I don't know
In [122]: past('tuple(); ()')
Expr
โฐโโCall
โฐโโName
โโโtuple
โฐโโLoad
Expr
โฐโโTuple
โฐโโLoad
neat
I think it is just caching, the same way ints works
yes, the main difference is that in the first variant we load a name and call a function, while with the second variant, we're directly defining a specific value using the grammar
[] also doesnโt call a function
so you will always get True if you use is to compare empty tuples
print('Hello everyone')
dict doesn't have a load
In [123]: past('dict(); {}')
Expr
โฐโโCall
โฐโโName
โโโdict
โฐโโLoad
Expr
โฐโโDict
or my printer is messed up
[] calls BUILD_LIST, interesting
wait what
and {} calls build_map
>>> () is ()
<stdin>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
True
that's not always a good way to check
!e
print((1,) is (1,))
a = (1,)
b = (1,)
print(a is b)
@wide shuttle :white_check_mark: Your eval job has completed with return code 0.
001 | <string>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
002 | True
003 | True
did not foresee this happening
@deft pagoda only things that can be used as assignment have an expr context (load/store etc.)
>>> (1,) is (1,)
True
>>> a = (1,)
>>> b = (1,)
>>> a is b
False
on 3.7
(c)python keeps a lot of values around, latin1 chars etc. too
Dict/Set displays can't be used to assign, so there is no need for them to contain a context
Ves that's the difference of fully compiled and compiled individually in the repl
difference between 3.7 and 3.8
hm?
@wide shuttle try inspect co_consts
can do
exec("a=1,")
exec("b=1,")
print(a is b)
###
False
When compiling to bytecode, python does an optimization called constant tuples. Which is basically converting tuples to constants if all elements are a constant
on the same code object
so if you do compile("(1,) is (1,)", ...).co_consts you will see only 1 constant
Yeah, i figured
related to the optimization of constant lists to tuples
e.g. for i in [1, 2, 3]
It does that on sets too, when all elements are constant and only used in contain operation
python optimizes where it can for literals
it makes sets frozen
does that shrink a set
โ๏ธ โ๏ธ โ๏ธ
are frozen sets faster?
Yes
i should probably be freezing more sets
And immutable
so it's like tuple/list
I really love the term of freezing, frozen sets, frozen dataclasses
hey soo 'is' comparison is checking to see if it is the same object
I've used frozen dataclasses once
probably should have used namedtuples instead, lol
i think i like dataclasses more
yeah, thats a nice one
a ='aa'
b='aa'
a==b //this is true
a is b // this is also true
So python reuses strings it previously created?
hi
hey
interesting... for Dataclasses:
There is a tiny performance penalty when using frozen=True: __init__() cannot use simple assignment to initialize fields, and must use object.__setattr__().
so while tuple/set are faster cause they are immutable/frozen, the user data classes aren't
yeah, but you get hashes
it reuses things it can pop into a constant during compilation, but I believe strings have a bit more going around them since with dicts they're the base of a lot of things
Well, namedtuples are cool, but they are tuples, they donโt serve the same purpose as dataclasses
a ='aa'
b='aa'
a==b //this is true
a is b // this is also trueSo python reuses strings it previously created?
That's because Python optimizes strings. They're immutable, so there's no need to keep around two copies of the same string.
@fresh warren
You can force them to be different objects by using an f-string with an expression in it
!e
a = "aa"
b = f"a{'a'}"
print(a == b)
print(a is b)
@wide shuttle :white_check_mark: Your eval job has completed with return code 0.
001 | True
002 | False
That's why using is with literals now gives a warning in Python 3.8
Oh i see thats interesting
The implementation/optimization details can muddy the water a bit
I guess another way is to create a wrapper class around string if u wanna force it to be a new obj
makes sense
all is interesting, but I really have to go back to my work. I wish sometimes I could self-ban from this channels ๐
Well, just close the window ๐
okay now I am thinking
Dataclasses have a __post_init__ which is super cool
>>> a, b = (), ()
>>> ctypes.c_longlong.from_address(id(a) + 8).value = id(Tuple)
>>> type(a), type(b)
(<class '__console__.Tuple'>, <class '__console__.Tuple'>)```
expected that tbh
i wish meta classes had a post_init
@deft pagoda i'm looking into metaclasses atm
class Metastamp(type):
def __init__(cls, bases, args, dct):
cls._metastamp = datetime.utcnow()
is there a better way to do something like this?
it's not real-world btw
that's fine, you could always just keep a dictionary around
i think people use meta classes to register classes fairly often, so you could add time information too
though you can do that in init_subclass now
i still think returning variations of defaultdicts from __prepare__ is my favorite use of metas
!e
from itertools import count
class IncrDict(dict):
def __init__(self, start=0, step=1):
self.counter = count(start, step)
def __missing__(self, key):
if key.startswith('__'):
raise KeyError(key)
self[key] = next(self.counter)
return self[key]
class EnumMetaclass(type):
def __prepare__(*args, **kwargs):
start = kwargs.get('start', 0)
step = kwargs.get('step', 1)
return IncrDict(start, step)
class Enum(metaclass=EnumMetaclass):
def __init_subclass__(cls, **kwargs):
"""Pull kwargs from class def"""
class Fruit(Enum, start=10, step=15):
APPLE
BANANA
KIWI
print(Fruit.APPLE, Fruit.BANANA, Fruit.KIWI)
@deft pagoda :white_check_mark: Your eval job has completed with return code 0.
10 25 40
neato
nice
!e
class Stringy:
def __init__(self, name):
self.name = name
def __getattr__(self, attr):
self.ext = attr; return self
def __matmul__(self, other):
print(f'Emailing {self}@{other}')
def __str__(self):
return '.'.join(attr for attr in self.__dict__.values())
def __call__(self, msg):
self.msg = f'.. {msg}'; return self
class StringyDefaultDict(dict):
def __missing__(self, key):
if key.startswith('__'): raise KeyError(key)
self[key] = Stringy(key); return self[key]
class EmailMeta(type):
def __prepare__(*args):
return StringyDefaultDict()
class Server(metaclass=EmailMeta): ...
class Email(Server):
lemon@discord.py('Hi lemon!')
ves@zappa.edu('Hi ves!')
joe@mama.com('Hi joe!')
@deft pagoda :white_check_mark: Your eval job has completed with return code 0.
001 | Emailing lemon@discord.py... Hi lemon!
002 | Emailing ves@zappa.edu... Hi ves!
003 | Emailing joe@mama.com... Hi joe!
similar trick here
In [57]: Fruit = type('Fruit', (Enum,), dict.fromkeys(["APPLE", "KIWI"]), start=10, step=15)
In [58]: Fruit.KIWI
In [59]:
``` how would you call this inline?
you need to () afterwards
still nothing
that's the point really
class Fruit(Enum, start=10, step=15):
APPLE
BANANA
KIWI
is very nice to look at, at least to me
ye, it is quite nice
you could try passing in the defaultdict
that might work, i dunno
no, i guess, it still won't be able to do anything
what was i thinking
who knows
That reminds me of a little thing Iโve done with matmul https://gist.github.com/Akarys42/a28e3b3b3f6c05fcb597753c2593bda9
Esoteric email address - see use-case.py for more info! - esoteric-email.py
i started to brainstorm ways to use properties instead of metas, but the meta solution seemed easier
Maybe you could do algebraic data types like that : )
class List(SumType):
Nil
Cons[int, List]
But I'm not sure how you would allow the reference to List in Cons[int, List]
with a pretty sophisticated dict-like from __prepare__
What is __prepare__ used for?
Hm
This channel is for general discussion of the Python language. Check out #โ๏ฝhow-to-get-help if you want help with a specific question.
@deft pagoda
!e
from collections import namedtuple
DeferClass = namedtuple("DeferClass", "name")
class SumType(type):
@classmethod
def __prepare__(metacls, cls, bases, **kwds):
return {cls: DeferClass(cls)}
class A(metaclass=SumType):
x = A
print(A.x)
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
DeferClass(name='A')
Maybe add some descriptor magic?
Why am I able to change the size of a list while iterating over it without python complaining, but doing the same thing iterating over a dictionary python throws a runtime error
because the iterators work differently internally
Both are bad practices - mutating a collection while iterating, but only one of those two exmaples causes an error
the list iterator just goes index by index afaik
# i mean this shold cause an error, but doesn't
x = [1,2,3,4]
for i in x:
x.pop()
the dict iterator has to do more work, as traversing a hash table is not easy
the reason the dict errors is not because it is bad practice, it errors because the default iterator it not capable of iterating over a mutated dict
python will happily let you stab yourself
Yea -- thanks @grave jolt that is what I am looking for
s, x = y i have seen something like this in some codes, what does the , is for
and what that means?
!e
colors = ["red", "green", "blue"]
color1, color2, color3 = colors
print(color1)
print(color2)
print(color3)
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
001 | red
002 | green
003 | blue
o, o ok thx
I find that mechanic helpful
you can also use * to take multiple elements
zero, *others, four = range(5)
others is then a list of 1 2 3
https://gist.github.com/salt-die/1adebb3639ded8e1215b2fa2584d0583 i use a meta here to place dummy objects for all the attributes --- then i modify them later in the __new__ method
What does this have to do with Python?
@undone hare We're not talking about how names and values are mapped internally, but rather what the properties and behavior of the object itself it
For an object to implement the Mapping protocol, it needs to support some operations
Things like__getitem__, __len__, __contains__ keys, items, values, get
A MutableMapping should also be able to __setitem__ and so on
So, objects may use a mapping/dict-like object internally or Python may use a dict/mapping internally to implement those objects
Ah yes, my pint was about that each objects are mapping by definition, because they map an attribute name to a value, not in a python python sense
but those objects themselves don't support that interface
But all objects are using dicts under the hood, aren't they?
My bike has wheels, but it's not a wheel itself
But it has a wheel attribute that refer to its actual two wheels
Yes, so it's a has-a relationship, not an is-a relationship
The dictionaries are mapping objects, we're using those. The object that has a dict as an attribute is not a mapping object itself.
There's no inheritance relationship, but rather composition
Anyway, the point was more that our Cog objects don't need to become Mapping objects
Well, it has some attributes that are mapping already
So we could dump those in the cache, and they will be.. Cached, I guess
But I think we've meandered away from the original conversation about ABCs
I think I expressed myself wrongly
My thoughts were about having redis compatible objects, that can be dropped into the cache, and be serialized
So we could have a redis serializable dictionary, and drop it into the cache, to have the two required lists
I had been glossing over what names tuples are, but I finally looked into them last night
Where have these been all my life?
(in collections, obviously)
Can someone give me code to convert this epoch to date
1589490297436
Time not required
Date in YYYYMMDD format
what have you tried
You can use the .fromtimestamp methods, but not really the right channel for this
this isn't the channel for this. try asking in a help channel instead
I have tried searching on google using datetime module and time
this isn't the channel for this. try asking in a help channel instead
@north root Ok
I know I should use a help channel for this, but it's just a quick question. Are lambda functions restricted to doing math only?
Their syntax is restricted to expressions
but realistically you only want to use them for simple stuff in certain situations
Like a quick print statement?
python lambdas are very restricted compared to other langs
well no
it can be any expression
idk if big dumb is the right word
but they are certainly more restricted
you cant use them as say an event callback
๐ it is what it is
print is a function call in python 3, which is an expression
but you cant have more then one afaik
or with chaining or
basically lambdas are very restricted
most of the usage lambdas get are the key arguments for min/max/sort etc. or linking callbacks in things like GUI modules
python doesnt want you using them like you would in another lang
they arent really closures
Aren't they? All functions in python are closures.
what are closures?
!e
def f():
x = 1
def g(): return x
x = 2
return g
print(f()())
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
2
!e
def f():
x = 1
g = lambda: x
x = 2
return g
print(f()())
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
2
cool
>>> x = 1
>>> g = lambda: x
>>> x = 2
>>> g
<function <lambda> at 0x7f14d0406c80>
>>> g()
2
So, the same thing, outside of a function. This is illustating that... x is leftover after being returned but still inside the current scope. When g is called, it returns the last value of x?
Here are a few more examples of me trying to make sense of what is going on
>>> y = 1
>>> g = lambda: y
>>> y()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not callable
>>> g = lambda: z
>>> g()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 1, in <lambda>
NameError: name 'z' is not defined
I think I get it. Unless I dont.
For some reason this makes it more clear.
>>> def func():
... x = 1
... def g(): return x
... x = 2
... return g
...
>>> func()
<function func.<locals>.g at 0x7f14d0406bf8>
There is a function inside the function when the function is called
The function holds on to the value in the surrounding scope in its closure
You can actually inspect that
>>> def function():
... def g():
... print(a)
... a = "hello"
... return g
...
>>> g = function()
>>> g.__closure__
(<cell at 0x7fbf8bc85c50: str object at 0x7fbf8bc85c70>,)
!e
def function():
def g(): print(a)
a = "hello"
return g
g = function()
print(g.__code__.co_freevars)
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
('a',)
I get it. I still have trouble seeing the application of this, because it seems mind bendy for the sake of it. But I know that eventually the use cases will become clear to me in an epiphany. Decorators are a form of closure right?
Well, decorators use closures, yes.
!e
def log(fn):
def decorated(*args, **kwargs):
print(f"[LOG] {fn} {args} {kwargs}")
return fn(*args, **kwargs)
return decorated
decorated_abs = log(abs)
decorated_abs(-42)
decorated_abs(6.7)
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
001 | [LOG] <built-in function abs> (-42,) {}
002 | [LOG] <built-in function abs> (6.7,) {}
Here decorated has access to fn which is local in log's scope.
!e
# Here's a gotcha:
adders = [(lambda x: x + a) for a in range(10)]
print(adders[3](7))
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
16
It's equivalent to:
def __adders_listcomp():
result = []
for a in range(10):
result.append(lambda x: x + a)
return result
adders = __adders_listcomp()
(Literally!)
!e
import dis
def f():
return [a+1 for a in range(10)]
dis.dis(f)
3 0 LOAD_CONST 1 (<code object <listcomp> at 0x7fbbc91d5240, file "<string>", line 3>)
2 LOAD_CONST 2 ('f.<locals>.<listcomp>')
4 MAKE_FUNCTION 0
6 LOAD_GLOBAL 0 (range)
8 LOAD_CONST 3 (10)
10 CALL_FUNCTION 1
12 GET_ITER
14 CALL_FUNCTION 1
16 RETURN_VALUE
Disassembly of <code object <listcomp> at 0x7fbbc91d5240, file "<string>", line 3>:
3 0 BUILD_LIST 0
2 LOAD_FAST 0 (.0)
>> 4 FOR_ITER 12 (to 18)
6 STORE_FAST 1 (a)
8 LOAD_FAST 1 (a)
10 LOAD_CONST 0 (1)
12 BINARY_ADD
14 LIST_APPEND 2
16 JUMP_ABSOLUTE 4
>> 18 RETURN_VALUE
Wow, thats is a module i have to learn what its doing
It prints something as bytecode.
The operation names are pretty self-explanatory, actually.
LOAD_CONST loads some constant. In this case it's a piece of code.
LOAD_CONST loads a constant -- string 'f.<locals>.<listcomp>'
MAKE_FUNCTION creates a function with the given code object and the given name
LOAD_GLOBAL loads a global variable, in this case by the name range
LOAD_CONST loads 10
CALL_FUNCTION calls range, taking the top1 elements from the stack to use as arguments.
...and so on
I'm going to have to play with using lambda as the left most statement in a listcomp
As you can see from the example I ran, the function doesn't behave as expected.
# Here's a gotcha:
adders = [(lambda x: x + a) for a in range(10)]
print(adders[3](7))
>>> 16
Because a is just a variable inside the list comprehension, and it will change with the next iteration.
right
In this regard, map might be better:
!e
adders = list(map(lambda a: lambda x: x + a, range(10)))
print(adders[3](7))
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
10
In simple terms, map(fn, iterable) <=> (fn(x) for x in iterable), but it's not literally true because of this gotcha
If you find list(map( ugly (I do), you can use ```py
[*map(lambda a: lambda x: x + a, range(10))]
Other ways to solve it:
adders = [(lambda x, a=a: x + a) for a in range(10)]
adders = [(lambda a: lambda x: x + a)(i) for i in range(10)]
# the variable name in the inner function doesn't matter, it's changed only for clarity
The entire concept of programming with map, reduce, filter, lambda is still foreign enough that I have to squint at the screen to get it. But I mostly do. I need to practice using those solutions but I would much prefer list comps. Does the "gotcha" you mentioned only ever happen if the left most statement of the list comp is a lambda?
A lot of solutions could very well just use the lazy object instead of a list
adders = map(lambda a: lambda x: x + a, range(10))
I see a lot of people just using lists because that's what they're familiar with
I'm gonna make it a project to use those functions for the sake of it until I get them without thinking about it.
If I look at the function and then the "translation" into a list comp or some iteration, I can see it, but I still have to look at the translation.
You can try to implement your versions of map, filter, reduce (aka foldl), rfold, chain and count as an exercise.
sounds like a good idea
But python isn't a functional language, so these functions are often clumsy to use.
It's often more readable to use list or generator comprehensions instead of filter and map.
yeah I have gathered, yet still Id like to be familiar with them anyway.
But that depends on your tastes and the particular problem.
I think it will make for a good thing to post as a gist for a person of my level to do that exercise. Ill do that next.
If you really like functional stuff, you can also implement Maybe.
Maybe is a container that contains either Nothing or Just <value>. It can be used for error handling without using exceptions.
Example:
safe_division(6, 3) = Just(2)
safe_division(90, 10) = Just(9)
safe_division(42, 0) = Nothing
Then there's the bind operator that looks like this: >>=. It allows you to to apply a function to a Maybe.
# Just x >>= f == f x, given that f returns a maybe
(Just(9) >>= safe_sqrt) == Just(3)
(Just(-2) >>= safe_sqrt) == Nothing
# Nothing >>= f == Nothing
(Nothing >>= safe_sqrt) == Nothing
Python doesn't have >>=, but >> is close enough.
So, for example, if you have 4 functions: validate_username, validate_password, validate_email, validate_country_code, that take a UserRegistrationRequest as an input and return Maybe UserRegistrationRequest, you can define a registration function easily like this:
def validate_registration_request(r):
return (
Just(r)
>> validate_username
>> validate_password
>> validate_email
>> validate_country_code
)
def register_user(registration_request):
registration_request = validate_registration_request(registration_request)
if registration_request == Nothing:
# report the error
else:
# register the user
Or you can do something #esoteric-python-worthy:
@if_just
def register_user(registration_request: UserRegistrationRequest):
# register the user:
@if_nothing
def register_user():
# handle the error
(as a technicality, Just(-2) >>= abs would generally not work, you would want abs <$> Just(-2) <$> the operator for functor map. >>= works if the function also returns a Maybe.)
Oh, that's true
there is also splat <*>, but that would not be too useful with python
is that Maybe and Just built in or imported?
Are you guys trying to make Pyskell again?
@unkempt rock No, Maybe is a general concept that exists under different names in some languages.
I see.
I don't know if it's a natural addition to python, but you can still implement it as an exercise.
yeah thats probably further down the functional with python rabbithole than I plan to go. Still, you do see use of the "functional functions" in the wild enough that I shouldnt have to open a thesaurus to read what they say
Are you guys trying to make Pyskell again?
Tell me more ๐
Tss
Tbh thatโs not a bad idea, Iโve been looking for a cool idea for a language
aka Shh
dogelang
Dogelang is the other way around, which is a bit silly
which tbh, it the exact opposite of A python syntax functional Lang
I do kind of want to make some of the more complex typeclasses
like Bicyclic Monads
๐ด <=<๐ด
Well, Elixir is a ruby-syntaxed functional language, maybe the same could be done for python.
Well, you could transpile something like that into Python.
But i can't see there selenium
Tbh Iโve been wanting to write a new language
def traverse_list(list_):
___0m = ___match([], (list_,), {})
if ___0m is not ___NOMATCH:
return "done"
___1m = ___match(___List("h", "t"), (list_,), {})
if ___1m is not ___NOMATCH:
h, t = ___1m
return traverse_list(t)
I wrote a compiler at my last job but that was for 2 known Langโs
I wanna make something new
Python functional Lang piques my interest
If you want to use python for that, I can recommend lark-parser. It supports LALR, Earley and CYK parsers and can be used with EBNF-grammar.
Well, it would make a good prototype, especially if it's gonna be transpiled into python
Thereโs a couple ways i could
It also has the python grammar as an example
Python would be cool, kinda wanna learn rust
Compiler as a tutorial project ๐
Lol Iโm a masochist
I see
Don't forget to check out Hylang
A python syntax functional Lang would be dope gave me a hint
Transpiring to python would be easier then targeting say llvm
true
After transpiling, you can also use pypy3 for performance
Since writing a JIT is probably not an easy task.
Ya donโt really wanna touch that lol
I recently made an impure dynamic functional language and solved some easy uni problems with it, with that library it took me ~600 sloc.
product = list -> lfold(a, x -> a * x, 1, list)
sum = list -> lfold(acc, x -> acc + x, 0, list)
get_first = s -> let [a, b]=s in a
get_second = s -> let [a, b]=s in b
positive_mod = x, m -> if mod(x, m) > 0 then mod(x, m) else mod(x, m) + m
chinese_theorem = inputs ->
let
rs = map(get_first, inputs)
as = map(get_second, inputs)
in let m = product(as)
in let mis = map(a -> div(m, a), as)
in positive_mod(
sum(map(
s -> let [a, r, mi]=s
in r * mi * inverse(mi, a),
zip(as, rs, mis)
)),
m
)
inverse = e, a -> let [x, y, gcd] = euclid(e, a) in x
-- ax + by = gcd(a, b)
-- (a, b) -> [x, y, gcd(a, b)]
euclid = a, b ->
if b == 0 then [1, 0, a]
else let [y, x, g] = euclid(b, mod(a, b))
in [x, y + (-1)*div(a, b)*x, g]
{- [r, a]: x === r (mod a) -}
__main__ = chinese_theorem([
[6, 28],
[22, 27],
[34, 37],
[13, 31]
])
Implementation of the https://en.wikipedia.org/wiki/Chinese_remainder_theorem algorithm
Yeah, it's a bit ugly...
interesting
Looks a bit nicer with ligatures!
I didn't make proper pattern matching because I'm lazy.
But it works in let.
Naming variables with single letters helps as well.
I decided to steal comment syntax from haskell just because of syntax highlighting.
Is it cheating if after trying to write the my_map function I decide to just look at the stdlib? haha @grave jolt
If it only accepts a single iterable and func only takes one arg, I can probably get it working but its the cases where the func takes multiples args and the possible of iterables is many that mine will start to break
I might think about it a little more before I look
none of the builtins are
Makes sense, that's why they are built-ins
map is a class in python. in builtins.py. Thats really weird.
I thought it was a function because...
its not capitalized like a function
class map(object):
"""
map(func, *iterables) --> map object
Make an iterator that computes the function using arguments from
each of the iterables. Stops when the shortest iterable is exhausted.
"""
def __getattribute__(self, *args, **kwargs): # real signature unknown
""" Return getattr(self, name). """
pass
def __init__(self, func, *iterables): # real signature unknown; restored from __doc__
pass
def __iter__(self, *args, **kwargs): # real signature unknown
""" Implement iter(self). """
pass
@staticmethod # known case of __new__
def __new__(*args, **kwargs): # real signature unknown
""" Create and return a new object. See help(type) for accurate signature. """
pass
Dioes that not look like an excessive amount of empty methods that just say pass?
That's probably a generated stub file
range is a neat class that some people may not think is one
Probably almost this entire file is nothing but methods that say pass
Yes, it's s stub
probably has a .pyi extension
Well, you can try to make sense of it.
I'd expect a proper stub to use ... and define the types
this is how I'd expect a normal stub file to look https://github.com/python/typeshed/blob/master/stdlib/2and3/builtins.pyi#L1218
JavaScript. HTML, CSS.
I can do that.
That overload decorator might be the thing Im missing...
Wait a second
Is that overload literally how they define the function six times with different numbers of parameters?
That is so cheap
haha
I might have to look into these things more to understand if thats what that is
the overload is just for typing
(stub files are to help ides etc. get the types for you)
CPython is not the only implementation. There's also PyPy, written (previously, I think) in Python
>>> def my_map(func, *iters):
... mapped = []
... for iter in iters:
... for idx in iter:
... mapping = func(idx)
... mapped.append(mapping)
... return mapped
the part that has me messed up is the multiple iterables that are possible. I know thats flawed. But that is... almost there... sort of...
not really
it might be better to do
[func(x) for x in iters]
You can use zip
yeeeeah
||```py
def my_map(func, *iterables):
mapped = []
arg_tuples = zip(iterables)
for args in arg_tuples:
mapped.append(func(*args))
return mapped
yeah also the possibility of multiple args was the next problem
You would need yield for that
I'm not so good with how to write those but I know that you do that./
def my_map(func, *iterables):
arg_tuples = zip(*iterables)
for args in arg_tuples:
yield func(*args)
It also looks much nicer.
I wish lists, sets and tuples had a map method.
I have to play with all these snippets before all this will sink in but its at least at the point now where I immediately understand what a map call is returning
[1, -2, -3].map(abs) == [1, 2, 3]
(1, -2, -3).map(abs) == (1, 2, 3)
Well, you can pick a project where you'll have to work with sequences a lot.
!e
print(__builtins__.__doc__)
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
001 | Built-in functions, exceptions, and other objects.
002 |
003 | Noteworthy: None is the `nil' object; Ellipsis represents `...' in slices.
Are different quotes in nil and ... intentional?
looks like it is left from 1998
>>> my_map(add)
<generator object my_map at 0x7f14cc447a98>
>>> map(add)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: map() must have at least two arguments.
Oh, there is a mistake there. The args to zip should be unpacked
If you know where to look for them, python is a solid tool to assist with that
@flat gazelle fixed
With that unpacking fix, it looks like the my_map works
I have been testing it and trying to break it
and comparing it to map
it seems pretty good
this reddit post is quite interesting https://www.reddit.com/r/Python/comments/gk8jnj/friday_python_puzzler_whats_the_difference/
47 votes and 23 comments so far on Reddit
for those who don't want to open reddit lol
def one(): numbers = range(6) numbers = (i for i in numbers if i % 2) numbers = (i for i in numbers if i % 3) return numbers def two(): numbers = range(6) for p in (2, 3): numbers = (i for i in numbers if i % p) return numbers print('one', *one()) print('two', *two())what's the difference?
||p ends up same in both cases for two() because generators are lazy and the name p only gets evaluated after the generator is advanced?||
Kinda, it's the same issue as with lambdas in a comprehension.
Also
!e
import dis
def two():
numbers = range(6)
for p in (2, 3):
numbers = (i for i in numbers if i % p)
return numbers
dis.dis(two)
: - (
Oh, the top comment on reddit had the same idea
Comprehensions are implemented as functions to avoid polluting the local namespace.
And the iterable get passed as an argument to that function, so there is no self-reference there.
No, I'm wrong.
It's interesting that such a powerful function that can be used for so many things could be packed into the few lines that made our my_map
Let me show you a more powerful one
def nand(x, y):
return (not (x and y))
You can get to any combinational logic only by using composition of nand.
that is interesting but seems too mind bendy to be practical. It's forcing me to go back over when I memorizaed these. If I remember correctly, with and, if either is False the total is False.
So.... in this case, if either is False, then its True?
There's also xor.. I think it's also one that alow to build any other
I don't think so.
I would like it if generator expressions could have __repr__ that showed the entire list, but if that was possible it would defeat the purpose wouldnt it?
How do you make OR with XORs?
instad of <generator blah blah at 0x7dsa7fsdf67889>
Well, yes, it would defeat the purpose.
Haskell can do it, why can't python
You could make your own generator wrapper that would print a few first values.
I just pass it into list() but sometimes it doesnt work
You can't always print everything in the generator.
yeah
!e
def zero():
while True:
yield 0
print(list(zero()))
@grave jolt :warning: Your eval job timed out or ran out of memory.
[No output]
wtf
isnt that an infinite generator of 0s?
right
@flat gazelle Can you make a lazy list do some side effects on fetching the next element in Haskell?
In Python, that would be a problem while printing a generator.
ok
class GenWrap:
def __init__(self, gen):
self.gen = gen
def first_10(self):
gen = self.gen
for idx, yielded in zip(range(10), gen):
print(f"{yielded} ", end="")
def __repr__(self):
self.first_10()
return ""
def zeros():
for i in range(100):
yield 0
zw = GenWrap(zeros())
print(zw)
this is imperfect
!e
class IterablePeeker:
def __init__(self, iterable):
self.iterable = iter(iterable)
self.is_iterable_exhausted = False
self.cache = []
self.load(3)
def load(self, n):
try:
for _ in range(n):
self.cache.append(next(self.iterable))
except StopIteration:
self.is_iterable_exhausted = True
def __iter__(self):
return self
def __next__(self):
if self.cache == []:
raise StopIteration
result = self.cache.pop(0)
self.load(1)
return result
def __str__(self):
if self.is_iterable_exhausted:
return "iter([" + ", ".join(map(str, self.cache)) + "])"
else:
return "iter([" + ", ".join(map(str, self.cache)) + ", ...])"
i = IterablePeeker(range(6))
for x in i:
print(f"value: {x}, iterator: {i}")
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
001 | value: 0, iterator: iter([1, 2, 3, ...]
002 | value: 1, iterator: iter([2, 3, 4, ...]
003 | value: 2, iterator: iter([3, 4, 5, ...]
004 | value: 3, iterator: iter([4, 5])
005 | value: 4, iterator: iter([5])
006 | value: 5, iterator: iter([])
damn
This version allows you to 'peek' into the generator while preserving all the elements.
god dammit, forgot the )
yeah so thats why I was trying to alias self.gen into gen because I knew if I started yielding them it would break the generator
let me look this one over
@grave jolt I like the above implementation, particularly the good usage of StopIteration. But you may want to consider using deque for self.cache instead of a list, for popping on the left end (result = self.cache.pop(0)). That's not particularly great on performance if the list size grows to anything non-trivial, as all of the other elements have to be shifted accordingly if it's not using a linked list (which is effectively how deque is implemented). I know it's just an example, but figured that it worth mentioning (either for you or anyone else that might not be aware).
I have considered that, since pop(0) makes each __next__ O(k) where k is the cache size
I like a lot of things about this code.
I should've mentioned that, thanks, @grizzled vigil
To be fair, I never used deque before.
Ive never added dunder methods to something that made it iterable before so I didnt think of them as obvious
Well, you can create a function that creates a generator function and then attaches a __str__ to it, but that's kinda nasty
!e
from collections import deque
class IterablePeeker:
def __init__(self, iterable):
self.iterable = iter(iterable)
self.is_iterable_exhausted = False
self.cache = deque()
self.load(3)
def load(self, n):
try:
for _ in range(n):
self.cache.append(next(self.iterable))
except StopIteration:
self.is_iterable_exhausted = True
def __iter__(self):
return self
def __next__(self):
if len(self.cache) == 0:
raise StopIteration
result = self.cache.popleft()
self.load(1)
return result
def __str__(self):
if self.is_iterable_exhausted:
return "iter([" + ", ".join(map(str, self.cache)) + "])"
else:
return "iter([" + ", ".join(map(str, self.cache)) + ", ...])"
i = IterablePeeker(range(6))
for x in i:
print(f"value: {x}, iterator: {i}")
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
001 | value: 0, iterator: iter([1, 2, 3, ...])
002 | value: 1, iterator: iter([2, 3, 4, ...])
003 | value: 2, iterator: iter([3, 4, 5, ...])
004 | value: 3, iterator: iter([4, 5])
005 | value: 4, iterator: iter([5])
006 | value: 5, iterator: iter([])
IterablePeeker v2, now with ) and deque!
I like how you have used try/except blocks not just to give you messages that things are not working but as part of the control flow. I do that very simply, typically to explain when a type is not the right type. It saves a conditional.
But you are fully expecting that StopIteration
Which then affects things
I don't really like using try-except as logic mechanisms.
And with the iterator protocol, it can be misleading
Because if a StopIteration occurs unexpectedly inside a generator, it will be silenced and treated as a signal to stop the iteration.
mm
Also your use of map to stringify everthing in the cache is a pretty useful way of using map.
Oh, I'm wrong
!e
def gen():
yield 1
yield 2
generator_that_should_not_be_empty_but_is_because_of_a_bug = iter([])
yield next(generator_that_should_not_be_empty_but_is_because_of_a_bug)
yield 3
print(*gen())
@grave jolt :x: Your eval job has completed with return code 1.
001 | Traceback (most recent call last):
002 | File "<string>", line 5, in gen
003 | StopIteration
004 |
005 | The above exception was the direct cause of the following exception:
006 |
007 | Traceback (most recent call last):
008 | File "<string>", line 8, in <module>
009 | RuntimeError: generator raised StopIteration
makes sense
maybe don't have a the 3 hard coded, but as another parameter
with a default of 3
I dont know if I will ever be good enough to write out that in the amount of time that you wrote that whole thing out. I think it took me as long to go over it and read it as it took you to write it. And in the same time, I wrote mine haha.
But maybe I will get better. I at least understand it
also, having try, except used for control flow is perfectly fine for python, i don't see anything wrong with it
on other languages it's typically not a good idea
however, in the Python world it's kind of accepted for that purpose
The only time I do it is to avoid an if statement that I can just catch with an error. Especially if there are many ifs required.
personally, I avoid try/except clauses for control flow despite the acceptance of it with Pythonistas
Its more than acceptance. There are people who teach that you should be doing it that way
Yeah, the main problem with my GenWrap is that it does exhaust the generator by trying to look at it.
!e
from collections import deque
from collections.abc import Iterable
class IterablePeeker:
def __init__(self, iterable, /, *, cache_size=3):
if not isinstance(iterable, Iterable):
raise TypeError("first argument to constructor must be iterable, got:"
f"({type(iterable)}) {iterable}")
if not isinstance(cache_size, int):
raise TypeError(f"cache_size must be int, got: ({type(cache_size)}) {cache_size}")
self.iterable = iter(iterable)
self.is_iterable_exhausted = False
self.cache = deque()
self.populate_cache(n=cache_size)
def populate_cache(self, n):
try:
for _ in range(n):
self.cache.append(next(self.iterable))
except StopIteration:
self.is_iterable_exhausted = True
def __iter__(self):
return self
def __next__(self):
if len(self.cache) == 0:
raise StopIteration
result = self.cache.popleft()
self.populate_cache(1)
return result
def __str__(self):
if self.is_iterable_exhausted:
return "iter([" + ", ".join(map(str, self.cache)) + "])"
else:
return "iter([" + ", ".join(map(str, self.cache)) + ", ...])"
# TeSt-DrIvEn DeVeLoPmEnT! # I can't use comic sans here, so here's an alternative
i = IterablePeeker(range(6))
for x in i:
print(f"value: {x}, iterator: {i}")
i = IterablePeeker(IterablePeeker(range(6)))
for x in i:
print(f"value: {x}, iterator: {i}")
@grave jolt :white_check_mark: Your eval job has completed with return code 0.
001 | value: 0, iterator: iter([1, 2, 3, ...])
002 | value: 1, iterator: iter([2, 3, 4, ...])
003 | value: 2, iterator: iter([3, 4, 5, ...])
004 | value: 3, iterator: iter([4, 5])
005 | value: 4, iterator: iter([5])
006 | value: 5, iterator: iter([])
007 | value: 0, iterator: iter([1, 2, 3, ...])
008 | value: 1, iterator: iter([2, 3, 4, ...])
009 | value: 2, iterator: iter([3, 4, 5, ...])
010 | value: 3, iterator: iter([4, 5])
011 | value: 4, iterator: iter([5])
... (truncated - too many lines)
Full output: https://paste.pythondiscord.com/limocupaza
It's partly a matter of preference: LBYL (look before you leap) vs EAFP (easier to ask for forgiveness than permission). From a performance perspective, it depends on how frequently the exception occurs, but is trivial in most cases.
It's getting kinda long, Maybe I should've posted it on gist.
I'm definitely in the camp of LBYL.
Its beautiful to see someone post code and then another person improve and then another person
It's called git :)
yeah haha