What about a -pedantic flag?

I think it could be useful an additional -pedantic flag that implies -b and warns about:

  • "stringa" "stringb" (and suggest +)
  • statement \ continuation (and suggest parenthesis)
  • from modulex import * (__init__.py files apart)
  • a == None, a == (), a == frozenset() (suggest is)
  • type(a) == sometype (suggest isinstance(a, sometype))
  • type(a) in (type1, ...) (suggest isinstance(a, (type1, ...)))
  • context managers not managed with with
  • possible use of @classmethod
  • useless else (ie if x: return 0 else return 1 at the end of a function)
  • useless if (if statement then x = True else x= False, instead of x = statement)
  • use of except:
  • f(a={}) or other mutables
  • raise Exception() (or BaseException or RuntimeException)
  • for i in range(len(iterable))
  • a = 1, (suggest a = (1, ))
  • a.__hash__() instead of hash(a) (and repr(), str(), …)
  • global, nonlocal eval() and exec() usage
  • .1 (suggest 0.1)
  • unreachable code
  • unused code, like variables, imports, “private” and “protected” class members
  • possible AttributeError caused by None
  • local variable redifine global one (id = 3, for example, but not “{id}.format(id=3)”)
  • suggest use of tuple if a list is not modified
  • same for frozenset

Why build this into the interpreter? Linters can handle most of if not all of these situations.

2 Likes

Well, many interpreter and compilers do this checks, if enabled. See gcc and javac for example.

Furthermore, many third-party projects become part of the py stdlib. Think about pip or venv.

As an alternative, Python could add a linter in the standard library and integrate it with the python executable. So if a linter option is present in the invocation of python, the linter is invoked with those parameters before the interpreter.

Anyway, IMHO

  • "stringa" "stringb"
  • statement \
  • a = 1,
  • .1

should be deprecated by default… :stuck_out_tongue:

Most of this is a correct code which often occurred in real world. Alternatives are not better, may be worse or just incorrect. Such warnings are more than useless, they would cause harm.

2 Likes

Alternatives are not better, may be worse or just incorrect

Explain, please.

Most of this is a correct code

Of course the code is “correct” It’s “correct” because is interpreted without errors or warnings by the Py interpreter. It’s just IMHO prone to programmer errors, or simply ugly:

  1. "stringa" "stringb": consider this:
a = (
    "string1", 
    "string2" 
    "string3", 
    "string4", 
)

This error in particular happened to me personally. I spent hours to understand the stupid problem…

  1. statement \:

consider this:

if verylongstatement1 and verylongstatement2 or \
    verylongstatement1:

   [code]

IMHO is much more readable

if (verylongstatement1 and 
    verylongstatement2 or
    verylongstatement1
):
   [code]

  1. a = 1, prone to stupid errors. I added one time a comma by error, and the code did not work, without any errors. Also this time I spent hours to understand the problem. Is it not true that “Explicit is better than implicit”?

Another example?

10,2 / 2
#(10, 1.0)

Obviously, you wrong the decimal separator, and you want 10.2 / 2. If I had to write (10, 2) / 2 this errors can be avoided.

  1. .1 IMHO it’s simply an ugly, lazy and less readable way to write a float. For what I know Python is proud to be defined as a language that can be read as simple English. Well, not only in English, but also in mathematics .1 means nothing.

Does CPython optimise + here? Otherwise this should most definitely not be deprecated.

3 Likes

Yeah, the peephole optimizer will get this case:

>>> import dis
>>> def f(): return 'a' + 'b'
...
>>> dis.dis(f)
  1           0 LOAD_CONST               1 ('ab')
              2 RETURN_VALUE

but I actually like the

(
  'splitting'
  'really'
  'long strings over multiple lines'
)

use-case.

1 Like

Because

(
  'splitting' +
  'really' +
  'long strings over multiple lines'
)

it’s not more readable, explicit and less error prone?

How much code would this break? There are perhaps hundreds or thouthands of occurrences only in the stdlib.

+ is a runtime concatenation (it is optimized out at compile time for strings in CPython, but this is an implementation and does not work with f-strings). "stringa" "stringb".split() is not the same as "stringa" + "stringb".split(), so converting it to using + will introduce bugs.

Also it is a way to get around the limitation of f-strings which cannot have \ in internal expressions.

It is not always possible. You cannot use parenthesis in

with foo() as a, \
     bar() as b:

