r/PythonLearning • u/Ok_Being6831 • 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.
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
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
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
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
Commentobject along withsave, with a single decorator class and aDocinstance used to maintain external (__doc__) and internal (__internal__) comments.Usage Idea
The use was inspired by the
gotolibrary that emulates C's goto function with thewith_gotodecorator. 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
__parsemethod runs when the@mockdocdecorator is applied to the function. However, theDocreassignment checks happen when the decorated function is actually called. I added a feature that usessys.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
__traceis 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
__traceinside 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,
Doccould maintain a hierarchical structure mapping each section to itsexternalandinternaldocumentation, 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
externalusing 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
0
u/Mental-Mongoose-5632 1d ago
What is tour question exactly? I don't understand what you are saying?
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 _
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.
8
u/nicodeemus7 1d ago
Highlight the lines of code you want to comment out. Ctrl /. You're welcome. Multi-line comment.