Re: on writing a while loop for rolling two dice

2021-08-29 Thread Peter Otten

On 28/08/2021 14:00, Hope Rouselle wrote:


def how_many_times():
   x, y = 0, 1
   c = 0
   while x != y:
 c = c + 1
 x, y = roll()
   return c, (x, y)
--8<---cut here---end--->8---

Why am I unhappy?  I'm wish I could confine x, y to the while loop.  The
introduction of ``x, y = 0, 1'' must feel like a trick to a novice.  How
would you write this?  Thank you!


I'd probably hide the while loop under the rug:

>>> import random
>>> def roll_die():
while True: yield random.randrange(1, 7)


Then:

>>> def hmt():
for c, (x, y) in enumerate(zip(roll_die(), roll_die()), 1):
if x == y:
return c, (x, y)


>>> hmt()
(1, (2, 2))
>>> hmt()
(4, (4, 4))
>>> hmt()
(1, (5, 5))


OK, maybe a bit complicated... but does it pay off if you want to 
generalize?


>>> def roll_die(faces):
while True: yield random.randrange(1, 1 + faces)

>>> def hmt(faces, dies):
for c, d in enumerate(zip(*[roll_die(faces)]*dies), 1):
if len(set(d)) == 1: return c, d


>>> hmt(10, 1)
(1, (2,))
>>> hmt(10, 2)
(3, (10, 10))
>>> hmt(10, 3)
(250, (5, 5, 5))
>>> hmt(1, 10)
(1, (1, 1, 1, 1, 1, 1, 1, 1, 1, 1))

You decide :)

--
https://mail.python.org/mailman/listinfo/python-list


Re: on the popularity of loops while and for

2021-08-29 Thread Barry


> On 28 Aug 2021, at 22:42, Hope Rouselle  wrote:
> 
> I'd like get a statistic of how often each loop is used in practice.  
> 
> I was trying to take a look at the Python's standard libraries --- those
> included in a standard installation of Python 3.9.6, say --- to see
> which loops are more often used among while and for loops.  Of course,
> since English use the preposition ``for'' a lot, that makes my life
> harder.  Removing comments is easy, but removing strings is harder.  So
> I don't know yet what I'll do.
> 
> Have you guys ever measured something like that in a casual or serious
> way?  I'd love to know.  Thank you!

I am interesting in why you think that choice of while vs. for is about 
popularity?

Surely the choice is made in most cases by the algorithm?
If you have an iterator then you use for.
Of course you can use while instead of for but in code review that will get 
queried.

Barry



> -- 
> https://mail.python.org/mailman/listinfo/python-list

-- 
https://mail.python.org/mailman/listinfo/python-list


Re: on writing a while loop for rolling two dice

2021-08-29 Thread dn via Python-list
On 29/08/2021 20.06, Peter Otten wrote:
...
> OK, maybe a bit complicated... but does it pay off if you want to
> generalize?
> 
 def roll_die(faces):
> while True: yield random.randrange(1, 1 + faces)
> 
 def hmt(faces, dies):
> for c, d in enumerate(zip(*[roll_die(faces)]*dies), 1):
>     if len(set(d)) == 1: return c, d


Curiosity:
why not add dies as a parameter of roll_die()?

Efficiency:
- wonder how max( d ) == min( d ) compares for speed with the set() type
constructor?
- alternately len( d ) < 2?
- or len( d ) - 1 coerced to a boolean by the if?
- how much more efficient is any of this (clever thinking!) than the
OP's basic, simpler, and thus more readable, form?