Some modules (for example tkinter and turtle) are designed for the star import. Also it is convenient for experimenting in REPL.

a is () and a is frozenset() are just pure errors. You will get a syntax warning for a is (). It is an implementation detail that empty tuple and frozen set are singletons. Also, since the semantic of == and is is different, it may be that you need a == None. For example, mock.ANY == None is true, but mock.ANY is None is false.

type(a) == sometype (suggest isinstance(a, sometype) )

In some cases isinstance() is a wrong method, you need to check an exact type. An example is pickle.

Same as above.

Oh, there are a lot of cases when you do not use a context manager with with directly, but pass it to ExitStack.enter_context() or manage manually in setUp()/tearDown(). Some context managers can also be used as decorators.

What do you mean? @classmethod is a useful feature, it is used everywhere. There is nothing wrong with it, and banning it will break a lot of code.

It is a matter of style and may be historically conditioned. It may make the code more symmetrical (especially if you have a sequence of chained if’s). It is easier to move code between branches if they are on the same level. Finally, banning this will cause a code churn.

useless if ( if statement then x = True else x= False , instead of x = statement )

It is just wrong.

if [1, 2]:
    x = True
else:
    x = False

is not the same as

x = [1, 2]

There is nothing wrong with use of a bare except if you use it correctly (reraise the exception, pass it to other handler, or report it at continue on the top level of the program). There may be even cases when except: is more preferable than except BaseException: – it works even if BaseException is not defined, that may be a case at the shutdown stage.

Nothing wrong with this if the value is never modified or if it is used as a cache. This makes the signature clearer than using None or a special singleton.

Exception are designed to be raised. In particularly RuntimeError is designed to be raised if no other special exception is appropriate.

for i in range(len(a)):
    a.append(i*b[i]+a[i])

It is a matter of style. Parenthesis are redundant here. Do you suggest to write also a = (x+(y*z)) instead of a = x+y*z?

Who writes a.__hash__()? There are subtle differences between a.__hash__() and hash(a), but you should use a.__hash__() only if you are aware of these differences and intentionally want to use the result of a.__hash__().

Could you please explain?

It is a syntax accepted in a lot of other programming languages. It helps with interoperability.

Unreachable code is usually a code temporary unreachable for debugging purposes. It would be annoying to get warnings about it. Also, some code can be unreachable only on particular platforms of Python versions.

There is coverage.py for this. It is a complex tool which often has false detection. It should not be a part of the compiler.

There are special tools for this. It is a complex problem, and this is not a work of the compiler.

You already have an AttributeError exception for this. What you want more?

This will break a working code after adding a new global with a name that matches the local variable.

In many case it is a matter of style. The compiler replaces a list with a tuple in some cases if it is absolutely safe. But in general case we cannot determine this.

Same as above. In addition, there is a syntax for tuple, list and set displays and comprehensions, but there is no syntax for a frozenset display and comprehension. Creating a frozenset is always a function call, which is slower than creating a set with a set display and comprehension.

3 Likes

No one, because it’s a warning.

But you can do:

with foo(
    verylongstatement1,
    verylongstatement2,
    somekey = verylongstatement3,
) as a, bar() as b:

“Redundant”? Because

  • a += 1 vs a++
  • if elif vs switch - case
  • str(1) + " time" vs 1 + " time"
  • self vs this
  • a = 0 if x == 3 else 2 vs a = x == 3 ? 0 : 2
  • pass vs well… nothing

are “redundant”? Are “a matter of style”? I suppose that they improve readability and explicitness. a = 1, is really unreadable. It’s hard to distinguish the comma, it’s easily confused with a = 1. And furthermore if you add a comma by mistake… good debugging. I’ve done this error with sqlalchemy and it was a nightmare, becase there was no error. The code simply didn’t work.

Because 0.1 is not supported by other languages?

I don’t reply to the others, since I agree it’s not work for the interpreter but for a linter. I just want to repeat that I hope a linter will be added to the stdlib and will be integrated to the python command line options.

I just want to answer this:

But x = bool([1, 2]) yes.

@storchaka

Just for fun I tested a loop without indexing access (i.e. compatible with generic iterables):

for index, (a_item, b_item) in enumerate(zip(a, b)):
    a.append(index * b_item + a_item)

I think this would be the preferred (pythonic) way :slight_smile:

Unfortunately the result will differ when len(b) > len(a). Modification during iteration is tricky :slight_smile: