Skip to content

Use Union[bytes, Text] for paths in os.py #1054

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 21, 2017

Conversation

euresti
Copy link
Contributor

@euresti euresti commented Mar 21, 2017

In order to unify these two versions I'm making all paths be _PathType = Union[bytes, Text]
Fixes #439

In order to unify these two versions I'm making all paths be _PathType = Union[bytes, Text]
Fixes python#439
def symlink(source: _PathType, link_name: _PathType) -> None: ...
def unlink(path: _PathType) -> None: ...
def utime(path: _PathType, times: Optional[Tuple[int, int]]) -> None: ...
def walk(top: _PathType, topdown: bool = ..., onerror: Any = ...,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AnyStr is actually correct here: os.walk returns bytes if you give it bytes and Text if you give it Text. Besides, we should generally avoid Unions in return types.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. Went a bit too far here.

def execle(file: _PathType, *args) -> None: ...
def execlp(file: _PathType, *args) -> None: ...
def execlpe(file: _PathType, *args) -> None: ...
def execv(path: _PathType, args: Union[Tuple[_PathType], List[_PathType]]) -> None: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PathType is an unfortunate name for the args, since they don't have to be Paths.

I think the best solution might be to add a second type alias (or just use Union directly here), so we can easily change the _PathType alias later to include os.PathLike in Python 3.6+.

This also applies to the other exec* function and to the spawn* functions below.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

def execvp(file: AnyStr, args: Union[Tuple[AnyStr], List[AnyStr]]) -> None: ...
def execvpe(file: AnyStr, args: Union[Tuple[AnyStr], List[AnyStr]],
def execv(path: _PathType, args: Union[Tuple[_PathType], List[_PathType]]) -> None: ...
def execve(path: _PathType, args: Union[Tuple[_PathType], List[_PathType]], env: Mapping[_PathType, _PathType]) -> None: ...
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comments as on the Python 2 version.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

@JelleZijlstra
Copy link
Member

Would it make sense to unify the Python 2 and 3 stubs first, or are the differences still big enough to make that not feasible?

@euresti
Copy link
Contributor Author

euresti commented Mar 21, 2017

Would it make sense to unify the Python 2 and 3 stubs first, or are the differences still big enough to make that not feasible?

I did start with a a full merge but thought splitting it up might be useful. I think my plan is:

  1. _PathType
  2. _StatVFS -> statvfs_result, make _Environs equal
  3. Merge all the comments (Lots of # only Unix)
  4. Merge everything else (adding py3 checks to 2) to make sure tests pass
  5. Rename files

I think that will make it easier to review everything and not miss anything. I'm still pretty new to github so I don't know if splitting up a diff into multiple PRs is useful or not.

@JelleZijlstra
Copy link
Member

Thanks, that makes sense. Splitting things up definitely makes it easier to review.

@JelleZijlstra JelleZijlstra merged commit 2e1ebab into python:master Mar 21, 2017
@JelleZijlstra
Copy link
Member

Thanks!

@euresti euresti deleted the path_type branch March 21, 2017 08:33
@sproshev
Copy link
Contributor

I think os.PathLike introduced in Python 3.6 could be easily supported by changing

_PathType = Union[bytes, Text]

to

if sys.version_info >= (3, 6):
    _PathType = Union[bytes, Text, PathLike]
else:
    _PathType = Union[bytes, Text]

in stdlib/3/os/__init__.pyi

@gvanrossum
Copy link
Member

Hey @JelleZijlstra Welcome to the crew! Can you please always use Squash Merge to merge PRs? We don't want the contributor's local history preserved forever in our master branch; the history remains available for archeologists in the (closed) PR itself. For this PR what's done is done, but in the future I'd really appreciate it.

(Also, we probably need to write this up somewhere.)

@JelleZijlstra
Copy link
Member

Oops, sorry for that. I'll squash in the future.

@JelleZijlstra
Copy link
Member

JelleZijlstra commented Mar 22, 2017

This caused some errors in mypy's test suite:

FAILURE  #121 check stdlibsamples (3.2)

subprocess.py:1322: error: Argument 2 to "execvp" has incompatible type List[str]; expected "Union[Tuple[Union[bytes, str]], List[Union[bytes, str]]]"
subprocess.py:1324: error: Argument 2 to "execvpe" has incompatible type List[str]; expected "Union[Tuple[Union[bytes, str]], List[Union[bytes, str]]]"

I think mypy is right here: the argument to List is invariant, so List[str] should not be compatible with List[Union[bytes, str]]. We can fix this by making it take Union[Tuple[Union[bytes, str], ...], List[bytes], List[str], List[Union[bytes, str]] instead, so I'll submit a PR to that effect (but not right now).

We can't make these functions take a Sequence[Union[bytes, str]] because the exec* functions really do accept only tuples and lists.

@JelleZijlstra JelleZijlstra mentioned this pull request Mar 22, 2017
@gvanrossum
Copy link
Member

Sounds like a plan. Maybe the union should even include List[bytes], List[Text], List[Union[bytes, Text]]? Also please change Tuple[Union[bytes, Text]] to Tuple[Union[bytes, Text], ...] (otherwise only tuples of length 1 are accepted).

@euresti
Copy link
Contributor Author

euresti commented Mar 22, 2017

Oops. Sorry about that. Will send out a PR right now!

@JelleZijlstra
Copy link
Member

This was fixed in #1075.

n8henrie added a commit to n8henrie/typeshed that referenced this pull request Jan 9, 2018
It seems that AnyStr has the main advantage of ensuring that multiple arguments (or argument(s) and return values) have the same type (i.e. `str` and `bytes` are both okay but shouldn't be mixed). However it makes it significantly more difficult to cast to a single type.

With a Union, something like:

```python
def foo(a: Union[bytes, Text]) -> Text:
    if isinstance(a, bytes):
        a = a.decode()
    return "Hello, " + a
```

works fine. With AnyStr, you have to define a new intermediate variable, something like:

```python
def foo(a: AnyStr) -> str:
    if isinstance(a, bytes):
        b = a.decode()
    else:
        b = a
    return "Hello, " + b
```

If there's no advantage to using AnyStr (since there's no other AnyStr argument or return value), I think the Union would be simpler, have no significant disadvantage, and there is [precedent for similar changes](python#1054).

> It's only the case that if there's exactly one parameter of type exactly AnyStr, and no other use of AnyStr in the signature, then Union[str, bytes] should be acceptable.

-  [gvanrossum](python#439 (comment))

Discussion: 

- python#1054
- python/mypy#1141
- python#439
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants