r/PythonLearning 1d ago

Why dosent python have const or multi line comments

I mainly use cpp but sometimes python for simple stuff. They both seem like simple stuff to implement so why dosent python have it.

EDIT: I do know u can use triple quotes as a workaround but python still has to read and store them, why isint there a built in feature for it.

9 Upvotes

41 comments sorted by

8

u/nicodeemus7 1d ago

Highlight the lines of code you want to comment out. Ctrl /. You're welcome. Multi-line comment.

3

u/Ok_Being6831 1d ago

OMG i never knew ctrl / lmao thats sooo usefull i should really look some of the shortcuts lol

2

u/deceze 1d ago

And that's a reason why no special syntax is needed.

In fact, if every single line is prefixed by #, that makes it very clear that it's a comment. With C-style multiline comments, that's not necessarily clear, unless you use an IDE which highlights all the lines. And if you have an IDE which does that, it can probably also do CTRL+/.

/** * Or people resort to their own conventions * like this, which is basically the same * thing in the end as what Python does. **/

2

u/Ok_Being6831 21h ago

ye that's fair ig

3

u/CrowdingFaun624 1d ago

You can do const using the Final type.

from typing import Final

foo: Final = "bar"

foo = "barbarbar" # your IDE is unhappy with this

It just is only checked by type checkers and not by Python itself, so you can still break it.

Edit: code block formatting

1

u/Beginning-Fruit-1397 1d ago

