Axy,

Nobody denies some of the many ways you can make a good design. But people
have different priorities that include not just conflicts between elements
of a design but also equally important factors like efficiency and deadlines
and not breaking too badly with the past.

You can easily enough design your own sub-language of sorts within the
python universe. Simply write your own module(s) and import them. Choose
brand new names for many of your functions or replace existing functions
carefully by figuring out which namespace a function is defined in, then
creating a new function with the same name that may call the old function
within it by explicitly referring to it.

SO if you want a new max() then either create an axy_max() or perhaps link
the original max to original_max and make your own max() that after playing
around internally, might call original_max.

Here is an example. Suppose you want the maximum value in a nested structure
like:

nested = [ 1, [2, 3, [4, 5], 6], 7]

This contains parts at several levels including an inner list containing yet
another inner list. using max(nested) will not work even if some intuition
wants it to work.

If you want your own version of max to be flexible enough to deal with this
case too, then you might find a flattener function or  a function that
checks the depth of a structure such as a list or tuple, and apply it as
needed until you have arguments suitable to hand to original_max. Your max()
may end up being recursive as it keep peeling back one level and calling
itself on the results which may then peel back another level. 

But the people who built aspects of python chose not to solve every single
case, however elegant that might be, and chose to solve several common cases
fairly efficiently.

What we often end up doing here is pseudo-religious discussions that rarely
get anywhere. It really is of no importance to discuss what SHOULD HAVE BEEN
for many scenarios albeit with some exceptions. Some errors must be slated
to be fixed or at least have more warnings in the documentation. And,
proposals for future changes to Python can be submitted and mostly will be
ignored.

What people often do not do is to ask a question that is more easy to deal
with. Asking WHY a feature is like it is can be a decent question. Asking
how to get around a feature such as whether there is some module out there
that implements it another way using some other function call, is another
good question. COMPLAINING about what has been done and used for a long time
is sometimes viewed differently and especially if it suggests people doing
this were stupid or even inconsistent. 

Appealing to make-believe rules you choose to live your life by also tends
not to work. As you note, overengineering can cause even more problems than
a simple consistent design, albeit it can also create a rather boring and
useless product.

Too much of what happens under the table is hidden in python and if you
really study those details, you might see how a seemingly trivial task like
asking to create a new object of some class, can result in a cascade of code
being run that does things that themselves result in cascades of more code
as the object is assembled and modified using a weird number of searches and
executions for dunder methods in classes and metaclasses it is based on as
well as dealing with other objects/functions like descriptors and
decorators. Since our processors are faster, we might be able to afford a
design that does so much for you and we have garbage collection to deal with
the many bits and pieces created and abandoned in many processes. So our
higher level designs can often look fairly simple and even elegant but the
complexity now must be there somewhere.

I hate to bring up  an analogy as my experience suggests people will take it
as meaning way more (or less) than I intend. Many languages, especially
early on, hated to fail. Heck, machines crashed. So an elegant design was
required to be overlaid with endless testing to avoid the darn errors.
Compilers had to try to catch them even earlier so you did not provide any
argument to a function that was not the same type. You had to explicitly
test for other things at run time to avoid dividing by zero or take the
square root of a negative number or see if a list was empty ...

Python allows or even often encourages a different paradigm where you throw
errors when needed but mainly just TRY something and be prepared to deal
with failure. It too is an elegant design but a very different one. And, you
can do BOTH. Heck, you can do many styles of programming as the language
keeps being extended. There is no one right way most of the time even if
someone once said there is.

So if the standard library provides one way to do something, it may not be
the only way and may not match what you want. Sometimes the fix for a
request is made by adding options like the default=value for max, and
sometimes by allowing the user to specify the comparison function to use
with another keyword argument. This literally lets you use max() to actually
easily calculate what min() does or do something entirely different like
find the longest word in a sentence or the shortest:

>>> words = "A sentence with sesquipedalian words like
disestablishmentarianism to measure length".split()
...         
>>> words
...         
['A', 'sentence', 'with', 'sesquipedalian', 'words', 'like',
'disestablishmentarianism', 'to', 'measure', 'length']

So the length can be measured by len() and this asks for the maximum:

>>> max(words, key=len)
...         
'disestablishmentarianism'

The max() function here does not care and simply uses whatever key you
wanted. 

Simply making a function that returns a negative of the length suffices to
make this act like min():

>>> def neglen(arg): return( - len(arg))
... 
>>> neglen("hello")
-5
>>> max(words, key=neglen)
'A'

My point is that in one sense many standard library functions already have
had features added to them in ways that do not break existing programs and
they do allow fairly interesting problems to be solved. But sometimes such
features may mean either not accepting some formats as arguments that
another function supports, or perhaps needing to try to change lots of
functions at the same time to remain in sync. That is clearly often not
possible or may be highly expensive or it may simply be done more gradually
over many releases. I believe extending list comprehension or generators of
that sort to also do sets and dictionaries may have been done incrementally
even if today it seems like a unified set of things done in the same general
way.

So my PERSONAL view is that when looking at issues, we need to NOT find a
single principle and then insist it is the one and only principle to guide
us. We need to evaluate multiple ideas and not rule out any prematurely. We
then need to weigh the ideas in specific cases and see if anything dominates
or if perhaps a few in proper combination are a reasonable compromise. So I
submit as far as compilers and interpreters go, they would "love" really
short variable names and not see oodles of whitespace that just is there to
be ignored, as well as comments to ignore. But generally, we don't care
because what we focus on is the experience of human programmers and the ones
who read or write or maintain the code. For them, we want generally
variables long enough to hold some amount of meaningfulness. Similarly, many
other features are a tradeoff and although it is likely far cheaper to
write:

    deeply.nested.object.fieldname = 0 if deeply.nested.object.fieldname > 9

As more like:

    short = deeply.nested.object.fieldname
    short = 0 if short > 9

It makes sense to allow the programmer to choose either way and either have
a compiler or interpreter optimize code with regions like this that keep
referring to nested areas, or pay the costs to keep re-traversing it even if
it takes a millionth of a second longer.

Now if I am designing something under my own control, boy do I take short
cuts when prototyping and ignore lots of "rules" as I build the skeleton. If
I am somewhat satisfied, I might start applying many of the principles I use
including adding lots of comments, naming variables in more meaningful ways,
perhaps refactoring for more efficiency, changing the code to handle special
cases, setting up to detect some errors and catch them and do something
appropriate, write a manual page and so on. Lots of things are then
important but not so much in early stages.

Python is a work in progress built by lots of people and I am regularly
amazed at how well much of it hangs together despite some areas where it is
a tad inconsistent. But I am pretty sure many of the inconsistencies have
been handled in ways that are not seen unless you search. There must be
other implementations for functions that handle edge cases or that try many
ways and return the ones that do not generate errors. 

Do you prefer languages that pretty much either require you to create many
functions for each number and type of arguments like 
max(int, int) -> int
max(int, int, int) -> int
max(int, int, int, int) -> int
max(int, double) -> double
...
max(int, double, uint16) -> double

And so on? I know languages that allow you to write frameworks and then
internally create dozens of such variants to use as needed at compile time.
Each function can be optimized but so many functions may have their own
expenses.

But a language like python takes a different tack and one function alone can
do so many things with ease including handling any number of arguments,
defaults for some, many kinds of arguments and especially those that are
either compatible or implement some protocol, and so on. When you view
things that way, the design of max() and sum() may well make quite a bit
more sense and also why they are not identically designed.



-----Original Message-----
From: Python-list <python-list-bounces+avi.e.gross=gmail....@python.org> On
Behalf Of Axy via Python-list
Sent: Tuesday, February 21, 2023 2:37 PM
To: python-list@python.org
Subject: Re: Tuple Comprehension ???

On 21/02/2023 19:11, avi.e.gr...@gmail.com wrote:
> In your own code, you may want to either design your own functions, or use
them as documented or perhaps create your own wrapper functions that
carefully examine what you ask them to do and re-arrange as needed to call
the function(s) you want as needed or return their own values or better
error messages.  As a silly example, this fails:
>
> max(1, "hello")
>
> Max expects all arguments to be of compatible types. You could write your
own function called charMax() that converts all arguments to be of type str
before calling max() or maybe call max(... , key=mycompare) where compare as
a function handles this case well.
>
> The key point is that you need to adapt yourself to what some function you
want to use offers, not expect the language to flip around at this point and
start doing it your way and probably breaking many existing programs.
>
> Yes, consistency is a good goal. Reality is a better goal.

I don't think overengineering is a good thing. Good design utilizes
associativity so a person don't get amazed by inconsistency in things that
expected to be similar.

Axy.

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

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

Reply via email to