English language 'treachery':
- one die
- multiple dice
(probably not followed in US-English (can't recall), particularly on
computers running the Hollywood Operating System).

Continuous Education:
Thanks for the reminder that enumerate() can be seeded with a "start" value!
-- 
Regards,
=dn
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: on writing a while loop for rolling two dice

2021-08-29 Thread Chris Angelico
On Sun, Aug 29, 2021 at 8:14 PM dn via Python-list
 wrote:
> Efficiency:
> - wonder how max( d ) == min( d ) compares for speed with the set() type
> constructor?

That may or may not be an improvement.

> - alternately len( d ) < 2?
> - or len( d ) - 1 coerced to a boolean by the if?

Neither of these will make any notable improvement. The work is done
in constructing the set, and then you're taking the length. How you do
the comparison afterwards is irrelevant.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: on writing a while loop for rolling two dice

2021-08-29 Thread Peter Otten

On 29/08/2021 12:13, dn via Python-list wrote:

On 29/08/2021 20.06, Peter Otten wrote:
...

OK, maybe a bit complicated... but does it pay off if you want to
generalize?


def roll_die(faces):

 while True: yield random.randrange(1, 1 + faces)


def hmt(faces, dies):

 for c, d in enumerate(zip(*[roll_die(faces)]*dies), 1):
     if len(set(d)) == 1: return c, d



Curiosity:
why not add dies as a parameter of roll_die()?


Dunno. Maybe because I've "always" [1] wanted a version of 
random.randrange() that generates values indefinitely. It would need to 
check its arguments only once, thus leading to some extra



Efficiency:
- wonder how max( d ) == min( d ) compares for speed with the set() type
constructor?


I did the simplest thing, speed was not a consideration. If it is, and 
dies (sorry for that) is large I'd try


first = d[0]
all(x == first for x in d)  # don't mind one duplicate test

For smaller numbers of dice I'd unpack (first, *rest) inside the for 
loop. But it's a trade-off, you' have to measure if/when it's better to 
go through the whole tuple in C.




- alternately len( d ) < 2?
- or len( d ) - 1 coerced to a boolean by the if?
- how much more efficient is any of this (clever thinking!) than the
OP's basic, simpler, and thus more readable, form?


It really isn't efficiency, it's a (misled?) sense of aesthetics where 
I've come to prefer


- for-loops over while, even when I end up with both to get the desired for

- enumerate() over an explicit counter even though there is the extra 
unpack, and you still need to initialize the counter in the general case:


for i, item in enumerate([]): pass
print(f"There are {i+1} items in the list.")  # Oops


English language 'treachery':
- one die
- multiple dice


You might have inferred that I knew (or had looked up) the singular of 
dice, so this is but a momentary lapse of reason. It hurts me more than 
you, trust me. Not as much, as going on record with confusing they're 
and their, but still ;)



