#internals-and-peps

1 messages ยท Page 39 of 1

deft pagoda
#

it's only ambiguous until you define it in the grammar

full jay
#

But it's objectively a less clear option than others

gray mirage
#
x = (y if condition)  # -> x = None
(x = y) if condition  # -> Nothing happens
deft pagoda
#

no

gray mirage
#

it's ambiguous which of these is actually happening without parens

willow shore
#

man yeah the logic needed to make up for it starts becoming annoying

full jay
#

See to me, I'd expect it to error out instead

deft pagoda
#

statement if condition or expr if condition else

full jay
#

Because if it's not that condition, and it has no other option, it panics

deft pagoda
#

you can determine which is which with simple look ahead

gray mirage
#

which, again, is adding unnecessary mental overhead to something we already have a one-line solution for

#
if condition: x = y
deft pagoda
#

yes, but it would be consistent with generator expressions, which is why i want it

full jay
#

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

willow shore
#

it would have to be like x = y if condition (else x = x or None)

gray mirage
#

it doesn't feel natural to me either

deft pagoda
#

i'd also like it for lambdas

willow shore
#

implied

deft pagoda
#

(x + y for x, y)

full jay
#

Explicit is better than implicit

#

And especially so in this case

gray mirage
#
lambda x, y: x + y
#

again, that's pretty readable

full jay
#

There's a lot of potential what ifs in those

deft pagoda
#

More implicit is more than implicit.

gray mirage
#

although I am of the opinion that lambdas should be banned

unkempt rock
#

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

willow shore
#

if you actually do omit else though I believe it would have to imply else x = x or none

#

so it actually gets defined ?

flat gazelle
#

ye, python does not do long expressions well

willow shore
#

but doesn't change the value if it is already defined

flat gazelle
#

unlike kotlin , raku, and pure functional languages

willow shore
#

so health = 100, health -= 10 if player.ishit would still work

flat gazelle
#

for good reasons

gray mirage
#

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

full jay
#

Something else that checks for that would call the hit

willow shore
#

that's really not the point

gray mirage
#

I suppose that's true

willow shore
#

that's entirely up to what you choose for an architecture

unkempt rock
#

@unkempt rock that is actually called ternary abuse, and you can be arrested for it

#

lol

gray mirage
#

haha

true ridge
#

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

unkempt rock
#

I have at least learned how its problematic since learning it

true ridge
#

total of 72 occurences

gray mirage
#

I'd be OK with it if it did return None on not condition

#

that makes a lot more sense

unkempt rock
#

Same

full jay
#

72 occurrences of it not liking the None or...

true ridge
#
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
gray mirage
#

72 x if y else None

full jay
#

Ah I getcha

true ridge
#
        globs = self.curframe.f_globals if hasattr(self, 'curframe') else None
#

...

full jay
#

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

true ridge
#

@full jay can you pass me an example on assignments

full jay
#

Well like the glob = you have right there

true ridge
#

ah, I see.

full jay
#

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

true ridge
#

yea yea, I can see your point there

willow shore
#

I mean, you'd notice very quickly that this is what it does I guess

unkempt rock
#

return is spitting out a value, assignment is spitting out a value to the variable

full jay
#

But not instinctively. Certainly not at first

willow shore
#

no, but just to satisfy all this common use tho hmm

unkempt rock
#

ternary is a problem if you're actively replacing conditionals with it

willow shore
#

idk

full jay
#

Actually, I think I know why

willow shore
#

what if you could define it...

#

define the default

unkempt rock
#

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.

full jay
#

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

unkempt rock
#

inline assignments with fallback and concise logic

full jay
#

But as part of an assignment, it feels like it might not be the natural next step

peak spoke
#

they're commonly used for some sort of default values

willow shore
#

I use ternarys when it makes semantical/rhetorical sense

true ridge
#

this is very interesting vote

unkempt rock
#

ohhh

full jay
#

Kind of glad we didn't end up with the then

#

Seems like it would have been too wordy

true ridge
#

indeed

deft pagoda
#

still want do-while

unkempt rock
#

do while is when it does the thing at least once?

#

foo = rather x than y
imagine if that was your ternary syntax

deft pagoda
#

i like it

narrow kettle
#

No

full jay
#

spam = probably x i_guess y

true ridge
full jay
#

Dude, your archive is a master work

narrow kettle
#

eggs = kinda y perhaps x maybe z

full jay
#

There's so many interesting goodies in there

true ridge
#

hahaha, thanks

#

I tend to create small projects and dump them there when I'm done and no longer interested

full jay
#

It serves as interesting thought experiments, that's for sure

unkempt rock
#

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.

true ridge
#

yea, it is mainly for future. If I apply a job, at least I can show some of my example projects from there

full jay
#

Absolutely

unkempt rock
#

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.

deft pagoda
#

i've started dumping into gists more

#

i have a gist that converts numpy arrays to bach

wispy jacinth
#

To bach ?

willow shore
#

coding is always better with some bach

unkempt rock
#

I'm pretty he means the classical composer.

#

but maybe its a typo for bash.

ripe harness
#

How should you typehint *?

fossil pumice
#

technically it's a tuple.

#

but I wouldn't typehint it at all

peak spoke
#

* alone, or *args ?

grizzled vigil
#

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.

fossil pumice
#

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

grizzled vigil
fossil pumice
#

yeah, I think that's absolutely fine too

grizzled vigil
#

So I think either is probably fine, depending on if constraining the type is relevant

fossil pumice
#

totally

peak spoke
#

I would've thought * is just part of the syntax

#

does / work in a similiar fashion?

fossil pumice
#

actually for / i have no idea

#

that's probably just handled in the lexer since it's not valid in any other form.

narrow kettle
#

Callable[..., str]

#

aaand thats what i needed

dry charm
#

how do i loop in Python?

narrow kettle
#

cool

somber halo
#

hey

fossil pumice
#

@dry charm this is not a support channel.

dry charm
#

oh, ok

#

sorry

somber halo
#
  • is an iterable unpacker
fossil pumice
somber halo
#

in that context

dry charm
#

thx

fossil pumice
#

yeah, I believe the implementation is that all the args you send it get unpacked exactly like with *args but never assigned a name.

grizzled vigil
#

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

somber halo
#

just found this channel

#

or rather

#

server I mean

#

didn't expect such a big one

grizzled vigil
#

Welcome. :-)

somber halo
#

I'm thinking about starting to publish some Python stuff this year

#

namely some stuff about profiling and microbenchmarking

fossil pumice
#

didn't expect such a big one
neither did we, I think.

somber halo
#

just found this server randomly because Discord just published a public server browser

fossil pumice
#

yep, you and thousands of other people in the past few days.

narrow kettle
#

when did you make this server?

fossil pumice
somber halo
#

mike oldfied? this server keeps getting better

#

: D

#

will head on to that channel then

oblique crystal
#

what is / separator?

#

I mean I know * for unpacking or delimiting keywords args

#

neat stuff

#

but / has also some interesting thing in it?

somber halo
#

** is the one for kwargs

north root
#

/ is for delimiting positional args iirc

peak spoke
#

It's new in 3.8 for positional only args in func defs

somber halo
#
  • is for regular ones
#

it works in the same as when you intend to unpack a dictionary with **

oblique crystal
#

oh really? do you have some quick example or link?

peak spoke
#

The TypeError is also nice for them

oblique crystal
#

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

peak spoke
#

lot of builtins have positional only args but that's more of a limitation of their variable signatures afaik

fossil pumice
#

@oblique crystal

#

!pep 570

fallen slateBOT
#
**PEP 570 - Python Positional-Only Parameters**
Status

Accepted

Created

20-Jan-2018

Type

Standards Track

oblique crystal
#

yeah ok for built in but for us I mean

fossil pumice
#

I like this feature

peak spoke
#

I had a nice example but can't think of it now, the pep ought to have something through

oblique crystal
#

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

somber halo
#

typically you don't want functions/methods which accept a lot of arguments

oblique crystal
#

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

somber halo
#

hence that typical pattern of using structs with the arguments and pass that struct to a callable

oblique crystal
#

@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

somber halo
#

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

oblique crystal
#

๐Ÿคทโ€โ™‚๏ธ I think for class inits it is not really bad idea either.

somber halo
#

I've seen worse things : D

oblique crystal
#

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

somber halo
#

damn, seems overkill to me.

#

then again, might make sense in the context

oblique crystal
#

like.... I could make a class to encompas it all and pass that class object. But would it really make sense?

somber halo
#

you would have a cleaner and simpler interface, so in my book it would make sense

#

especially considering that number of arguments

oblique crystal
#

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?

somber halo
#

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

oblique crystal
#

I would say that it makes sense if that class would be not a single-purpose thing

somber halo
#

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

oblique crystal
#

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

somber halo
#

I do see that point on making things a bit less explicit through this sort of obfuscation with a configuration object

oblique crystal
#

@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

somber halo
#

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

oblique crystal
#

hm.....

grizzled vigil
#

@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)

oblique crystal
#
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
somber halo
#

yup, I usually use that one. ๐Ÿ™‚

#

could have mentioned using a dict in the first place, sorry

oblique crystal
#

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 ๐Ÿ˜ฆ

peak spoke
#

that's an odd version

oblique crystal
#

๐Ÿคทโ€โ™‚๏ธ 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

peak spoke
#

been EOL for a bit now, and 3.6 + above is where things seem to really start to pick up

oblique crystal
#

yeah. but I joined only recently so I am careful on voicing such things

#

No g-string tho hit me .

grizzled vigil
#

@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.

north root
grizzled vigil
#

Let's try to keep this channel focused on the Python language itself, text editors and IDEs are preferably discussed in #tools-and-devops.

oblique crystal
#

Thanks aeros, that's a good idea, linking that pep.

gaunt crane
#

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

flat gazelle
wispy jacinth
#

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

wide shuttle
#

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

flat gazelle
#

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

wispy jacinth
#

