Recreate Mario's half-pyramids using hashes (#) from CS50
Your code is quite nice. Your use of functions seem quite reasonable. And your style is nice too. Good job!

Calling print is expensive. To improve performance we can build the strings without using the for loop. For example to build \$n-(i + 1)\$ spaces we can use:
&gt;&gt;&gt; n = 12; i = 1
&gt;&gt;&gt; &quot; &quot; * (n - (i + 1))
'          '


To build the pyramid you can delegate the creation of the spaces to Python by using str.ljust and str.rjust.
These functions allow an easy to use way to build the pyramid.
&gt;&gt;&gt; 'abc'.ljust(10)
'abc       '
&gt;&gt;&gt; 'abc'.rjust(10)
'       abc'


Using print_spaces(l, n) is harder to understand than print(&quot;  &quot;).

Your use of comments are not great. Most of them can be understood by just reading the code.
I only found one of your comments to help.
Because it explains something that isn't particuarly clear in clear English.
# there's two spaces between two pyramid. at the print_spaces function range(n - i+1)
# so initialize l = 1 and n = 4, would always give (4-(1+1)) = 2

However this is a sign that your code is complicated.
Which if you fix will make your comment redundent.


If we put these together we can greatly simplify your code.
from sys import exit
from cs50 import get_int

def main():
    while True:
        num = get_int(&quot;Height: &quot;)
        if num &gt;= 1 and num &lt;= 8:
            break

    print_pyramid(num)
    exit(0)

def print_pyramid(n):
    for i in range(n):
        hashes = &quot;#&quot; * (i + 1)
        print(hashes.rjust(n) + &quot;  &quot; + hashes)

main()

There are still some more improvements that can be made.

Using sys.exit(0) is abnormal. It is normally best to just let Python handle that for you.

We can add an if __name__ == &quot;__main__&quot; guard to prevent your code from running if you import it accidentally.
This is because if you use import filename you can import this program by accident and we don't want to run the main function if that happens.

I personally am a fan of always using the &lt; operator.
It just feels natural to see numbers ordered from smallest to largest, left to right.

We can Pythonify the print in print_pyramid by using the Format Specification Mini-Language.
There are three parts to this.

By using a format string we can enter values where we want them in a formatted string.
&gt;&gt;&gt; a = 1; b = 2; c = 3
&gt;&gt;&gt; f&quot;{b}:{a} -&gt; {c}&quot;
'2:1 -&gt; 3'
&gt;&gt;&gt; &quot;{b}:{a} -&gt; {c}&quot;  # Not a format string
'{b}:{a} -&gt; {c}'
&gt;&gt;&gt; &quot;{b}:{a} -&gt; {c}&quot;.format(a=a, b=b, c=c)  # Old style format strings
'2:1 -&gt; 3'


We can use the alignment option to align the values where we want them to.
&gt;&gt;&gt; a = 1; b = 2; c = 3
&gt;&gt;&gt; f&quot;{b:&gt;3}:{a:&lt;3} -&gt; {c:^3}&quot;
'  2:1   -&gt;  3 '


We can enter the width using the same {} syntax as the values.
&gt;&gt;&gt; a = 1; b = 2; c = 3; width = 3
&gt;&gt;&gt; f&quot;{b:&gt;{width}}:{a:&lt;{width}} -&gt; {c:^{width}}&quot;
'  2:1   -&gt;  3 '





from sys import exit
from cs50 import get_int


def main():
    while True:
        num = get_int(&quot;Height: &quot;)
        if 1 &lt;= num and num &lt;= 8:
            break
    print_pyramid(num)


def print_pyramid(n):
    for i in range(n):
        hashes = &quot;#&quot; * (i + 1)
        print(f&quot;{hashes:&gt;{n}}  {hashes}&quot;)


if __name__ == &quot;__main__&quot;:
    main()