(probably not followed in US-English (can't recall), particularly on
computers running the Hollywood Operating System).


I've come to the conclusion that International English is hopelessly and 
inevitably broken. That's the price native speakers have to pay for 
having they're (oops, I did it again!) language used as lingua franca.



Continuous Education:
Thanks for the reminder that enumerate() can be seeded with a "start" value!


[1] I think I've suggested reimplementing the whole module in terms of 
generators -- can't find the post though.


--
https://mail.python.org/mailman/listinfo/python-list


code to initialize a sequence

2021-08-29 Thread joseph pareti
In the code attached below, the A-variant is from somebody else who knows
Python better than I. But I do not like to just use any code without having
a grasp, specifically the line in* bold*, so I wrote the B-variant which
gives the same results. The C-variant is identical to A and is there for
verification: after resetting the seed I expect the same sequence. The
D-variant is closer to the way I code, and it does not work.


import random
from random import randint, seed

def generate_sequence(length, n_unique):
*return [randint(0, n_unique-1) for k in range(length)]*

def generate_sequence_JP(length, n_unique):
   LI = []
   for k in range(length):
 LI.append(randint(0, n_unique-1))
   return(LI)
def generate_sequence_EXPLICIT(length, n_unique):
   X =[None] * length
  for i in range(length):
X[i] = [randint(0, n_unique-1)]
   return X
#
# MAIN PROGRAM
#
random.seed(2)
A = generate_sequence(4, 10 )
random.seed(2)
B = generate_sequence_JP(4, 10)
random.seed(2)
C = generate_sequence(4, 10 )
random.seed(2)
D = generate_sequence_EXPLICIT(4, 10 )
print(A)
print(type(A))
print('-')
print(B)
print(type(B))
print('-')
print(C)
print(type(C))
print('-')
print(D)
print(type(D))


Regards,
Joseph Pareti - Artificial Intelligence consultant
Joseph Pareti's AI Consulting Services
https://www.joepareti54-ai.com/
cell +49 1520 1600 209
cell +39 339 797 0644
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: code to initialize a sequence

2021-08-29 Thread Peter Otten

On 29/08/2021 20:44, joseph pareti wrote:

In the code attached below, the A-variant is from somebody else who knows
Python better than I. But I do not like to just use any code without having
a grasp, specifically the line in* bold*, so I wrote the B-variant which
gives the same results. The C-variant is identical to A and is there for
verification: after resetting the seed I expect the same sequence. The
D-variant is closer to the way I code, and it does not work.


So you do you want us to debug the _EXPLICIT version?
The assignment

>  X[i] = [randint(0, n_unique-1)]

creates a list with one element and turns it into an item in the list X.
You don't want  a list, you want the numerical value, the 
straight-forward way to achieve that being


X[i] = randint(0, n_unique-1)

An alternative is to assign to a slice

X[i:i+1] = [randint(...)]

but that would only make sense if the right-hand-side list weren't 
created in the line and ditched immediately afterwards.





import random
from random import randint, seed

def generate_sequence(length, n_unique):
*return [randint(0, n_unique-1) for k in range(length)]*


The above is the most pythonic of the three versions. Once you 
understand how for loops with a list.append() are turned into 
comprehensions it will be easy to write and read that style. Definitely 
worth learning and adopting.




def generate_sequence_JP(length, n_unique):
LI = []
for k in range(length):
  LI.append(randint(0, n_unique-1))
return(LI)



This is also fine and often used when the loop body is a bit more 
complex, but



def generate_sequence_EXPLICIT(length, n_unique):
X =[None] * length
   for i in range(length):
 X[i] = [randint(0, n_unique-1)]
return X


this is garbage even when it works, usually indicative of premature 
optimization.


Random (yeah!) remark: Python uses half-open intervals (i. e. intervals 
that include the lower bound, but not the upper bound) almost 
everywhere. randint() is one exception.

Personally I prefer its conformist sister randrange(); with that
randint(0, n_unique-1)
becomes
randrange(n_unique)


#
# MAIN PROGRAM
#
random.seed(2)
A = generate_sequence(4, 10 )
random.seed(2)
B = generate_sequence_JP(4, 10)
random.seed(2)
C = generate_sequence(4, 10 )
random.seed(2)
D = generate_sequence_EXPLICIT(4, 10 )
print(A)
print(type(A))
print('-')
print(B)
print(type(B))
print('-')
print(C)
print(type(C))
print('-')
print(D)
print(type(D))


Regards,
Joseph Pareti - Artificial Intelligence consultant
Joseph Pareti's AI Consulting Services
https://www.joepareti54-ai.com/
cell +49 1520 1600 209
cell +39 339 797 0644




--
https://mail.python.org/mailman/listinfo/python-list


Re: Making command-line args available to deeply-nested functions

2021-08-29 Thread George Fischhof
Loris Bennett  ezt írta (időpont: 2021. aug.
26., Cs, 16:02):

> George Fischhof  writes:
>
> [snip (79 lines)]
>
> >> > Hi,
> >> >
> >> > Also you can give a try to click and / or  typer packages.
> >> > Putting args into environment variables can be a solution too
> >> > All of these depends on several things: personal preferences,
> colleagues
> >> /
> >> > firm standards, the program, readability, variable accessibility (IDE
> >> > support, auto completition) (env vars not supported by IDEs as they
> are
> >> not
> >> > part of code)
> >>
> >> Thanks for the pointers, although I have only just got my head around
> >> argparse/configargparse, so click is something I might have a look at
> >> for future project.
> >>
> >> However, the question of how to parse the arguments is somewhat separate
> >> from that of how to pass (or not pass) the arguments around within a
> >> program.
>
> [snip (16 lines)]
> >
> > Hi,
> > I thought not just parsing, but the usage method: you add a decorator to
> > the function where you want to use the parameters. This way you do not
> have
> > to pass the value through the calling hierarchy.
> >
> > Note: typer is a newer package, it contains click and leverages command
> > line parsing even more.
>
> Do you have an example of how this is done?  From a cursory reading of
> the documentation, it didn't seem obvious to me how to do this, but then
> I don't have much understanding of how decorators work.
>
> Cheers,
>
> Loris
>
>
> --
> This signature is currently under construction.
> --
> https://mail.python.org/mailman/listinfo/python-list


Hi,

will create a sample code on Monday - Tuesday

BR,
George
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: PEP Idea: Real private attribute

2021-08-29 Thread Mehrzad Saremi
No, a class ("the class that I'm lexically inside") cannot be accessed from
outside of the class. This is why I'm planning to offer it as a core
feature because only the parser would know. There's apparently no elegant
solution if you want to implement it yourself. You'll need to write
self.__privs__[__class__, "foo"], whenever you want to use the feature and
even wrapping it in superclasses won't remedy it, because the parent class
isn't aware which class you're inside. It seems to me name mangling must
have been an ad-hoc solution in a language that it doesn't really fit when
it could have been implemented in a much more cogent way.

Best,
[image: image.gif][image: image.gif]

Mehrzad


On Sun, 29 Aug 2021 at 02:18, Chris Angelico  wrote:

> On Sun, Aug 29, 2021 at 7:40 AM Mehrzad Saremi 
> wrote:
> >
> > Python currently uses name mangling for double-underscore attributes.
> Name
> > mangling is not an ideal method to avoid name conflicting. There are
> > various normal programming patterns that can simply cause name
> conflicting
> > in double-underscore members. A typical example is when a class is
> > re-decorated using the same decorator. The decorator can not take
> > double-underscore members without name conflicts. For example:
> >
> > ```
> > @custom_decorator("a")
> > @custom_decorator("b")
> > class C:
> > pass
> > ```
> >
> > The `@custom_decorator` wrapper may need to hold private members, but
> > Python's current name conflict resolution does not provide any solution
> and
> > the decorator cannot hold private members without applying tricky
> > programming methods.
> >
> > Another example is when a class inherits from a base class of the same
> name.
> >
> > ```
> > class View:
> > """A class representing a view of an object; similar to
> > numpy.ndarray.view"""
> > pass
> >
> > class Object:
> > class View(View):
> > """A view class costumized for objects of type Object"""
> > pass
> > ```
> >
> > Again, in this example, class `Object.View` can't take double-underscore
> > names without conflicting with `View`'s.
> >
> > My idea is to introduce real private members (by which I do not mean to
> be
> > inaccessible from outside the class, but to be guaranteed not to conflict
> > with other private members of the same object). These private members are
> > started with triple underscores and are stored in a separate dictionary
> > named `__privs__`. Unlike `__dict__` that takes 'str' keys, `__privs__`
> > will be a double layer dictionary that takes 'type' keys in the first
> > level, and 'str' keys in the second level.
> >
> > For example, assume that the user runs the following code:
> > ```
> > class C:
> > def __init__(self, value):
> > self.___member = value
> >
> > c = C("my value")
> > ```
> >
> > On the last line, Python's attribute setter creates a new entry in the
> > dictionary with key `C`, adds the value "my value" to a new entry with
> the
> > key 'member'.
> >
> > The user can then retrieve `c.___member` by invoking the `__privs__`
> > dictionary:
> >
> > ```
> > print(c.__privs__[C]['member'])  # prints 'my value'
> > ```
> >
> > Note that, unlike class names, class objects are unique and there will
> not
> > be any conflicts. Python classes are hashable and can be dictionary keys.
> > Personally, I do not see any disadvantage of using __privs__ over name
> > mangling/double-underscores. While name mangling does not truly guarantee
> > conflict resolution, __privs__ does.
>
> Not entirely sure how it would know the right type to use (subclassing
> makes that tricky), but whatever your definition is, there's nothing
> stopping you from doing it yourself. Don't forget that you have
> __class__ available if you need to refer to "the class that I'm
> lexically inside" (that's how the zero-arg super() function works), so
> you might do something like self.__privs__[__class__, "foo"] to refer
> to a thing.
>
> ChrisA
> --
> https://mail.python.org/mailman/listinfo/python-list
>
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: PEP Idea: Real private attribute

2021-08-29 Thread Chris Angelico
On Mon, Aug 30, 2021 at 5:49 AM Mehrzad Saremi  wrote:
>
> No, a class ("the class that I'm lexically inside") cannot be accessed from
> outside of the class. This is why I'm planning to offer it as a core
> feature because only the parser would know. There's apparently no elegant
> solution if you want to implement it yourself. You'll need to write
> self.__privs__[__class__, "foo"], whenever you want to use the feature and
> even wrapping it in superclasses won't remedy it, because the parent class
> isn't aware which class you're inside. It seems to me name mangling must
> have been an ad-hoc solution in a language that it doesn't really fit when
> it could have been implemented in a much more cogent way.
>

If the parent class isn't aware which class you're in, how is the
language going to define it?

Can you give a full run-down of the semantics of your proposed privs,
and how it's different from something like you just used above -
self.__privs__[__class__, "foo"] - ? If the problem is the ugliness
alone, then say so; but also, how this would work with decorators,
since you specifically mention them as a use-case.

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: on writing a while loop for rolling two dice

2021-08-29 Thread dn via Python-list
On 29/08/2021 22.24, Chris Angelico wrote:
> On Sun, Aug 29, 2021 at 8:14 PM dn via Python-list
>  wrote:
>> Efficiency:
>> - wonder how max( d ) == min( d ) compares for speed with the set() type
>> constructor?
> 
> That may or may not be an improvement.
> 
>> - alternately len( d ) < 2?
>> - or len( d ) - 1 coerced to a boolean by the if?
> 
> Neither of these will make any notable improvement. The work is done
> in constructing the set, and then you're taking the length. How you do
> the comparison afterwards is irrelevant.

It was far too late for either of us (certainly this little boy) to be
out-and-coding - plus an excellent illustration of why short-names are a
false-economy which can quickly (and easily) lead to "technical debt"!


The "d" is a tuple (the 'next' returned from the zip-output object)
consisting of a number of die-throw results). Thus, can toss that into
len() without (any overhead of) conversion to a set.
-- 
Regards,
=dn
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: on writing a while loop for rolling two dice

