docs: def from_string(cls, string, *, default_func=None):
          '''Construct a Service from a string.
  
          If default_func is provided and any ServicePart is missing, it is called with
          default_func(protocol, part) to obtain the missing part.
          '''
          if not isinstance(string, str):
              raise TypeError(f'service must be a string: {string}')
  
          parts = string.split('://', 1)
          if len(parts) == 2:
              protocol, address = parts
          else:
              item, = parts
              protocol = None
              if default_func:
                  if default_func(item, ServicePart.HOST) and default_func(item, ServicePart.PORT):
                      protocol, address = item, ''
                  else:
                      protocol, address = default_func(None, ServicePart.PROTOCOL), item
              if not protocol:
                  raise ValueError(f'invalid service string: {string}')
  
          if default_func:
              default_func = partial(default_func, protocol.lower())
          address = NetAddress.from_string(address, default_func=default_func)
          return cls(protocol, address)