Determine if an attribute can be replaced with a property.

    Args:
        names: The complete list of all attribute names for the class.
        attrs: The attribute dict returned by __prepare__.
        annotations: A mapping of all defined annotations for the class.
        attr: The attribute to test.

    Returns:
        True if the attribute can be replaced with a property; else False.

def _is_propertyable(
    names,  # type: List[str]
    attrs,  # type: Dict[str, Any]
    annotations,  # type: Dict[str, type]
    attr,  # Dict[str, Any]
):
    # type: (...) -> bool
    """Determine if an attribute can be replaced with a property.

    Args:
        names: The complete list of all attribute names for the class.
        attrs: The attribute dict returned by __prepare__.
        annotations: A mapping of all defined annotations for the class.
        attr: The attribute to test.

    Returns:
        True if the attribute can be replaced with a property; else False.
    """
    return (
        attr in annotations
        and not attr.startswith("_")
        and not attr.isupper()
        and "__{}".format(attr) not in names
        and not isinstance(getattr(attrs, attr, None), types.MethodType)
    )