2021-08-29 Thread Chris Angelico
On Mon, Aug 30, 2021 at 9:53 AM dn via Python-list
 wrote:
>
> On 29/08/2021 22.24, Chris Angelico wrote:
> > On Sun, Aug 29, 2021 at 8:14 PM dn via Python-list
> >  wrote:
> >> Efficiency:
> >> - wonder how max( d ) == min( d ) compares for speed with the set() type
> >> constructor?
> >
> > That may or may not be an improvement.
> >
> >> - alternately len( d ) < 2?
> >> - or len( d ) - 1 coerced to a boolean by the if?
> >
> > Neither of these will make any notable improvement. The work is done
> > in constructing the set, and then you're taking the length. How you do
> > the comparison afterwards is irrelevant.
>
> It was far too late for either of us (certainly this little boy) to be
> out-and-coding - plus an excellent illustration of why short-names are a
> false-economy which can quickly (and easily) lead to "technical debt"!
>
>
> The "d" is a tuple (the 'next' returned from the zip-output object)
> consisting of a number of die-throw results). Thus, can toss that into
> len() without (any overhead of) conversion to a set.

Oh. Well, taking the length of the tuple is fast... but useless. The
point was to find out if everything in it was unique :)

Conversion to set tests this because the length of the set is the
number of unique elements; checking max and min works because two
scans will tell you if they're all the same; using all with a
generator stops early if you find a difference, but requires
back-and-forth calls into Python code; there are various options, and
the choice probably won't make a material performance difference
anyway :)

