Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 81 additions & 26 deletions Lib/fractions.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,23 @@ def _round_to_figures(n, d, figures):
return sign, significand, exponent


# Pattern for matching non-float-style format specifications.
_GENERAL_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
(?:
(?P<fill>.)?
(?P<align>[<>=^])
)?
(?P<sign>[-+ ]?)
# Alt flag forces a slash and denominator in the output, even for
# integer-valued Fraction objects.
(?P<alt>\#)?
# We don't implement the zeropad flag since there's no single obvious way
# to interpret it.
(?P<minimumwidth>0|[1-9][0-9]*)?
(?P<thousands_sep>[,_])?
""", re.DOTALL | re.VERBOSE).fullmatch


# Pattern for matching float-style format specifications;
# supports 'e', 'E', 'f', 'F', 'g', 'G' and '%' presentation types.
_FLOAT_FORMAT_SPECIFICATION_MATCHER = re.compile(r"""
Expand Down Expand Up @@ -414,27 +431,42 @@ def __str__(self):
else:
return '%s/%s' % (self._numerator, self._denominator)

def __format__(self, format_spec, /):
"""Format this fraction according to the given format specification."""

# Backwards compatiblility with existing formatting.
if not format_spec:
return str(self)
def _format_general(self, match):
"""Helper method for __format__.

Handles fill, alignment, signs, and thousands separators in the
case of no presentation type.
"""
# Validate and parse the format specifier.
match = _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec)
if match is None:
raise ValueError(
f"Invalid format specifier {format_spec!r} "
f"for object of type {type(self).__name__!r}"
)
elif match["align"] is not None and match["zeropad"] is not None:
# Avoid the temptation to guess.
raise ValueError(
f"Invalid format specifier {format_spec!r} "
f"for object of type {type(self).__name__!r}; "
"can't use explicit alignment when zero-padding"
)
fill = match["fill"] or " "
align = match["align"] or ">"
pos_sign = "" if match["sign"] == "-" else match["sign"]
alternate_form = bool(match["alt"])
minimumwidth = int(match["minimumwidth"] or "0")
thousands_sep = match["thousands_sep"] or ''

# Determine the body and sign representation.
n, d = self._numerator, self._denominator
if d > 1 or alternate_form:
body = f"{abs(n):{thousands_sep}}/{d:{thousands_sep}}"
else:
body = f"{abs(n):{thousands_sep}}"
sign = '-' if n < 0 else pos_sign

# Pad with fill character if necessary and return.
padding = fill * (minimumwidth - len(sign) - len(body))
if align == ">":
return padding + sign + body
elif align == "<":
return sign + body + padding
elif align == "^":
half = len(padding) // 2
return padding[:half] + sign + body + padding[half:]
else: # align == "="
return sign + padding + body

def _format_float_style(self, match):
"""Helper method for __format__; handles float presentation types."""
fill = match["fill"] or " "
align = match["align"] or ">"
pos_sign = "" if match["sign"] == "-" else match["sign"]
Expand All @@ -449,6 +481,9 @@ def __format__(self, format_spec, /):
trim_point = not alternate_form
exponent_indicator = "E" if presentation_type in "EFG" else "e"

if align == '=' and fill == '0':
zeropad = True

# Round to get the digits we need, figure out where to place the point,
# and decide whether to use scientific notation. 'point_pos' is the
# relative to the _end_ of the digit string: that is, it's the number
Expand Down Expand Up @@ -530,7 +565,25 @@ def __format__(self, format_spec, /):
else: # align == "="
return sign + padding + body

def _operator_fallbacks(monomorphic_operator, fallback_operator):
def __format__(self, format_spec, /):
"""Format this fraction according to the given format specification."""

if match := _GENERAL_FORMAT_SPECIFICATION_MATCHER(format_spec):
return self._format_general(match)

if match := _FLOAT_FORMAT_SPECIFICATION_MATCHER(format_spec):
# Refuse the temptation to guess if both alignment _and_
# zero padding are specified.
if match["align"] is None or match["zeropad"] is None:
return self._format_float_style(match)

raise ValueError(
f"Invalid format specifier {format_spec!r} "
f"for object of type {type(self).__name__!r}"
)

def _operator_fallbacks(monomorphic_operator, fallback_operator,
handle_complex=True):
"""Generates forward and reverse operators given a purely-rational
operator and a function from the operator module.

Expand Down Expand Up @@ -617,7 +670,7 @@ def forward(a, b):
return monomorphic_operator(a, Fraction(b))
elif isinstance(b, float):
return fallback_operator(float(a), b)
elif isinstance(b, complex):
elif handle_complex and isinstance(b, complex):
return fallback_operator(complex(a), b)
else:
return NotImplemented
Expand All @@ -630,7 +683,7 @@ def reverse(b, a):
return monomorphic_operator(Fraction(a), b)
elif isinstance(a, numbers.Real):
return fallback_operator(float(a), float(b))
elif isinstance(a, numbers.Complex):
elif handle_complex and isinstance(a, numbers.Complex):
return fallback_operator(complex(a), complex(b))
else:
return NotImplemented
Expand Down Expand Up @@ -781,22 +834,22 @@ def _floordiv(a, b):
"""a // b"""
return (a.numerator * b.denominator) // (a.denominator * b.numerator)

__floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv)
__floordiv__, __rfloordiv__ = _operator_fallbacks(_floordiv, operator.floordiv, False)

def _divmod(a, b):
"""(a // b, a % b)"""
da, db = a.denominator, b.denominator
div, n_mod = divmod(a.numerator * db, da * b.numerator)
return div, Fraction(n_mod, da * db)

__divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod)
__divmod__, __rdivmod__ = _operator_fallbacks(_divmod, divmod, False)

def _mod(a, b):
"""a % b"""
da, db = a.denominator, b.denominator
return Fraction((a.numerator * db) % (b.numerator * da), da * db)

__mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod)
__mod__, __rmod__ = _operator_fallbacks(_mod, operator.mod, False)

def __pow__(a, b):
"""a ** b
Expand Down Expand Up @@ -825,8 +878,10 @@ def __pow__(a, b):
# A fractional power will generally produce an
# irrational number.
return float(a) ** float(b)
else:
elif isinstance(b, (float, complex)):
return float(a) ** b
else:
return NotImplemented

def __rpow__(b, a):
"""a ** b"""
Expand Down
27 changes: 17 additions & 10 deletions Lib/ftplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -900,11 +900,17 @@ def ftpcp(source, sourcename, target, targetname = '', type = 'I'):

def test():
'''Test program.
Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...
Usage: ftplib [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...

-d dir
-l list
-p password
Options:
-d increase debugging level
-r[file] set alternate ~/.netrc file

Commands:
-l[dir] list directory
-d[dir] change the current directory
-p toggle passive and active mode
file retrieve the file and write it to stdout
'''

if len(sys.argv) < 2:
Expand All @@ -930,15 +936,14 @@ def test():
netrcobj = netrc.netrc(rcfile)
except OSError:
if rcfile is not None:
sys.stderr.write("Could not open account file"
" -- using anonymous login.")
print("Could not open account file -- using anonymous login.",
file=sys.stderr)
else:
try:
userid, acct, passwd = netrcobj.authenticators(host)
except KeyError:
except (KeyError, TypeError):
# no account for host
sys.stderr.write(
"No account -- using anonymous login.")
print("No account -- using anonymous login.", file=sys.stderr)
ftp.login(userid, passwd, acct)
for file in sys.argv[2:]:
if file[:2] == '-l':
Expand All @@ -951,7 +956,9 @@ def test():
ftp.set_pasv(not ftp.passiveserver)
else:
ftp.retrbinary('RETR ' + file, \
sys.stdout.write, 1024)
sys.stdout.buffer.write, 1024)
sys.stdout.buffer.flush()
sys.stdout.flush()
ftp.quit()


Expand Down
Loading
Loading