for exqmple i would define do do_something([0,10,2,3] and he would do_something(0,10,3,3)

flat gazelle
#

in this case the latter

#

but if that list were a variable, you would want a list arg

#

it really makes little difference

wispy jacinth
#

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

cloud crypt
#

can you not

wide shuttle
#

!tempmute 710775817302704178 1H investigating the random number drop

fallen slateBOT
#

:incoming_envelope: :ok_hand: applied mute to @unkempt rock until 2020-05-15 10:13 (59 minutes and 59 seconds).

cloud crypt
#

@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

wispy jacinth
#

Why does that guy post phone numbers

flat gazelle
#

if you are just going iterate, you do want an Iterable type hint

#

in order to allow for generators as an argument

cloud crypt
#

mhm

flat gazelle
#

you should generally not have a list type hint unless you are mutating it

#

Sequence if you need random access

wispy jacinth
#

ah didnt know

cloud crypt
#

Ah btw, are we getting aiter and anext in std any time soon? ๐Ÿ‘€

flat gazelle
#

Iterable if you do not

cloud crypt
#
async def aiter(some_object):
    return await some_object.__aiter__()

async def anext(async_iter):
    return await async_iter.__anext__()
``` I think
flat gazelle
#

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))

cloud crypt
#

indeed

#

I wonder why did you do for ... in ... something += ... instead of sum()

flat gazelle
#

I would get a recursion error

cloud crypt
#

ahhhh

#

I am blind sorry

#

builtins.sum ๐Ÿ˜‰

flat gazelle
#

also, the point is a lot less clear when I use a builtin

cloud crypt
#

indeed

#

well you were showing to * or not to *, so not sure

wispy jacinth
#

ok

pallid igloo
#

im curious why is it so rare to find users using boto3 and aioboto3

grizzled vigil
#

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.

wide shuttle
#

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

grizzled vigil
#

Haha, yeah. I still tend to talk in the third person out of habit :-)

wide shuttle
#

im curious why is it so rare to find users using boto3 and aioboto3
@pallid igloo

Those are the libaries for AWS, right?

grizzled vigil
#

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.

pallid igloo
#

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

wide shuttle
#

It looks like something I'd be using if I were to use AWS.

flat gazelle
#

try to get something to work

wide shuttle
#

I misread your sentence, I think

undone hare
#

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? lemon_pika

flat gazelle
#

you could ast.parse it and traverse it manually, crashing if a node is incorrect

molten onyx
#

Define "safely"

#

You could split on . and call getattr() in a loop

flat gazelle
#

or just

current_item = builtins
for attr in some_str.split("."):
    current_item = getattr(current_item, attr)
undone hare
#

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

molten onyx
#

This kind of sandboxing is bound to fail

flat gazelle
#

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

molten onyx
#

yeah this isn't executing any code

flat gazelle
#

I do wonder how much information you can get with just this though

willow shore
#

what kind of path? what's the context

undone hare
#

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

deft pagoda
#

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

molten onyx
#

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]

flat gazelle
#
[elem for sublist in some_it for elem in sublist]
#

or, if they are specifically lists

deft pagoda
#

i use chain.from_iterable all the time

flat gazelle
#

sum(nested_list, [])

molten onyx
#

...I don't know why I didn't think of the "nested" loop

#

sum for list is pretty questionable though duckydevil

#

I'd genuinely rather use reduce

flat gazelle
#

it is, convenient though

deft pagoda
#
def flatten(iterables):
    for iterable in iterables:
        yield from iterable
list(flatten(my_stuff))
molten onyx
#

yeah I guess a flatten function would also be a solution

#

Python needs a builtin flatmap to be honest

flat gazelle
#

I mean, a nested listcomp is close

undone hare
#
list(itertools.chain(*some_it))```
flat gazelle
#

having something like [*(2, 4) for _ in range(10)] would be neat though

deft pagoda
#
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

flat gazelle
#

ye, you can use a full tree traversal algo for that

hasty thistle
#

wouldn't you use more_itertools.flatten if you're using more_itertools anyways?

deft pagoda
#

it breaks

hasty thistle
#

(though there do seem to be some small differences)

deft pagoda
#
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

molten onyx
#

Python's iteration model doesn't really lend itself too-too nicely to composing operations

hasty thistle
#

ah right, it breaks if it's not nested

flat gazelle
#

that is nice

molten onyx
#

oh hey bisection

deft pagoda
#

yeah i used bisection

molten onyx
#

That repr is very funky

#

Though it makes sense

deft pagoda
#
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
molten onyx
#

I have no idea what's going on in that __getitem__

deft pagoda
#

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

molten onyx
deft pagoda
#

i guess i just assume disjoint intervals here

oblique crystal
#

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

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

potent shell
#

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...

flat gazelle
#

extend works with any iterable

#

+= only works with lists

oblique crystal
#

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

molten onyx
#

extend would raise if it were not iterable

oblique crystal
#

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

flat gazelle
#

so that (4 + 3) * 6 is not (7, 7, 7, 7, 7, 7)

oblique crystal
#

oh, never though of it. Makes sense

sacred tinsel
#

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

undone hare
#

Parens are just a delimiter for the parser, they are thrown away and doesnโ€™t end up in the parse tree iirc

oblique crystal
#

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 ()

potent shell
#

so python print("Like this")

oblique crystal
#

it can be just comma separated values

#

yes

sacred tinsel
#

() makes an empty tuple

oblique crystal
#

oh lol ok

sacred tinsel
#

which I hate

flat gazelle
#

it is hard to find anything better though

sacred tinsel
#

you can't make a tuple with an empty tuple in it via (())

undone hare
#

Thatโ€™s weird, who wants an empty tuple?

sacred tinsel
#

that needs the comma

#

yeah I don't know, I'd just do tuple()

wide shuttle
#

because it's not empty!

#

(1) won't make a tuple either

oblique crystal
#

that is what started the converstation ๐Ÿ™‚

wide shuttle
#

I know

#

I read it

#

I'm just messing with our wizard

flat gazelle
#

singleton tuples are also a bit off

sacred tinsel
#

the wizard does not like to be messed with

oblique crystal
#

I mean, we just looped back to the fact that it feels like there is some loss of consistency here

flat gazelle
#

(8,) is weird

undone hare
#

It makes sense though, and make parsing way easier

flat gazelle
#

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

oblique crystal
#

afaik in many sql libs when you insert values you need to have that kind of things (8,)

wide shuttle
#

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

undone hare
#

I guess (,1) doesnโ€™t work, does it? Nope

wide shuttle
#

For convenience, though, we allow dropping the final comma if it leads to an unambiguous expression

#

So, we get

#

()
(1,)
(1, 2)

#

voila

undone hare
#

It does make sense to have some trailing commas in some cases

wide shuttle
#

and it's allowed

#

!e print((1, 2, ))

fallen slateBOT
#

@wide shuttle :white_check_mark: Your eval job has completed with return code 0.

(1, 2)
undone hare
#

Like if you have one element per line and keep the trailing comma, git will generate better patches and less conflicts

flat gazelle
#

personally, I do not like commas much for enumerations, I like the lisp way better. I do understand it is needed for parsing

potent shell
#

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?

flat gazelle
#

but still

peak spoke
#

and it's nicer to work with in general with trailing commas and elements on individual lines

undone hare
#

How does lisp enums works?

flat gazelle
#

ah, no. I just meant enumeration as a list

#

you just do '(1 2 3 5 8 9 6) (quote optional depending on context)

peak zephyr
#

lots of idiotic stupid parenthesis

undone hare
#

Well, thatโ€™s a bit better indeed

flat gazelle
#

meh, with a good editor, parens are fine

oblique crystal
#

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

flat gazelle
#

Raku kind of has something similar with angle brackets

<1 2 3 5 6> >>+>> 8
(9 10 11 13 14)
``` `>>+>>` being a hyper operator
wide shuttle
#

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

sacred tinsel
#

wait, when does dropping the trailing comma lead to ambiguity? only when there is exactly 1 element, right?

oblique crystal
#

I think yes

undone hare
#

I think so

wide shuttle
#

1, -> 1

grave jolt
#

Maybe (,) would be better?

wide shuttle
#

But that's not consistent

#

That's adding a comma

oblique crystal
#

no cause one comma per actual elemtn

sacred tinsel
#

I'd just not have a literal for an empty tuple

wide shuttle
#

That's two exceptions for a zero-length tuple

undone hare
#

Or just use tuple(), and make () be.. I donโ€™t know actually

wide shuttle
#

I honestly go back and forth between hating () and thinking it's okay

#

You can see it in my code

sacred tinsel
#

if the empty set needs no literal, neither does the tuple

grave jolt
#

Maybe there's an inherent problem in using the same characters for specifying the order of operations and a tuple.

wide shuttle
#

I think they added it because the tuple is very fundamental to Python

grave jolt
#

{*()}

undone hare
#

Well, if the () doesnโ€™t yield a tuple anymore, what would it be? SyntaxError?

sacred tinsel
#

yeah, but an empty one?

#

it's inconsistent with the idea of comma being what creates a tuple

#

creates a weird exception

deft pagoda
#
for {}[()] in '...':
    print('Hello Satan.')
wide shuttle
#

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

oblique crystal
#

how long it has been a thing?

wide shuttle
#

Anyway, this is just framing the grammar

undone hare
#

I think this was here for quite long

wide shuttle
#

it probably had nothing to do with the original decision

undone hare
#

Probably when the tuple was created

deft pagoda
#

i think the 'comma creates a tuple' expression is kinda stupid

oblique crystal
#

well comma is what tell interperter it's a tuple

deft pagoda
#

i can create long tuples without any commas

#
tuple(range(5))
true ridge
#

We're talking about the grammar itself

undone hare
#

Well, tuple() is actually slower than () ๐Ÿ˜„

oblique crystal
#

but here you use constructor explicitly

wide shuttle
#

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

oblique crystal
#

so empty tuple is always in memory?

wide shuttle
#

but it's interesting to see what's going on under the hood

undone hare
#

Yup

wide shuttle
#

I don't know

deft pagoda
#
In [122]: past('tuple(); ()')
Expr
โ•ฐโ”€โ”€Call
   โ•ฐโ”€โ”€Name
      โ”œโ”€โ”€tuple
      โ•ฐโ”€โ”€Load
Expr
โ•ฐโ”€โ”€Tuple
   โ•ฐโ”€โ”€Load
#

neat

oblique crystal
#

same id

#

despite the way it's created

undone hare
#

I think it is just caching, the same way ints works

wide shuttle
#

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

undone hare
#

[] also doesnโ€™t call a function

oblique crystal
#

so you will always get True if you use is to compare empty tuples

placid citrus
#

print('Hello everyone')

deft pagoda
#

dict doesn't have a load

#
In [123]: past('dict(); {}')
Expr
โ•ฐโ”€โ”€Call
   โ•ฐโ”€โ”€Name
      โ”œโ”€โ”€dict
      โ•ฐโ”€โ”€Load
Expr
โ•ฐโ”€โ”€Dict
#

or my printer is messed up

undone hare
#

[] calls BUILD_LIST, interesting

sacred tinsel
#

wait what

oblique crystal
#

and {} calls build_map

sacred tinsel
#
>>> () is ()
<stdin>:1: SyntaxWarning: "is" with a literal. Did you mean "=="?
True
wide shuttle
#

that's not always a good way to check

#

!e

print((1,) is (1,))
a = (1,)
b = (1,)
print(a is b)
fallen slateBOT
#

@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
wide shuttle
#

o hwait

#

difference between 3.7 and 3.8

#

nice

sacred tinsel
#

did not foresee this happening

true ridge
#

@deft pagoda only things that can be used as assignment have an expr context (load/store etc.)

wide shuttle
#
>>> (1,) is (1,)
True
>>> a = (1,)
>>> b = (1,)
>>> a is b
False

on 3.7

peak spoke
#

(c)python keeps a lot of values around, latin1 chars etc. too

true ridge
#

Dict/Set displays can't be used to assign, so there is no need for them to contain a context

peak spoke
#

Ves that's the difference of fully compiled and compiled individually in the repl

oblique crystal
#

difference between 3.7 and 3.8
hm?

true ridge
#

@wide shuttle try inspect co_consts

peak spoke
#

can do

exec("a=1,")
exec("b=1,")
print(a is b)
###
False
true ridge
#

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

wide shuttle
#

Yeah, i figured

#

related to the optimization of constant lists to tuples

#

e.g. for i in [1, 2, 3]

true ridge
#

It does that on sets too, when all elements are constant and only used in contain operation

peak spoke
#

python optimizes where it can for literals

true ridge
#

it makes sets frozen

deft pagoda
#

does that shrink a set

true ridge
#

โ„๏ธ โ„๏ธ โ„๏ธ

oblique crystal
#

are frozen sets faster?

true ridge
#

Yes

deft pagoda
#

i should probably be freezing more sets

peak spoke
#

And immutable

oblique crystal
#

so it's like tuple/list

true ridge
#

I really love the term of freezing, frozen sets, frozen dataclasses

oblique crystal
#

I never used I think frozen sets

#

or any froze dataclasses

fresh warren
#

hey soo 'is' comparison is checking to see if it is the same object

deft pagoda
#

i've used frozen dataclasses

#

and frequent __slots__

sacred tinsel
#

I've used frozen dataclasses once

#

probably should have used namedtuples instead, lol

deft pagoda
#

i think i like dataclasses more

sacred tinsel
#

I use typing.NamedTuple a lot

#

it's like ice cream - really nice

deft pagoda
#

yeah, thats a nice one

fresh warren
#

a ='aa'
b='aa'
a==b //this is true
a is b // this is also true

So python reuses strings it previously created?

unkempt rock
#

hi

sacred tinsel
#

hey

oblique crystal
#

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

deft pagoda
#

yeah, but you get hashes

peak spoke
#

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

undone hare
#

Well, namedtuples are cool, but they are tuples, they donโ€™t serve the same purpose as dataclasses

wide shuttle
#

a ='aa'
b='aa'
a==b //this is true
a is b // this is also true

So 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)
fallen slateBOT
#

@wide shuttle :white_check_mark: Your eval job has completed with return code 0.

001 | True
002 | False
wide shuttle
#

That's why using is with literals now gives a warning in Python 3.8

fresh warren
#

Oh i see thats interesting

wide shuttle
#

The implementation/optimization details can muddy the water a bit

fresh warren
#

I guess another way is to create a wrapper class around string if u wanna force it to be a new obj

wide shuttle
#

The idea is that you should never have to

#

Strings are immutable

fresh warren
#

makes sense

oblique crystal
#

all is interesting, but I really have to go back to my work. I wish sometimes I could self-ban from this channels ๐Ÿ˜‚

undone hare
#

Well, just close the window ๐Ÿ˜‰

cloud crypt
#

okay now I am thinking

sacred tinsel
#

Dataclasses have a __post_init__ which is super cool

cloud crypt
#
>>> 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

deft pagoda
#

i wish meta classes had a post_init

unkempt rock
#

@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

deft pagoda
#

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

unkempt rock
#

can someone be my friend please lmao

#

@deft pagoda show me an example ๐Ÿ˜™

deft pagoda
#

!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)
fallen slateBOT
#

@deft pagoda :white_check_mark: Your eval job has completed with return code 0.

10 25 40
final whale
#

neato

unkempt rock
#

nice

deft pagoda
#

!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!')
fallen slateBOT
#

@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!
deft pagoda
#

similar trick here

flat gazelle
#
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?
deft pagoda
#

you need to () afterwards

flat gazelle
#

still nothing

deft pagoda
#

oh, if you're using my Enum, you can't use that constructor

#

it bypasses __prepare__

flat gazelle
#

ah

#

so you would have to call it manually?

deft pagoda
#

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

flat gazelle
#

ye, it is quite nice

deft pagoda
#

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

undone hare
deft pagoda
#

i started to brainstorm ways to use properties instead of metas, but the meta solution seemed easier

grave jolt
#

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]

deft pagoda
#

with a pretty sophisticated dict-like from __prepare__

grave jolt
#

What is __prepare__ used for?

#

Hm

#

@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)
fallen slateBOT
#

@grave jolt :white_check_mark: Your eval job has completed with return code 0.

DeferClass(name='A')
grave jolt
#

Maybe add some descriptor magic?

karmic abyss
#

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

flat gazelle
#

because the iterators work differently internally

karmic abyss
#

Both are bad practices - mutating a collection while iterating, but only one of those two exmaples causes an error

flat gazelle
#

the list iterator just goes index by index afaik

karmic abyss
#
# i mean this shold cause an error, but doesn't
x = [1,2,3,4]
for i in x:
  x.pop()
valid roost
#

how come I can't do ```py
from time import time
text = time() #this

#

actually nvm

flat gazelle
#

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

grave jolt
#

@karmic abyss

flat gazelle
#

python will happily let you stab yourself

karmic abyss
#

Yea -- thanks @grave jolt that is what I am looking for

gusty solar
#

s, x = y i have seen something like this in some codes, what does the , is for

#

and what that means?

grave jolt
#

!e

colors = ["red", "green", "blue"]
color1, color2, color3 = colors
print(color1)
print(color2)
print(color3)
fallen slateBOT
#

@grave jolt :white_check_mark: Your eval job has completed with return code 0.

001 | red
002 | green
003 | blue
gusty solar
#

o, o ok thx

unkempt rock
#

I find that mechanic helpful

flat gazelle
#

you can also use * to take multiple elements

zero, *others, four = range(5)
#

others is then a list of 1 2 3

deft pagoda
unkempt rock
#

a person was delete my msh

#

why

#

...

shy vine
#

What does this have to do with Python?

wide shuttle
#

@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

undone hare
#

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

wide shuttle
#

but those objects themselves don't support that interface

undone hare
#

But all objects are using dicts under the hood, aren't they?

wide shuttle
#

My bike has wheels, but it's not a wheel itself

undone hare
#

But it has a wheel attribute that refer to its actual two wheels

wide shuttle
#

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

undone hare
#

Well, it has some attributes that are mapping already

#

So we could dump those in the cache, and they will be.. Cached, I guess

wide shuttle
#

But I think we've meandered away from the original conversation about ABCs

undone hare
#

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

boreal umbra
#

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)

gilded crow
#

Can someone give me code to convert this epoch to date

#

1589490297436

#

Time not required

#

Date in YYYYMMDD format

unkempt rock
#

what have you tried

peak spoke
#

You can use the .fromtimestamp methods, but not really the right channel for this

north root
#

this isn't the channel for this. try asking in a help channel instead

gilded crow
#

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

slender plover
#

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?

peak spoke
#

Their syntax is restricted to expressions

#

but realistically you only want to use them for simple stuff in certain situations

slender plover
#

Like a quick print statement?

narrow kettle
#

thats not an expression

#

thats a statement

slender plover
#

Oh

#

So they can only do math

narrow kettle
#

python lambdas are very restricted compared to other langs

#

well no

#

it can be any expression

slender plover
#

Oh!

#

So python lambdas are the big dumb compared to other langs

narrow kettle
#

idk if big dumb is the right word

#

but they are certainly more restricted

#

you cant use them as say an event callback

slender plover
#

๐Ÿ˜† it is what it is

peak spoke
#

print is a function call in python 3, which is an expression

narrow kettle
#

but you cant have more then one afaik

flat gazelle
#

you can with some creativity

#

but at point you are misusing a lambda

narrow kettle
#

in a tuple

#

lul

flat gazelle
#

or with chaining or

narrow kettle
#

basically lambdas are very restricted

peak spoke
#

most of the usage lambdas get are the key arguments for min/max/sort etc. or linking callbacks in things like GUI modules

narrow kettle
#

python doesnt want you using them like you would in another lang

#

they arent really closures

grave jolt
#

Aren't they? All functions in python are closures.

unkempt rock
#

what are closures?

grave jolt
#

!e

def f():
    x = 1
    def g(): return x
    x = 2
    return g

print(f()())
fallen slateBOT
#

@grave jolt :white_check_mark: Your eval job has completed with return code 0.

2
grave jolt
#

!e

def f():
    x = 1
    g = lambda: x
    x = 2
    return g

print(f()())
fallen slateBOT
#

@grave jolt :white_check_mark: Your eval job has completed with return code 0.

2
unkempt rock
#

cool

unkempt rock
#
>>> 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

wide shuttle
#

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>,)
grave jolt
#

!e

def function():
    def g(): print(a)
    a = "hello"
    return g
g = function()
print(g.__code__.co_freevars)
fallen slateBOT
#

@grave jolt :white_check_mark: Your eval job has completed with return code 0.

('a',)
unkempt rock
#

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?

grave jolt
#

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)
fallen slateBOT
#

@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,) {}
grave jolt
#