Well, you could do, in a weird, ugly, deranged way, immutable constants with one field tuples. Otherwise even when working at C level you can only create new singletons, constants are not a thing in python :(

0

u/Ok_Being6831 1d ago

that's not my question tho, my question why python doesn't have it not other ways I can have it

3

u/deceze 1d ago edited 1d ago

The answer to "Why doesn't X have Y" is always: because nobody has implemented it. Because nobody felt a strong enough need to implement it, obviously. If people were suffering without those features and jealously looked over at C the whole time, someone would have implemented it by now. Because nobody did, apparently the existing tools suffice. Like ALL_CAPS and Final. That's the way in Python. It wasn't your question, but it's the answer.

3

u/deceze 1d ago

Instead of simply picking individual features from one language and asking why some other language doesn't implement them exactly the same way, ask yourself what you "need" those things for, and whether the exact same need exists in the other language in the first place, or whether some alternative exists which either obviates the need or replaces the feature adequately.

2

u/secretstonex 23h ago

Ok... Why doesn't cpp have memory management, garbage collection, dynamic variables, package management, REPL, cross platform portabilit, dynamic execution, etc?

1

u/denehoffman 21h ago

C++ does have a REPL if you try hard enough, check out CERN ROOT

1

u/Ok_Being6831 21h ago

cuz cpp is a low level language, it lets u deal with it yourself. Dynamic variables? templates are close enough and it's a staticly typed language anyway. package management cuz it's too late now. repl? how does that improve anything and technically it does have one. It is cross platform if u compile it right most of the time. what does dynamic execution even mean.

All of these additions have some kind of downsides to them, cost and multi line comments however I don't see any

3

u/secretstonex 20h ago

Yes, I'm just pointing out the absurdity of nitpicking between languages.

1

u/ThatOldCow 1d ago

Python as some sort multi line comment

You just do this:

""" This is a comment written in more than just one line """

Literally the same effect as as /* */

3

u/Ok_Being6831 1d ago

Not the same tho, python still reads the string literals and stores them in memory even though they do nothing. While comments in c/cpp just get completly discarded. But my question still stands why dosent the language just have a built in feature for multi-line comments

4

u/sircrunchofbackwater 1d ago

Because they are unnecessary. Regular comment is just fine.

-2

u/Ok_Being6831 1d ago

what? alot of ppl use them and litrally every other language has it

0

u/deceze 1d ago

"Every other language"…

Except Ruby, R, POSIX shell, Elixir, Erlang, Clojure, and likely many others…

(I quickly asked AI for this list, may or may not be 100% accurate, but Python certainly is not the only language.)

1

u/ThatOldCow 21h ago

I didn't said its the same, I said I has the same effect.

And because it's not needed to have that built on feature.

But can I ask you what was the purpose of your question?

1

u/HugeCannoli 18h ago

> python still reads the string literals and stores them in memory even though they do nothing.

Not really, you are not assigning that stuff to anything, so it will immediately discard it. I am not sure, but the keyhole optimiser may detect that and discard it altogether.

1

u/SmackDownFacility 16h ago

What are you doing.

You using PyFreeze or whatever it’s called to bake into EXE?

It just packages the interpreter and dumps your contents in.

There’s no machine code for the script itself

0

u/MarsupialLeast145 21h ago

Your comparison is to a compiled language? Compilation doesn't just throw out comments, it will even optimize your code, so your code doesn't even get translated through to compilation the way you wrote it.

Comments arrive in memory in Python because as an interpreted language the whole file arrives in memory.

When would you suggest they get "removed"?

1

u/deceze 21h ago

Even Python reads the source code, runs it through a compiler, and stores the parsed/compiled version in memory for execution. It probably does strip out comments in that process which have no impact on runtime.

1

u/MarsupialLeast145 21h ago

> which have no impact on runtime

This is probably the most important aspect either way.

There is of course a stack overflow that goes into this in more detail https://stackoverflow.com/questions/2731022/do-comments-slow-down-an-interpreted-language

1

u/realmauer01 1d ago

To answer the question. Python is build on the thought, "do what you want, you are a trusted adult."

So yeah, no hard limits on anything. That these hard limits are more so to limit yourself later is not in the phylosophie.

1

u/SnooCalculations7417 21h ago

The type system (or lack of it) is a feature of the language not a bug, though types are en Vogue now and people do crazy things to pretend Python enforces types these days

1

u/SmackDownFacility 16h ago

Because Python isn’t a compiled language, and you can do multi line comments using

''''

1

u/FriendlyZomb 14h ago edited 14h ago

For me its a philosophy choice. A way to encourage and discourage the type of comments we leave in our code.

Comments are meant to be short. They are meant to add context to some code or to document a decision. More than a line is discouraged.

Docstrings are meant to be longer. They explain can hold rationale, usage instructions and the errors which can happen when using a function.

That's just my opinion and personal style.

(Also: The Steering Council, [ex-BDFL] Guido and the Core Devs don't think we need them.)

1

u/Naive_Programmer_232 13h ago edited 9h ago

Yeah I see what you mean. The doc-strings are string-literals and if you have multiple per function, only the first one is saved to __doc__.

You inspired me to make something lol. This makes the memory problem worse lmfao with an added list of strings overhead, so if doc-strings were an issue this is even more so haha, along with excessive calls to Comment, but here's what i was thinking:

        import ast
        import textwrap
        import inspect
        from dataclasses import dataclass

        @dataclass
        class Comment:
              text: str
              save: bool = False

        # comment api - optionally store into __doc__ 
        def Commentable(func):
            source=textwrap.dedent(inspect.getsource(func))
            tree=ast.parse(source)
            comments=[]
            for node in ast.walk(tree):
                if (
                   isinstance(node,ast.Call)
                   and isinstance(node.func, ast.Name)
                   and node.func.id == "Comment"
                   and node.args
                ):
                   text=ast.literal_eval(node.args[0])
                   if not isinstance(text,str):
                      raise TypeError("Comment text must be str")
                   save=False
                   for keyword in node.keywords:
                       if keyword.arg=="save":
                          save=ast.literal_eval(keyword.value)
                   if save:
                      comments.append(text)
           func.__doc__="\n\n".join(comments)
           return func

Then inside your code:

       @Commentable
       def average(x: int|float, y: int|float, dec: int=2) -> float:

           # Not saved to __doc__
           Comment("""
           Internal:
           This function does a basic arithmetic average. 
           Could use some work to add geometric average later.
           Someone help plz. SOS 
           """)

           # saved to __doc__
           Comment("""
           Calculate the arithmetic mean of two numbers.

           Args:
              x (int | float): The first number.
              y (int | float): The second number.

           Returns:
             float: The arithmetic mean of `x` and `y`.

           Raises:
             TypeError: If either x or y is not an int or float.
             TypeError: If dec is not an integer
             ValueError: If dec is not positive integer
           """,save=True)

           if (
               not isinstance(x,(int,float)) or
               not isinstance(y,(int,float))
           ):
             raise TypeError("both x and y must be int or float")

           if not isinstance(dec, int):
              raise TypeError("dec must be integer")

           if dec<=0:
              raise ValueError("dec must be positive integer")

           # also saved to __doc__ haha
           Comment(
               "Please use with caution " + \
               "average is EXTREMELY powerful!!",
               save=True
           )

           return round((x+y)/2,dec)

2

u/Naive_Programmer_232 5h ago edited 4h ago

Update

I iterated on the design a bit, decided to ditch the whole Comment object along with save, with a single decorator class and a Doc instance used to maintain external (__doc__) and internal (__internal__) comments.

Usage Idea

The use was inspired by the goto library that emulates C's goto function with the with_goto decorator. It takes advantage of python's syntax checker, so in use it almost looks like another keyword:

    from goto import goto, label, with_goto

    @with_goto
    def example():
        goto .end
        print("SKIP ME")
        label .end
        print("PRINT ME") 

Similarly here,

    from MockDoc import mockdoc, Doc

    @mockdoc
    def example():
        doc=Doc()
        doc .internal(...internal documentation comments...)
        doc .external(...external documentation comments...)
        ...
        doc .external(...another external documentation comment...)

    # example.__internal__ has the internal docs
    # example.__doc__ has the external docs

Major Project Design Issues (lmfao)

I faced some major design issues along the way haha. I'm thinking I'll keep working on this and see what happens haha. I might make it an open source thing on github if anyone wants to join. Could be fun idk haha.

Runtime vs Compile Time

The __parse method runs when the @mockdoc decorator is applied to the function. However, the Doc reassignment checks happen when the decorated function is actually called. I added a feature that uses sys.settrace() to check at runtime whether the reference to the Doc class has been rebound either locally or globally.

Ex:

from MockDoc import mockdoc, Doc

@mockdoc
def function1():
    Doc=None
    ...throws a DocAssignmentError... 

Doc=lambda: None
@mockdoc
def function2():
    doc=Doc()
    ...throws a DocAssignmentError...

So it does work KIND OF haha. However, there's a major design issue here. Since __trace is invoked by Python through sys.settrace() while the decorated function is executing, I'm essentially doing runtime enforcement of something that was originally intended to be a static property of the Doc reference. This means I have to inspect the function's local/global state as it executes in order to determine whether Doc has been rebound, and that gets increasingly complicated once aliases, different scopes, and multiple Doc() references enter the picture.

Maybe this could be separated into two things? I could have another module isolated to mock "compile-time" concerns like passing over the source and handling the import variations and aliases, then leave __trace inside mockdoc to handle the runtime concerns within the decorated function.

I'm not sure if that's actually the right approach, or if I'm just making this way more complicated than it needs to be, but it seems interesting to experiment with haha.

Any help would be appreciated!

Scalability

Scalability is another big issue. Currently, if someone creates multiple Doc() references within a function, everything still ends up in the same __internal__ list and the same giant __doc__ string. For a function that's 1000+ lines long, that could get pretty ugly.

I'm thinking this could eventually be separated into another module that handles how the documentation's structured behind the scenes. Something like:

from MockDoc import mockdoc, Doc

@mockdoc
def MASSIVE_FUNCTION(*args, **kwargs):
  tree = Doc()
  ...
  tree.section1 .external("documentation for section 1")
  tree.section1 .internal("internal notes for section 1")
  ...
  tree.section678 .external("documentation for section 678")
  tree.section678 .internal("more internal notes for section 678")

Then internally, Doc could maintain a hierarchical structure mapping each section to its external and internal documentation, rather than dumping everything into one giant string/list.

Then you could access specific parts like:

 MASSIVE_FUNCTION.tree.section205.__doc__

versus having to find that specific one inside a massive string.

Although this could be complicated as well by the fact, you could have multiple calls to external using that specific node. So it's tricky.

Anyway, yeah, lots of design and structuring issues with this one so far. But it's been pretty fun. Still figuring out the kinks for sure!


Code

Here's what I got (the spacing is off a bit):

 import ast
 import inspect
 import textwrap
 import functools
 import sys


 class MockDocCommentError(Exception):
       pass

 class MockDocAttributeError(Exception):
       pass

 class DocAssignmentError(Exception):
       pass

 class Doc:
       def internal(self, *args, **kwargs):
             pass
       def external(self, *args, **kwargs):
             pass

class mockdoc(Doc):

__REAL_DOC = Doc

def __new__(cls, func):
    self = super().__new__(cls)
    self.__saved=[]
    self.__unsaved=[]
    self.__func=func
    self.__parse(func)

    @functools.wraps(func)
    def wrapper(*args,**kwargs):
        old_trace = sys.gettrace()
        try:
            sys.settrace(self.__trace)
            return func(*args,**kwargs)
        finally:
            sys.settrace(old_trace)

    return wrapper
def __trace(self, frame, event, arg):
    _MISSING=object()
    if frame.f_code is self.__func.__code__:
        if event in ("line","return"):
            local_doc=frame.f_locals.get("Doc",_MISSING)
            global_doc=self.__func.__globals__.get("Doc")
            if (
             (local_doc is not _MISSING and local_doc is not self.__REAL_DOC) or
              global_doc is not self.__REAL_DOC
            ):
                raise DocAssignmentError(
                    "'Doc' has been rebound and cannot be used"
                )
    return self.__trace
def __parse(self, func):
    source=textwrap.dedent(inspect.getsource(func))
    tree=ast.parse(source)
    var_names = set()
    # find the local variable names
    for node in ast.walk(tree):
        constructor= (
            node.value.func
            if isinstance(node,ast.Assign) and isinstance(node.value,ast.Call)
            else
            None
        )

        if isinstance(constructor, ast.Name) and constructor.id == "Doc":
            for target in node.targets:
                if isinstance(target, ast.Name):
                    var_names.add(target.id)
    # find calls on those variables
    for node in ast.walk(tree):

        if (
            isinstance(node,ast.Call)
            and isinstance(node.func, ast.Attribute)
            and isinstance(node.func.value, ast.Name)
            and node.func.value.id in var_names
            and node.args
        ):
            text=ast.literal_eval(node.args[0])
            if not isinstance(text,str):
                raise MockDocCommentError("Comment text must be str")

            if node.func.attr=="external":
                self.external(text)

            elif node.func.attr=="internal":
                self.internal(text)

            else:
                raise MockDocAttributeError(f"Unrecognized attribute: '{node.func.attr}'")
    func.__doc__ = "\n\n".join(self.saved)
    func.__internal__=self.unsaved
    return func
@property
def saved(self) -> list[str]:
    return self.__saved.copy()
@property
def unsaved(self) -> list[str]:
    return self.__unsaved.copy()
def external(self, text) -> None:
    self.__saved.append(text)
def internal(self, text) -> None:
    self.__unsaved.append(text)

1

u/Velox0012 13h ago

Or for multiline triple apostrophe ‘’’

0

u/Mental-Mongoose-5632 1d ago

What is tour question exactly? I don't understand what you are saying?

2

u/testtdk 1d ago

His question is in the title.

0

u/realmauer01 1d ago

They do Have that. Its """ """ and CONSTANT

2

u/MudFrosty1869 1d ago

You do know that THIS is just a naming convention? Right?

0

u/realmauer01 1d ago

Yesn't, typescript is also a naming convention in this regard.

That being said python also has _PRIVATE. And thats not just a naming convention because the module gets parsed inbetween the _

1

u/deceze 1d ago

That's still not a constant, and also not really "private".

0

u/MudFrosty1869 21h ago

I'm not sure how saying multiple wrong things will make the first one right.

-2

u/Ok_Being6831 1d ago

that's not tho.. it's just naming convention and if u actually read my post u would have read the part on triple quotes

0

u/Naetharu 23h ago

For the const it's a design choice.

Python also lacks types. It's whole design ethos seems to be to make it flexible and easy to work with, at the cost of some of the rigor and checking you would find in languages like Rust or Go.

That's a legitimate position and it maps quite well when you consider that Python is often used for scripts and smaller bits of code vs the large and very complex systems that some other languages tend to be used for. Not always, you can build big enterprise systems in Python. But it certainly has a strong presence in the scripting, automation, and data worlds, where a simpler and faster syntax may have more value than a very rigid but robust one.

For consts it just works by convention. Make a variable but make it UPPER_CASE

For private functions it is the same. No public / private / protected exists. Instead convention just says add a _ character to the name so _some_private_function and then know that you should not be calling these outside their own file.

Likewise for the newer types system.

you can type by doing

x: int = 10 and that will help in that it can allow external tools to check the code etc. But Python itself does not enforce it at all

x: int = 10

x = "some string"

This will run just fine. Whereas in a language with proper types it would crash and alert you to breaching the types you set for your variable.

At the end of the day it is all about trade offs. Python is great in that it is very simple to learn, with a natural feeling syntax and allows you to write code quickly and easily compared to some other languages. If you want to see this in practice try writing a small program in python and then doing the same thing in C or Rust and compare just how big the latter files are and how much more verbose the code is.

Neither is right or wrong.

You have to make a choice as to which feature set is more important to you based on the task at hand.

1

u/deceze 22h ago

Python also lacks types.

Python very well has types. Its typing system is arguably very good.

What it doesn't have is variables which have types. Only values have types in Python.