ChrisA
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: PEP Idea: Real private attribute

2021-08-29 Thread Mehrzad Saremi
The proposed semantics would be the same as self.__privs__[__class__,
"foo"]; yes I can say the problem is ugliness. The following is an example
where name mangling can be problematic (of course there are workarounds,
yet if double-underscores are meant to represent class-specific members,
the following behavior is an infringement of the purpose).

```
class Weighable:
def weight(self):
raise NotImplementedError()


class AddWeight:
def __init__(self, weight):
self.weight = weight

def __call__(self, cls):
class Wrapper(cls, Weighable):
__weight = self.weight

def weight(self):
return self.__weight + (cls.weight(self) if issubclass(cls, Weighable) else
0)  # Unexpected behavior

return Wrapper


@AddWeight(2.0)
@AddWeight(1.0)
class C:
pass


print(C().weight())
```

> If the parent class isn't aware which class you're in, how is the
language going to define it?

I mean if you want to implement self.__privs__[__class__, "foo"] in a
parent class using __setattr__/__getattribute__ the __class__ value is
unknown.


On Mon, 30 Aug 2021 at 00:26, Chris Angelico  wrote:

> On Mon, Aug 30, 2021 at 5:49 AM Mehrzad Saremi 
> wrote:
> >
> > No, a class ("the class that I'm lexically inside") cannot be accessed
> from
> > outside of the class. This is why I'm planning to offer it as a core
> > feature because only the parser would know. There's apparently no elegant
> > solution if you want to implement it yourself. You'll need to write
> > self.__privs__[__class__, "foo"], whenever you want to use the feature
> and
> > even wrapping it in superclasses won't remedy it, because the parent
> class
> > isn't aware which class you're inside. It seems to me name mangling must
> > have been an ad-hoc solution in a language that it doesn't really fit
> when
> > it could have been implemented in a much more cogent way.
> >
>
> If the parent class isn't aware which class you're in, how is the
> language going to define it?
>
> Can you give a full run-down of the semantics of your proposed privs,
> and how it's different from something like you just used above -
> self.__privs__[__class__, "foo"] - ? If the problem is the ugliness
> alone, then say so; but also, how this would work with decorators,
> since you specifically mention them as a use-case.
>
> ChrisA
> --
> https://mail.python.org/mailman/listinfo/python-list
>
-- 
https://mail.python.org/mailman/listinfo/python-list


Re: on writing a while loop for rolling two dice

2021-08-29 Thread dn via Python-list
On 30/08/2021 00.47, Peter Otten wrote:
> On 29/08/2021 12:13, dn via Python-list wrote:
>> On 29/08/2021 20.06, Peter Otten wrote:
>> ...
>>> OK, maybe a bit complicated... but does it pay off if you want to
>>> generalize?
>>>
>> def roll_die(faces):
>>>  while True: yield random.randrange(1, 1 + faces)
>>>
>> def hmt(faces, dies):
>>>  for c, d in enumerate(zip(*[roll_die(faces)]*dies), 1):
>>>  if len(set(d)) == 1: return c, d
>>
>>
>> Curiosity:
>> why not add dies as a parameter of roll_die()?
> 
> Dunno. Maybe because I've "always" [1] wanted a version of
> random.randrange() that generates values indefinitely. It would need to
> check its arguments only once, thus leading to some extra

Each code-unit should do one job, and do it well!
SRP...


>> Efficiency:
>> - wonder how max( d ) == min( d ) compares for speed with the set() type
>> constructor?
> 
> I did the simplest thing, speed was not a consideration. If it is, and
> dies (sorry for that) is large I'd try
> 
> first = d[0]
> all(x == first for x in d)  # don't mind one duplicate test
> 
> For smaller numbers of dice I'd unpack (first, *rest) inside the for
> loop. But it's a trade-off, you' have to measure if/when it's better to
> go through the whole tuple in C.

For larger numbers of dice, and presuming a preference for an
inner-function which rolls only a single die per call; would it be more
efficient to test each individual die's value against the first,
immediately after its (individual) roll/evaluation (rinse-and-repeat for
each die thereafter)? This, on the grounds that the first mis-match
obviates the need to examine (or even roll), any other die/dice in the
collection.

OTOH the simulation of rolling n-number of dice, as would happen in the
real-world, would be broken by making the computer's algorithm more
efficient (rolling until the first non-equal value is 'found'). Does
that mean the realism of the model dies?
(sorry - no, I'm not sorry - you deserved that!)

Does one "roll" a die, or "shake the dice"???


We don't know the OP's requirements wrt to execution-efficiency.
However, as you (also) probably suffered, such exercises regularly
feature in stats and probability courses. Sometimes 'the numbers' are
quite large in order to better-illustrate ("smooth") distribution
characteristics, etc.


I have a love?hate relationship with questions of Python and
'efficiency'. Today, discovered that using a cut-down Linux version (on
AWS) was actually slower than using a full-fat distribution - upon
analysis, my friendly 'expert' was able to point the finger at the way
the two distros compiled/prepared/distribute the(ir) Python Interpreter.
(I'm glad he thought such investigation 'fun'!) All of which further
complicates the business of design, given we already know of situations
where approach-a will run faster than approach-b, on your machine; yet
the comparison may be reversed on mine.

This discussion forms a sub-set of that: when to use the built-in
functions (implemented in C) because they are (claimed to be) more
efficient than another approach - and, when one approach using a
built-in function might be faster than another 'built-in'/C-coded approach.

("small things amuse small minds" - mind how you describe my mind!)


"Bottom line": I prefer to think of Python's "efficiency" as reflected
in the amount of my time that is needed, in order to complete a project!


>> - alternately len( d ) < 2?
>> - or len( d ) - 1 coerced to a boolean by the if?
>> - how much more efficient is any of this (clever thinking!) than the
>> OP's basic, simpler, and thus more readable, form?
> 
> It really isn't efficiency, it's a (misled?) sense of aesthetics where
> I've come to prefer
> 
> - for-loops over while, even when I end up with both to get the desired for
> 
> - enumerate() over an explicit counter even though there is the extra
> unpack, and you still need to initialize the counter in the general case:
> 
> for i, item in enumerate([]): pass
> print(f"There are {i+1} items in the list.")  # Oops

Next thing you'll be using for-else...
[insane giggling]


It's interesting how we arrive at these views (as a trainer I spend a
lot of time trying to detect how learners build their mental maps, or
"models", of each topic).

I've always had a clear 'formula'/rule/hobgoblin: if the number of loops
can be predicted, use 'for', otherwise use 'while'.

Of course, Python alters that view because it offers a for-each, which
means that I don't need to know the number of loops, only that the loop
will cycle through each item in the iterable.

It used to be a far simpler world!


That said, I really miss the option of while controlling the loop with a
pre-condition AND having a repeat...until controlling the loop with a
post-condition - the former enabling >=0 loops; the latter, requiring at
least one!


Using enumerate() may be a matter of aesthetics (English-English
spelling!). However, it is a basic Python idiom. It is as much