Here decorated has access to fn which is local in log's scope.

unkempt rock
#

right.

#

I see the application of that

grave jolt
#

!e

# Here's a gotcha:
adders = [(lambda x: x + a) for a in range(10)]
print(adders[3](7))
fallen slateBOT
#

@grave jolt :white_check_mark: Your eval job has completed with return code 0.

16
grave jolt
#

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
unkempt rock
#

Wow, thats is a module i have to learn what its doing

grave jolt
#

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

unkempt rock
#

I'm going to have to play with using lambda as the left most statement in a listcomp

grave jolt
#

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.

unkempt rock
#

right

grave jolt
#

In this regard, map might be better:

#

!e

adders = list(map(lambda a: lambda x: x + a, range(10)))
print(adders[3](7))
fallen slateBOT
#

@grave jolt :white_check_mark: Your eval job has completed with return code 0.

10
grave jolt
#

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
unkempt rock
#

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?

wide shuttle
#

A lot of solutions could very well just use the lazy object instead of a list

grave jolt
#
adders = map(lambda a: lambda x: x + a, range(10))
wide shuttle
#

I see a lot of people just using lists because that's what they're familiar with

unkempt rock
#

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.

grave jolt
#

You can try to implement your versions of map, filter, reduce (aka foldl), rfold, chain and count as an exercise.

