Repository: astropy/astropy

Issue:
Composite units with strange unicode symbols do not work
### Description

One can use units like "M⊙" work but "M⊙/yr" does not. The problem is special-casing of non-composite units (which does work), but in composite units it appears the solar symbol is not recognized as being part of the M.

### Expected behavior

I'd expect composite units to work as well.

### How to Reproduce

```python
import astropy.units as u
u.Unit("M⊙/yr")
ValueError: 'M⊙/yr' did not parse as unit: At col 0, M is not a valid unit. Did you mean m? If this is meant to be a custom unit, define it with 'u.def_unit'. To have it recognized inside a file reader or other code, enable it with 'u.add_enabled_units'. For details, see https://docs.astropy.org/en/latest/units/combining_and_defining.html
```

Discussion:
github-actions[bot]: Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  - [ ] Do the proposed changes actually accomplish desired goals?
  - [ ] Do the proposed changes follow the [Astropy coding guidelines](https://docs.astropy.org/en/latest/development/codeguide.html)?
  - [ ] Are tests added/updated as required? If so, do they follow the [Astropy testing guidelines](https://docs.astropy.org/en/latest/development/testguide.html)?
  - [ ] Are docs added/updated as required? If so, do they follow the [Astropy documentation guidelines](https://docs.astropy.org/en/latest/development/docguide.html)?
  - [ ] Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for [rebase](https://docs.astropy.org/en/latest/development/development_details.html#rebase-if-necessary) and [squash](https://docs.astropy.org/en/latest/development/development_details.html#squash-if-necessary).
  - [ ] Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the [bot](https://docs.astropy.org/en/latest/development/development_details.html#pre-commit).
  - [ ] Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  - [ ] Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  - [ ] At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) *before* merge.
mhvk: Now also includes a fix for odd CDS units (which end in 0). None very likely to be encountered, but it really brings out the risk of using multiple paths to determine what unit is intended.

Patch:
diff --git a/astropy/units/format/cds.py b/astropy/units/format/cds.py
index ac691da037..02b8c04d48 100644
--- a/astropy/units/format/cds.py
+++ b/astropy/units/format/cds.py
@@ -106,8 +106,10 @@ class CDS(Generic):
             r"[x×]"
             return t
 
+        # Most units are just combinations of letters with no numbers, but there
+        # are a few special ones (\h is Planch constant) and three that end in 0.
         def t_UNIT(t):
-            r"\%|°|\\h|((?!\d)\w)+"
+            r"%|°|\\h|(a|eps|mu)0|((?!\d)\w)+"
             t.value = cls._get_unit(t)
             return t
 
diff --git a/astropy/units/format/cds_lextab.py b/astropy/units/format/cds_lextab.py
index 94b353f944..2b9aac73d2 100644
--- a/astropy/units/format/cds_lextab.py
+++ b/astropy/units/format/cds_lextab.py
@@ -14,7 +14,7 @@ _lextokens    = set(('CLOSE_BRACKET', 'CLOSE_PAREN', 'DIMENSIONLESS', 'DIVISION'
 _lexreflags   = 32
 _lexliterals  = ''
 _lexstateinfo = {'INITIAL': 'inclusive'}
-_lexstatere   = {'INITIAL': [('(?P<t_UFLOAT>((\\d+\\.?\\d+)|(\\.\\d+))([eE][+-]?\\d+)?)|(?P<t_UINT>\\d+)|(?P<t_SIGN>[+-](?=\\d))|(?P<t_X>[x×])|(?P<t_UNIT>\\%|°|\\\\h|((?!\\d)\\w)+)|(?P<t_DIMENSIONLESS>---|-)|(?P<t_PRODUCT>\\.)|(?P<t_OPEN_PAREN>\\()|(?P<t_CLOSE_PAREN>\\))|(?P<t_OPEN_BRACKET>\\[)|(?P<t_CLOSE_BRACKET>\\])|(?P<t_DIVISION>/)', [None, ('t_UFLOAT', 'UFLOAT'), None, None, None, None, ('t_UINT', 'UINT'), ('t_SIGN', 'SIGN'), ('t_X', 'X'), ('t_UNIT', 'UNIT'), None, ('t_DIMENSIONLESS', 'DIMENSIONLESS'), (None, 'PRODUCT'), (None, 'OPEN_PAREN'), (None, 'CLOSE_PAREN'), (None, 'OPEN_BRACKET'), (None, 'CLOSE_BRACKET'), (None, 'DIVISION')])]}
+_lexstatere   = {'INITIAL': [('(?P<t_UFLOAT>((\\d+\\.?\\d+)|(\\.\\d+))([eE][+-]?\\d+)?)|(?P<t_UINT>\\d+)|(?P<t_SIGN>[+-](?=\\d))|(?P<t_X>[x×])|(?P<t_UNIT>%|°|\\\\h|(a|eps|mu)0|((?!\\d)\\w)+)|(?P<t_DIMENSIONLESS>---|-)|(?P<t_PRODUCT>\\.)|(?P<t_OPEN_PAREN>\\()|(?P<t_CLOSE_PAREN>\\))|(?P<t_OPEN_BRACKET>\\[)|(?P<t_CLOSE_BRACKET>\\])|(?P<t_DIVISION>/)', [None, ('t_UFLOAT', 'UFLOAT'), None, None, None, None, ('t_UINT', 'UINT'), ('t_SIGN', 'SIGN'), ('t_X', 'X'), ('t_UNIT', 'UNIT'), None, None, ('t_DIMENSIONLESS', 'DIMENSIONLESS'), (None, 'PRODUCT'), (None, 'OPEN_PAREN'), (None, 'CLOSE_PAREN'), (None, 'OPEN_BRACKET'), (None, 'CLOSE_BRACKET'), (None, 'DIVISION')])]}
 _lexstateignore = {'INITIAL': ''}
 _lexstateerrorf = {'INITIAL': 't_error'}
 _lexstateeoff = {}
diff --git a/astropy/units/format/cds_parsetab.py b/astropy/units/format/cds_parsetab.py
index 20827a29e5..b7b07030fe 100644
--- a/astropy/units/format/cds_parsetab.py
+++ b/astropy/units/format/cds_parsetab.py
@@ -37,31 +37,31 @@ for _k, _v in _lr_goto_items.items():
 del _lr_goto_items
 _lr_productions = [
   ("S' -> main","S'",1,None,None,None),
-  ('main -> factor combined_units','main',2,'p_main','cds.py',147),
-  ('main -> combined_units','main',1,'p_main','cds.py',148),
-  ('main -> DIMENSIONLESS','main',1,'p_main','cds.py',149),
-  ('main -> OPEN_BRACKET combined_units CLOSE_BRACKET','main',3,'p_main','cds.py',150),
-  ('main -> OPEN_BRACKET DIMENSIONLESS CLOSE_BRACKET','main',3,'p_main','cds.py',151),
-  ('main -> factor','main',1,'p_main','cds.py',152),
-  ('combined_units -> product_of_units','combined_units',1,'p_combined_units','cds.py',166),
-  ('combined_units -> division_of_units','combined_units',1,'p_combined_units','cds.py',167),
-  ('product_of_units -> unit_expression PRODUCT combined_units','product_of_units',3,'p_product_of_units','cds.py',173),
-  ('product_of_units -> unit_expression','product_of_units',1,'p_product_of_units','cds.py',174),
-  ('division_of_units -> DIVISION unit_expression','division_of_units',2,'p_division_of_units','cds.py',183),
-  ('division_of_units -> combined_units DIVISION unit_expression','division_of_units',3,'p_division_of_units','cds.py',184),
-  ('unit_expression -> unit_with_power','unit_expression',1,'p_unit_expression','cds.py',193),
-  ('unit_expression -> OPEN_PAREN combined_units CLOSE_PAREN','unit_expression',3,'p_unit_expression','cds.py',194),
-  ('factor -> signed_float X UINT signed_int','factor',4,'p_factor','cds.py',203),
-  ('factor -> UINT X UINT signed_int','factor',4,'p_factor','cds.py',204),
-  ('factor -> UINT signed_int','factor',2,'p_factor','cds.py',205),
-  ('factor -> UINT','factor',1,'p_factor','cds.py',206),
-  ('factor -> signed_float','factor',1,'p_factor','cds.py',207),
-  ('unit_with_power -> UNIT numeric_power','unit_with_power',2,'p_unit_with_power','cds.py',222),
-  ('unit_with_power -> UNIT','unit_with_power',1,'p_unit_with_power','cds.py',223),
-  ('numeric_power -> sign UINT','numeric_power',2,'p_numeric_power','cds.py',232),
-  ('sign -> SIGN','sign',1,'p_sign','cds.py',238),
-  ('sign -> <empty>','sign',0,'p_sign','cds.py',239),
-  ('signed_int -> SIGN UINT','signed_int',2,'p_signed_int','cds.py',248),
-  ('signed_float -> sign UINT','signed_float',2,'p_signed_float','cds.py',254),
-  ('signed_float -> sign UFLOAT','signed_float',2,'p_signed_float','cds.py',255),
+  ('main -> factor combined_units','main',2,'p_main','cds.py',143),
+  ('main -> combined_units','main',1,'p_main','cds.py',144),
+  ('main -> DIMENSIONLESS','main',1,'p_main','cds.py',145),
+  ('main -> OPEN_BRACKET combined_units CLOSE_BRACKET','main',3,'p_main','cds.py',146),
+  ('main -> OPEN_BRACKET DIMENSIONLESS CLOSE_BRACKET','main',3,'p_main','cds.py',147),
+  ('main -> factor','main',1,'p_main','cds.py',148),
+  ('combined_units -> product_of_units','combined_units',1,'p_combined_units','cds.py',162),
+  ('combined_units -> division_of_units','combined_units',1,'p_combined_units','cds.py',163),
+  ('product_of_units -> unit_expression PRODUCT combined_units','product_of_units',3,'p_product_of_units','cds.py',169),
+  ('product_of_units -> unit_expression','product_of_units',1,'p_product_of_units','cds.py',170),
+  ('division_of_units -> DIVISION unit_expression','division_of_units',2,'p_division_of_units','cds.py',179),
+  ('division_of_units -> combined_units DIVISION unit_expression','division_of_units',3,'p_division_of_units','cds.py',180),
+  ('unit_expression -> unit_with_power','unit_expression',1,'p_unit_expression','cds.py',189),
+  ('unit_expression -> OPEN_PAREN combined_units CLOSE_PAREN','unit_expression',3,'p_unit_expression','cds.py',190),
+  ('factor -> signed_float X UINT signed_int','factor',4,'p_factor','cds.py',199),
+  ('factor -> UINT X UINT signed_int','factor',4,'p_factor','cds.py',200),
+  ('factor -> UINT signed_int','factor',2,'p_factor','cds.py',201),
+  ('factor -> UINT','factor',1,'p_factor','cds.py',202),
+  ('factor -> signed_float','factor',1,'p_factor','cds.py',203),
+  ('unit_with_power -> UNIT numeric_power','unit_with_power',2,'p_unit_with_power','cds.py',218),
+  ('unit_with_power -> UNIT','unit_with_power',1,'p_unit_with_power','cds.py',219),
+  ('numeric_power -> sign UINT','numeric_power',2,'p_numeric_power','cds.py',228),
+  ('sign -> SIGN','sign',1,'p_sign','cds.py',234),
+  ('sign -> <empty>','sign',0,'p_sign','cds.py',235),
+  ('signed_int -> SIGN UINT','signed_int',2,'p_signed_int','cds.py',244),
+  ('signed_float -> sign UINT','signed_float',2,'p_signed_float','cds.py',250),
+  ('signed_float -> sign UFLOAT','signed_float',2,'p_signed_float','cds.py',251),
 ]
diff --git a/astropy/units/format/generic.py b/astropy/units/format/generic.py
index 3f01d600da..7701ddea93 100644
--- a/astropy/units/format/generic.py
+++ b/astropy/units/format/generic.py
@@ -110,8 +110,11 @@ class Generic(Base):
             r"((sqrt)|(ln)|(exp)|(log)|(mag)|(dB)|(dex))(?=\ *\()"
             return t
 
+        # A possible unit is something that consists of characters not used
+        # for anything else: no spaces, no digits, signs, periods, stars,
+        # carets, parentheses or commas.
         def t_UNIT(t):
-            "%|([YZEPTGMkhdcmu\N{MICRO SIGN}npfazy]?'((?!\\d)\\w)+')|((?!\\d)\\w)+"
+            r"[^\s\d+\-\./\*\^\(\)\,]+"
             t.value = cls._get_unit(t)
             return t
 
@@ -457,6 +460,8 @@ class Generic(Base):
         elif not s.isascii():
             if s[0] == "\N{MICRO SIGN}":
                 s = "u" + s[1:]
+            elif s[0] == "°":
+                s = "deg" if len(s) == 1 else "deg_" + s[1:]
             if s[-1] in cls._prefixable_unit_symbols:
                 s = s[:-1] + cls._prefixable_unit_symbols[s[-1]]
             elif len(s) > 1 and s[-1] in cls._unit_suffix_symbols:
@@ -529,18 +534,11 @@ class Generic(Base):
     _regex_superscript: ClassVar[Pattern[str]] = re.compile(
         f"[{_superscripts}]?[{_superscripts[2:]}]+"
     )
-    _regex_deg: ClassVar[Pattern[str]] = re.compile("°([CF])?")
 
     @classmethod
     def _convert_superscript(cls, m: Match[str]) -> str:
         return f"({m.group().translate(cls._superscript_translations)})"
 
-    @classmethod
-    def _convert_deg(cls, m: Match[str]) -> str:
-        if len(m.string) == 1:
-            return "deg"
-        return m.string.replace("°", "deg_")
-
     @classmethod
     def parse(cls, s: str, debug: bool = False) -> UnitBase:
         if not isinstance(s, str):
@@ -559,8 +557,6 @@ class Generic(Base):
             # Translate superscripts to parenthesized numbers; this ensures
             # that mixes of superscripts and regular numbers fail.
             s = cls._regex_superscript.sub(cls._convert_superscript, s)
-            # Translate possible degrees.
-            s = cls._regex_deg.sub(cls._convert_deg, s)
 
         result = cls._do_parse(s, debug=debug)
         # Check for excess solidi, but exclude fractional exponents (accepted)
diff --git a/astropy/units/format/generic_lextab.py b/astropy/units/format/generic_lextab.py
index 752079bfb1..2f26cd155f 100644
--- a/astropy/units/format/generic_lextab.py
+++ b/astropy/units/format/generic_lextab.py
@@ -14,7 +14,7 @@ _lextokens    = set(('CARET', 'CLOSE_PAREN', 'COMMA', 'DOUBLE_STAR', 'FUNCNAME',
 _lexreflags   = 32
 _lexliterals  = ''
 _lexstateinfo = {'INITIAL': 'inclusive'}
-_lexstatere   = {'INITIAL': [("(?P<t_UFLOAT>((\\d+\\.?\\d*)|(\\.\\d+))([eE][+-]?\\d+)?)|(?P<t_UINT>\\d+)|(?P<t_SIGN>[+-](?=\\d))|(?P<t_FUNCNAME>((sqrt)|(ln)|(exp)|(log)|(mag)|(dB)|(dex))(?=\\ *\\())|(?P<t_UNIT>%|([YZEPTGMkhdcmuµnpfazy]?'((?!\\d)\\w)+')|((?!\\d)\\w)+)|(?P<t_DOUBLE_STAR>\\*\\*)|(?P<t_COMMA>\\,)|(?P<t_STAR>\\*)|(?P<t_PERIOD>\\.)|(?P<t_CARET>\\^)|(?P<t_OPEN_PAREN>\\()|(?P<t_CLOSE_PAREN>\\))|(?P<t_SOLIDUS>/)", [None, ('t_UFLOAT', 'UFLOAT'), None, None, None, None, ('t_UINT', 'UINT'), ('t_SIGN', 'SIGN'), ('t_FUNCNAME', 'FUNCNAME'), None, None, None, None, None, None, None, None, ('t_UNIT', 'UNIT'), None, None, None, (None, 'DOUBLE_STAR'), (None, 'COMMA'), (None, 'STAR'), (None, 'PERIOD'), (None, 'CARET'), (None, 'OPEN_PAREN'), (None, 'CLOSE_PAREN'), (None, 'SOLIDUS')])]}
+_lexstatere   = {'INITIAL': [('(?P<t_UFLOAT>((\\d+\\.?\\d*)|(\\.\\d+))([eE][+-]?\\d+)?)|(?P<t_UINT>\\d+)|(?P<t_SIGN>[+-](?=\\d))|(?P<t_FUNCNAME>((sqrt)|(ln)|(exp)|(log)|(mag)|(dB)|(dex))(?=\\ *\\())|(?P<t_UNIT>[^\\s\\d+\\-\\./\\*\\^\\(\\)\\,]+)|(?P<t_DOUBLE_STAR>\\*\\*)|(?P<t_COMMA>\\,)|(?P<t_STAR>\\*)|(?P<t_PERIOD>\\.)|(?P<t_CARET>\\^)|(?P<t_OPEN_PAREN>\\()|(?P<t_CLOSE_PAREN>\\))|(?P<t_SOLIDUS>/)', [None, ('t_UFLOAT', 'UFLOAT'), None, None, None, None, ('t_UINT', 'UINT'), ('t_SIGN', 'SIGN'), ('t_FUNCNAME', 'FUNCNAME'), None, None, None, None, None, None, None, None, ('t_UNIT', 'UNIT'), (None, 'DOUBLE_STAR'), (None, 'COMMA'), (None, 'STAR'), (None, 'PERIOD'), (None, 'CARET'), (None, 'OPEN_PAREN'), (None, 'CLOSE_PAREN'), (None, 'SOLIDUS')])]}
 _lexstateignore = {'INITIAL': ' '}
 _lexstateerrorf = {'INITIAL': 't_error'}
 _lexstateeoff = {}
diff --git a/astropy/units/format/generic_parsetab.py b/astropy/units/format/generic_parsetab.py
index 18f0b214a1..fb46b819a7 100644
--- a/astropy/units/format/generic_parsetab.py
+++ b/astropy/units/format/generic_parsetab.py
@@ -37,70 +37,70 @@ for _k, _v in _lr_goto_items.items():
 del _lr_goto_items
 _lr_productions = [
   ("S' -> main","S'",1,None,None,None),
-  ('main -> unit','main',1,'p_main','generic.py',196),
-  ('main -> structured_unit','main',1,'p_main','generic.py',197),
-  ('main -> structured_subunit','main',1,'p_main','generic.py',198),
-  ('structured_subunit -> OPEN_PAREN structured_unit CLOSE_PAREN','structured_subunit',3,'p_structured_subunit','generic.py',209),
-  ('structured_unit -> subunit COMMA','structured_unit',2,'p_structured_unit','generic.py',218),
-  ('structured_unit -> subunit COMMA subunit','structured_unit',3,'p_structured_unit','generic.py',219),
-  ('subunit -> unit','subunit',1,'p_subunit','generic.py',241),
-  ('subunit -> structured_unit','subunit',1,'p_subunit','generic.py',242),
-  ('subunit -> structured_subunit','subunit',1,'p_subunit','generic.py',243),
-  ('unit -> product_of_units','unit',1,'p_unit','generic.py',249),
-  ('unit -> factor product_of_units','unit',2,'p_unit','generic.py',250),
-  ('unit -> factor product product_of_units','unit',3,'p_unit','generic.py',251),
-  ('unit -> division_product_of_units','unit',1,'p_unit','generic.py',252),
-  ('unit -> factor division_product_of_units','unit',2,'p_unit','generic.py',253),
-  ('unit -> factor product division_product_of_units','unit',3,'p_unit','generic.py',254),
-  ('unit -> inverse_unit','unit',1,'p_unit','generic.py',255),
-  ('unit -> factor inverse_unit','unit',2,'p_unit','generic.py',256),
-  ('unit -> factor product inverse_unit','unit',3,'p_unit','generic.py',257),
-  ('unit -> factor','unit',1,'p_unit','generic.py',258),
-  ('division_product_of_units -> division_product_of_units division product_of_units','division_product_of_units',3,'p_division_product_of_units','generic.py',270),
-  ('division_product_of_units -> product_of_units','division_product_of_units',1,'p_division_product_of_units','generic.py',271),
-  ('inverse_unit -> division unit_expression','inverse_unit',2,'p_inverse_unit','generic.py',281),
-  ('factor -> factor_fits','factor',1,'p_factor','generic.py',287),
-  ('factor -> factor_float','factor',1,'p_factor','generic.py',288),
-  ('factor -> factor_int','factor',1,'p_factor','generic.py',289),
-  ('factor_float -> signed_float','factor_float',1,'p_factor_float','generic.py',295),
-  ('factor_float -> signed_float UINT signed_int','factor_float',3,'p_factor_float','generic.py',296),
-  ('factor_float -> signed_float UINT power numeric_power','factor_float',4,'p_factor_float','generic.py',297),
-  ('factor_int -> UINT','factor_int',1,'p_factor_int','generic.py',310),
-  ('factor_int -> UINT signed_int','factor_int',2,'p_factor_int','generic.py',311),
-  ('factor_int -> UINT power numeric_power','factor_int',3,'p_factor_int','generic.py',312),
-  ('factor_int -> UINT UINT signed_int','factor_int',3,'p_factor_int','generic.py',313),
-  ('factor_int -> UINT UINT power numeric_power','factor_int',4,'p_factor_int','generic.py',314),
-  ('factor_fits -> UINT power OPEN_PAREN signed_int CLOSE_PAREN','factor_fits',5,'p_factor_fits','generic.py',332),
-  ('factor_fits -> UINT power OPEN_PAREN UINT CLOSE_PAREN','factor_fits',5,'p_factor_fits','generic.py',333),
-  ('factor_fits -> UINT power signed_int','factor_fits',3,'p_factor_fits','generic.py',334),
-  ('factor_fits -> UINT power UINT','factor_fits',3,'p_factor_fits','generic.py',335),
-  ('factor_fits -> UINT SIGN UINT','factor_fits',3,'p_factor_fits','generic.py',336),
-  ('factor_fits -> UINT OPEN_PAREN signed_int CLOSE_PAREN','factor_fits',4,'p_factor_fits','generic.py',337),
-  ('product_of_units -> unit_expression product product_of_units','product_of_units',3,'p_product_of_units','generic.py',356),
-  ('product_of_units -> unit_expression product_of_units','product_of_units',2,'p_product_of_units','generic.py',357),
-  ('product_of_units -> unit_expression','product_of_units',1,'p_product_of_units','generic.py',358),
-  ('unit_expression -> function','unit_expression',1,'p_unit_expression','generic.py',369),
-  ('unit_expression -> unit_with_power','unit_expression',1,'p_unit_expression','generic.py',370),
-  ('unit_expression -> OPEN_PAREN product_of_units CLOSE_PAREN','unit_expression',3,'p_unit_expression','generic.py',371),
-  ('unit_with_power -> UNIT power numeric_power','unit_with_power',3,'p_unit_with_power','generic.py',380),
-  ('unit_with_power -> UNIT numeric_power','unit_with_power',2,'p_unit_with_power','generic.py',381),
-  ('unit_with_power -> UNIT','unit_with_power',1,'p_unit_with_power','generic.py',382),
-  ('numeric_power -> sign UINT','numeric_power',2,'p_numeric_power','generic.py',393),
-  ('numeric_power -> OPEN_PAREN paren_expr CLOSE_PAREN','numeric_power',3,'p_numeric_power','generic.py',394),
-  ('paren_expr -> sign UINT','paren_expr',2,'p_paren_expr','generic.py',403),
-  ('paren_expr -> signed_float','paren_expr',1,'p_paren_expr','generic.py',404),
-  ('paren_expr -> frac','paren_expr',1,'p_paren_expr','generic.py',405),
-  ('frac -> sign UINT division sign UINT','frac',5,'p_frac','generic.py',414),
-  ('sign -> SIGN','sign',1,'p_sign','generic.py',420),
-  ('sign -> <empty>','sign',0,'p_sign','generic.py',421),
-  ('product -> STAR','product',1,'p_product','generic.py',430),
-  ('product -> PERIOD','product',1,'p_product','generic.py',431),
-  ('division -> SOLIDUS','division',1,'p_division','generic.py',437),
-  ('power -> DOUBLE_STAR','power',1,'p_power','generic.py',443),
-  ('power -> CARET','power',1,'p_power','generic.py',444),
-  ('signed_int -> SIGN UINT','signed_int',2,'p_signed_int','generic.py',450),
-  ('signed_float -> sign UINT','signed_float',2,'p_signed_float','generic.py',456),
-  ('signed_float -> sign UFLOAT','signed_float',2,'p_signed_float','generic.py',457),
-  ('function_name -> FUNCNAME','function_name',1,'p_function_name','generic.py',463),
-  ('function -> function_name OPEN_PAREN main CLOSE_PAREN','function',4,'p_function','generic.py',469),
+  ('main -> unit','main',1,'p_main','generic.py',150),
+  ('main -> structured_unit','main',1,'p_main','generic.py',151),
+  ('main -> structured_subunit','main',1,'p_main','generic.py',152),
+  ('structured_subunit -> OPEN_PAREN structured_unit CLOSE_PAREN','structured_subunit',3,'p_structured_subunit','generic.py',163),
+  ('structured_unit -> subunit COMMA','structured_unit',2,'p_structured_unit','generic.py',172),
+  ('structured_unit -> subunit COMMA subunit','structured_unit',3,'p_structured_unit','generic.py',173),
+  ('subunit -> unit','subunit',1,'p_subunit','generic.py',196),
+  ('subunit -> structured_unit','subunit',1,'p_subunit','generic.py',197),
+  ('subunit -> structured_subunit','subunit',1,'p_subunit','generic.py',198),
+  ('unit -> product_of_units','unit',1,'p_unit','generic.py',204),
+  ('unit -> factor product_of_units','unit',2,'p_unit','generic.py',205),
+  ('unit -> factor product product_of_units','unit',3,'p_unit','generic.py',206),
+  ('unit -> division_product_of_units','unit',1,'p_unit','generic.py',207),
+  ('unit -> factor division_product_of_units','unit',2,'p_unit','generic.py',208),
+  ('unit -> factor product division_product_of_units','unit',3,'p_unit','generic.py',209),
+  ('unit -> inverse_unit','unit',1,'p_unit','generic.py',210),
+  ('unit -> factor inverse_unit','unit',2,'p_unit','generic.py',211),
+  ('unit -> factor product inverse_unit','unit',3,'p_unit','generic.py',212),
+  ('unit -> factor','unit',1,'p_unit','generic.py',213),
+  ('division_product_of_units -> division_product_of_units division product_of_units','division_product_of_units',3,'p_division_product_of_units','generic.py',226),
+  ('division_product_of_units -> product_of_units','division_product_of_units',1,'p_division_product_of_units','generic.py',227),
+  ('inverse_unit -> division unit_expression','inverse_unit',2,'p_inverse_unit','generic.py',238),
+  ('factor -> factor_fits','factor',1,'p_factor','generic.py',244),
+  ('factor -> factor_float','factor',1,'p_factor','generic.py',245),
+  ('factor -> factor_int','factor',1,'p_factor','generic.py',246),
+  ('factor_float -> signed_float','factor_float',1,'p_factor_float','generic.py',252),
+  ('factor_float -> signed_float UINT signed_int','factor_float',3,'p_factor_float','generic.py',253),
+  ('factor_float -> signed_float UINT power numeric_power','factor_float',4,'p_factor_float','generic.py',254),
+  ('factor_int -> UINT','factor_int',1,'p_factor_int','generic.py',267),
+  ('factor_int -> UINT signed_int','factor_int',2,'p_factor_int','generic.py',268),
+  ('factor_int -> UINT power numeric_power','factor_int',3,'p_factor_int','generic.py',269),
+  ('factor_int -> UINT UINT signed_int','factor_int',3,'p_factor_int','generic.py',270),
+  ('factor_int -> UINT UINT power numeric_power','factor_int',4,'p_factor_int','generic.py',271),
+  ('factor_fits -> UINT power OPEN_PAREN signed_int CLOSE_PAREN','factor_fits',5,'p_factor_fits','generic.py',289),
+  ('factor_fits -> UINT power OPEN_PAREN UINT CLOSE_PAREN','factor_fits',5,'p_factor_fits','generic.py',290),
+  ('factor_fits -> UINT power signed_int','factor_fits',3,'p_factor_fits','generic.py',291),
+  ('factor_fits -> UINT power UINT','factor_fits',3,'p_factor_fits','generic.py',292),
+  ('factor_fits -> UINT SIGN UINT','factor_fits',3,'p_factor_fits','generic.py',293),
+  ('factor_fits -> UINT OPEN_PAREN signed_int CLOSE_PAREN','factor_fits',4,'p_factor_fits','generic.py',294),
+  ('product_of_units -> unit_expression product product_of_units','product_of_units',3,'p_product_of_units','generic.py',313),
+  ('product_of_units -> unit_expression product_of_units','product_of_units',2,'p_product_of_units','generic.py',314),
+  ('product_of_units -> unit_expression','product_of_units',1,'p_product_of_units','generic.py',315),
+  ('unit_expression -> function','unit_expression',1,'p_unit_expression','generic.py',326),
+  ('unit_expression -> unit_with_power','unit_expression',1,'p_unit_expression','generic.py',327),
+  ('unit_expression -> OPEN_PAREN product_of_units CLOSE_PAREN','unit_expression',3,'p_unit_expression','generic.py',328),
+  ('unit_with_power -> UNIT power numeric_power','unit_with_power',3,'p_unit_with_power','generic.py',337),
+  ('unit_with_power -> UNIT numeric_power','unit_with_power',2,'p_unit_with_power','generic.py',338),
+  ('unit_with_power -> UNIT','unit_with_power',1,'p_unit_with_power','generic.py',339),
+  ('numeric_power -> sign UINT','numeric_power',2,'p_numeric_power','generic.py',350),
+  ('numeric_power -> OPEN_PAREN paren_expr CLOSE_PAREN','numeric_power',3,'p_numeric_power','generic.py',351),
+  ('paren_expr -> sign UINT','paren_expr',2,'p_paren_expr','generic.py',360),
+  ('paren_expr -> signed_float','paren_expr',1,'p_paren_expr','generic.py',361),
+  ('paren_expr -> frac','paren_expr',1,'p_paren_expr','generic.py',362),
+  ('frac -> sign UINT division sign UINT','frac',5,'p_frac','generic.py',371),
+  ('sign -> SIGN','sign',1,'p_sign','generic.py',377),
+  ('sign -> <empty>','sign',0,'p_sign','generic.py',378),
+  ('product -> STAR','product',1,'p_product','generic.py',387),
+  ('product -> PERIOD','product',1,'p_product','generic.py',388),
+  ('division -> SOLIDUS','division',1,'p_division','generic.py',393),
+  ('power -> DOUBLE_STAR','power',1,'p_power','generic.py',398),
+  ('power -> CARET','power',1,'p_power','generic.py',399),
+  ('signed_int -> SIGN UINT','signed_int',2,'p_signed_int','generic.py',405),
+  ('signed_float -> sign UINT','signed_float',2,'p_signed_float','generic.py',411),
+  ('signed_float -> sign UFLOAT','signed_float',2,'p_signed_float','generic.py',412),
+  ('function_name -> FUNCNAME','function_name',1,'p_function_name','generic.py',418),
+  ('function -> function_name OPEN_PAREN main CLOSE_PAREN','function',4,'p_function','generic.py',424),
 ]
diff --git a/docs/changes/units/17011.bugfix.rst b/docs/changes/units/17011.bugfix.rst
new file mode 100644
index 0000000000..94626e4f8e
--- /dev/null
+++ b/docs/changes/units/17011.bugfix.rst
@@ -0,0 +1,7 @@
+The unit parsers are now better at recognizing unusual composite
+units:
+
+- units involving special unicode symbols, like "L☉/pc²";
+- units that include CDS units ending in a 0, like "eps0/s";
+- units including the degree symbol, "°". For example, "°C/s" is no
+  longer incorrectly interpreted as "°C/s^2".


Test patch:
diff --git a/astropy/units/tests/test_format.py b/astropy/units/tests/test_format.py
index 9434cf6084..7d9bb69512 100644
--- a/astropy/units/tests/test_format.py
+++ b/astropy/units/tests/test_format.py
@@ -5,6 +5,7 @@ Regression tests for the units.format package
 
 from __future__ import annotations
 
+import re
 import warnings
 from contextlib import nullcontext
 from fractions import Fraction
@@ -16,7 +17,7 @@ from numpy.testing import assert_allclose
 
 from astropy import units as u
 from astropy.constants import si
-from astropy.units import PrefixUnit, Unit, UnitBase, UnitsWarning, dex
+from astropy.units import PrefixUnit, Unit, UnitBase, UnitsWarning, cds, dex
 from astropy.units import format as u_format
 from astropy.units.utils import is_effectively_unity
 from astropy.utils.exceptions import AstropyDeprecationWarning
@@ -123,6 +124,8 @@ def test_unit_grammar_fail(string):
         (["[cm/s2]"], dex(u.cm / u.s**2)),
         (["[K]"], dex(u.K)),
         (["[-]"], dex(u.dimensionless_unscaled)),
+        (["eps0/mu0"], cds.eps0 / cds.mu0),
+        (["a0.s"], cds.a0 * u.s),
     ),
     ids=lambda x: x.string,
 )
@@ -465,8 +468,6 @@ def test_units_available(unit_formatter_class, n_units):
 def test_cds_non_ascii_unit():
     """Regression test for #5350.  This failed with a decoding error as
     μas could not be represented in ascii."""
-    from astropy.units import cds
-
     with cds.enable():
         u.radian.find_equivalent_units(include_prefix_units=True)
 
@@ -956,7 +957,7 @@ def test_powers(power, expected):
         ("\N{MICRO SIGN}g", u.microgram),
         ("\N{GREEK SMALL LETTER MU}g", u.microgram),
         ("g\N{MINUS SIGN}1", u.g ** (-1)),
-        ("m\N{SUPERSCRIPT MINUS}\N{SUPERSCRIPT ONE}", 1 / u.m),
+        ("m\N{SUPERSCRIPT MINUS}\N{SUPERSCRIPT ONE}", u.m**-1),
         ("m s\N{SUPERSCRIPT MINUS}\N{SUPERSCRIPT ONE}", u.m / u.s),
         ("m\N{SUPERSCRIPT TWO}", u.m**2),
         ("m\N{SUPERSCRIPT PLUS SIGN}\N{SUPERSCRIPT TWO}", u.m**2),
@@ -985,6 +986,18 @@ def test_powers(power, expected):
 def test_unicode(string, unit):
     assert u_format.Generic.parse(string) == unit
     assert u.Unit(string) == unit
+    # Should work in composites too.
+    assert u.Unit(f"{string}/s") == unit / u.s
+    assert u.Unit(f"m {string}") == u.m * unit
+    assert u.Unit(f"{string} {string}") == unit**2
+    # Not obvious that "°2" should be "deg**2", but not easy to reject,
+    # and "R♃²" should work.  But don't run on examples with a space or that
+    # already end in a number.
+    if re.match(r"^\S*[^\d⁰¹²³⁴⁵⁶⁷⁸⁹]$", string):
+        assert u.Unit(f"{string}2") == unit**2
+    assert u.Unit(f"{string}/{string}") == u.dimensionless_unscaled
+    # Finally, check round-trip
+    assert u.Unit(unit.to_string("unicode")) == unit
 
 
 @pytest.mark.parametrize(