unkempt rock
#

sounds like a good idea

grave jolt
#

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.

unkempt rock
#

yeah I have gathered, yet still Id like to be familiar with them anyway.

grave jolt
#

But that depends on your tastes and the particular problem.

unkempt rock
#

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.

grave jolt
#

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
flat gazelle
#

(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.)

grave jolt
#

Oh, that's true

flat gazelle
#

there is also splat <*>, but that would not be too useful with python

grave jolt
#

Changed the example, should be correct now.

#

That's not a technicality at all.

unkempt rock
#

is that Maybe and Just built in or imported?

gray mirage
#

Are you guys trying to make Pyskell again?

grave jolt
#

@unkempt rock No, Maybe is a general concept that exists under different names in some languages.

unkempt rock
#

I see.

grave jolt
#

I don't know if it's a natural addition to python, but you can still implement it as an exercise.

unkempt rock
#

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

narrow kettle
#

Are you guys trying to make Pyskell again?

Tell me more ๐Ÿ‘€

grave jolt
#

Tss

narrow kettle
#

Tbh thatโ€™s not a bad idea, Iโ€™ve been looking for a cool idea for a language

grave jolt
#

aka Shh

flat gazelle
#

dogelang

narrow kettle
#

A python syntax functional Lang would be dope

#

It prob exists already tho

grave jolt
#

Dogelang is the other way around, which is a bit silly

flat gazelle
#

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

grave jolt
#

๐Ÿšด <=<๐Ÿšด

#

Well, Elixir is a ruby-syntaxed functional language, maybe the same could be done for python.

narrow kettle
#
def traverse_list(list):
         []: "done"
         [h:t]: traverse_list(t)
#

Iโ€™m intrigued

grave jolt
#

Well, you could transpile something like that into Python.

short kernel
#

But i can't see there selenium

narrow kettle
#

Tbh Iโ€™ve been wanting to write a new language

grave jolt
#
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)
narrow kettle
#

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

grave jolt
#

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

narrow kettle
#

Thereโ€™s a couple ways i could

grave jolt
#

It also has the python grammar as an example

narrow kettle
#

Python would be cool, kinda wanna learn rust

grave jolt
#

Compiler as a tutorial project ๐Ÿ‘

narrow kettle
#

Lol Iโ€™m a masochist

grave jolt
#

I see

gray mirage
#

Don't forget to check out Hylang

grave jolt
#

A python syntax functional Lang would be dope gave me a hint

narrow kettle
#

Transpiring to python would be easier then targeting say llvm

grave jolt
#

true

#

After transpiling, you can also use pypy3 for performance

#

Since writing a JIT is probably not an easy task.

narrow kettle
#

Ya donโ€™t really wanna touch that lol

grave jolt
#

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]
])
narrow kettle
#

Thatโ€™s so ugly

#

I@lvoe it

#

Love

grave jolt
#

Yeah, it's a bit ugly...

flint sapphire
#

interesting

grave jolt
#

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.

unkempt rock
#

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

grave jolt
#

I don't think it's implemented in python

#

Looked it up, It's not

peak spoke
#

none of the builtins are

grave jolt
#

Makes sense, that's why they are built-ins

unkempt rock
#

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?

peak spoke
#

That's probably a generated stub file

#

range is a neat class that some people may not think is one

unkempt rock
#

Probably almost this entire file is nothing but methods that say pass

grave jolt
#

Yes, it's s stub

#

probably has a .pyi extension

unkempt rock
#

mm

#

uh oh i dont know c

narrow kettle
#

Hmmm C

#

Makes brain fuck look elegant

grave jolt
#

Well, you can try to make sense of it.

peak spoke
#

I'd expect a proper stub to use ... and define the types

unkempt rock
#

I see what you mean now.

#

The builtins are in C.

#

I didnt realize that.

grave jolt
#

Do you know any languages other than Python?

#

Oh wow, they used goto

peak spoke
unkempt rock
#

JavaScript. HTML, CSS.

grave jolt
#

Well, C syntax is closer to JS. Just pretend it's JS.

#

But with static types.

unkempt rock
#

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

peak spoke
#

the overload is just for typing

unkempt rock
#

oh ok

#

there are six definitions

peak spoke
#

(stub files are to help ides etc. get the types for you)

unkempt rock
#

Right

#

Oh its still a stub

grave jolt
#

CPython is not the only implementation. There's also PyPy, written (previously, I think) in Python

unkempt rock
#
>>> 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]

grave jolt
#

You can use zip

unkempt rock
#

yeeeeah

grave jolt
#

||```py
def my_map(func, *iterables):
mapped = []
arg_tuples = zip(iterables)
for args in arg_tuples:
mapped.append(func(*args))
return mapped

unkempt rock
#

yeah also the possibility of multiple args was the next problem

grave jolt
#

Also, map doesn't return a list.

#

It returns a generator-like object.

flat gazelle
#

You would need yield for that

unkempt rock
#

I'm not so good with how to write those but I know that you do that./

grave jolt
#
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.

unkempt rock
#

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

grave jolt
#

[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__)
fallen slateBOT
#

@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.
grave jolt
#

Are different quotes in nil and ... intentional?

true ridge
#

looks like it is left from 1998

unkempt rock
#
>>> 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.
flat gazelle
#

Oh, there is a mistake there. The args to zip should be unpacked

unkempt rock
#

hi

#

i need help

#

can i use python to discover vulnerabilities

#

?

flat gazelle
#

If you know where to look for them, python is a solid tool to assist with that

grave jolt
#

@flat gazelle fixed

unkempt rock
#

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

north root
#

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?

flat gazelle
#

||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?||

grave jolt
#

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.

unkempt rock
#

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

grave jolt
#

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.

unkempt rock
#

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?

north root
#

what about the nor gate!

#

yeah if either is False, then it returns True for nand

oblique crystal
#

There's also xor.. I think it's also one that alow to build any other

grave jolt
#

I don't think so.

unkempt rock
#

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?

grave jolt
#

How do you make OR with XORs?

unkempt rock
#

instad of <generator blah blah at 0x7dsa7fsdf67889>

grave jolt
#

Well, yes, it would defeat the purpose.

flat gazelle
#

Haskell can do it, why can't python

grave jolt
#

You could make your own generator wrapper that would print a few first values.

unkempt rock
#

I just pass it into list() but sometimes it doesnt work

grave jolt
#

You can't always print everything in the generator.

unkempt rock
#

yeah

grave jolt
#

!e

def zero():
    while True:
        yield 0
print(list(zero()))
fallen slateBOT
#

@grave jolt :warning: Your eval job timed out or ran out of memory.

[No output]
grave jolt
#

wtf

unkempt rock
#

isnt that an infinite generator of 0s?

grave jolt
#

Oh

#

I didn't read the message

#

I just read [no output]

unkempt rock
#

right

grave jolt
#

@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.

white spade
#

ok

unkempt rock
#
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

grave jolt
#

__repr__ returns a string

#

without printing it

unkempt rock
#

ah right thats why it errors

#

its returning none

grave jolt
#

!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}")
fallen slateBOT
#

@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([])
unkempt rock
#

damn

grave jolt
#

This version allows you to 'peek' into the generator while preserving all the elements.

#

god dammit, forgot the )

unkempt rock
#

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

grizzled vigil
#

@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).

grave jolt
#

I have considered that, since pop(0) makes each __next__ O(k) where k is the cache size

unkempt rock
#

I like a lot of things about this code.

grave jolt
#

I should've mentioned that, thanks, @grizzled vigil

#

To be fair, I never used deque before.

unkempt rock
#

Ive never added dunder methods to something that made it iterable before so I didnt think of them as obvious

grave jolt
#

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}")
fallen slateBOT
#

@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([])
grave jolt
#

IterablePeeker v2, now with ) and deque!

unkempt rock
#

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

grave jolt
#

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.

unkempt rock
#

mm

#

Also your use of map to stringify everthing in the cache is a pretty useful way of using map.

grave jolt
#

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())
fallen slateBOT
#

@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
grave jolt
#

makes sense

deft pagoda
#

maybe don't have a the 3 hard coded, but as another parameter

grave jolt
#

That's a good idea.

#

It was just an example, though.

deft pagoda
#

with a default of 3

unkempt rock
#

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

deft pagoda
#

also, having try, except used for control flow is perfectly fine for python, i don't see anything wrong with it

somber halo
#

on other languages it's typically not a good idea

#

however, in the Python world it's kind of accepted for that purpose

unkempt rock
#

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.

somber halo
#

personally, I avoid try/except clauses for control flow despite the acceptance of it with Pythonistas

unkempt rock
#

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.

grave jolt
#

!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}")
fallen slateBOT
#

@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

grizzled vigil
#

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.

grave jolt
#

It's getting kinda long, Maybe I should've posted it on gist.

somber halo
#

I'm definitely in the camp of LBYL.

unkempt rock
#

Its beautiful to see someone post code and then another person improve and then another person

grave jolt
#

It's called git :)

unkempt rock
#

yeah haha