GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — bpo-43682: Make staticmethod objects callable by vstinner · Pull Request #25117 · python/cpython

Conversation

@vstinner

@vstinner vstinner commented Mar 31, 2021 •
edited by bedevere-bot
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member

Static methods created by the @staticmethod decorator are now
callable as regular functions.

https://bugs.python.org/issue43682

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@the-knights-who-say-ni the-knights-who-say-ni added the CLA signed label Mar 31, 2021
@bedevere-bot bedevere-bot added the awaiting core review label Mar 31, 2021
@vstinner

vstinner commented Apr 2, 2021

Copy link
Copy Markdown
Member Author

PR rebased to fix the enum doc fix from master.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Apr 7, 2021

Copy link
Copy Markdown
Member Author

I rebased my PR and reverted the io and_pyio changes (they should be made in a separated PR).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Apr 8, 2021

Copy link
Copy Markdown
Member Author

@serhiy-storchaka @gvanrossum @rhettinger: so, what do you think? this idea was rejected in 2015, but came back in 2021 and Guido likes the idea to make static methods callable. See also PR #25268 which makes staticmethod more "usable" to be used directly as a function.

It's a tricky topic. We could also require to always use staticmethod when using directly a function as a method:

class MyClass:
    method = staticmethod(func)

But sometimes, we need functions which can be used directly as method without staticmethod(), to mimick built-in function. Well see https://bugs.python.org/issue43682 and https://bugs.python.org/issue20309 discussions ;-)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gvanrossum

gvanrossum commented Apr 8, 2021

Copy link
Copy Markdown
Member

I like the idea. Why was it rejected in 2015?

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@serhiy-storchaka

serhiy-storchaka commented Apr 9, 2021

Copy link
Copy Markdown
Member

staticmethod is a simple thing. It is only purpose to decorate functions before setting them as class attribute if we do not want to make them instance methods. There is no use cases for calling staticmethod object.

Victor want to replace OpenWrapper with staticmethod(open) to keep builtin open a non-descriptor just for the case if some user code sets it as class attribute. This is very special case, and I think that it would be better to apply staticmethod immediately before setting a class attribute:

class MyClass:
    open = staticmethod(open)

In any case staticmethod is not perfect replacement of OpenWrapper. If we go this way, we should at least implement __doc__ for staticmethod, and preferably __repr__, __name__, __module__, __qualname__, __text_signature__, etc, etc. It is a big issue.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Apr 9, 2021

Copy link
Copy Markdown
Member Author

In any case staticmethod is not perfect replacement of OpenWrapper. If we go this way, we should at least implement doc for staticmethod, and preferably repr, name, module, qualname, text_signature, etc, etc. It is a big issue.

I solved this in PR #25268, did you see my PR?

This is very special case

See also https://bugs.python.org/issue43682#msg389907:

My usecase is to avoid any behavior difference between io.open and _pyio.open functions: PEP 399 "Pure Python/C Accelerator Module Compatibility Requirements". Currently, this is a very subtle difference when it's used to define a method.

It's a similar issue than PEP 570 (positional-only arguments) solved for Python re-implementation of a C extension. We should either prevent C extensions to behave than Python, or we should allow pure Python code to behave the same.

Here the issue is complex (changing built-in functions or Python functions to add/remove descriptor), and I propose to only change staticmethod(). In the whole stdlib, I'm only aware of io.open which is used sometimes directly to define a method (without @staticmethod). But the issue happens with any built-in function whch is reimplemented in Python (e.g. in PyPy).

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Apr 9, 2021

Copy link
Copy Markdown
Member Author

I merged my PR #25268 and rebased this PR on top of it.

By the way, just for consistency, should we also make class methods callable? I have no use case for that :-)

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gvanrossum

gvanrossum commented Apr 9, 2021 via email •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member
By the way, just for consistency, should we also make class methods
callable? I have no use case for that :-) I thought those are callable already.
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Apr 9, 2021

Copy link
Copy Markdown
Member Author

Guido:

I thought those are callable already.

Me too, but hey, static methods and class methods are weird!

$ python3
Python 3.9.2 (default, Feb 20 2021, 00:00:00) 
>>> def func(): pass
... 
>>> wrapper = classmethod(func)
>>> wrapper()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'classmethod' object is not callable

It's only callable when it goes throught the descriptor!

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Apr 9, 2021 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Member Author

Well, ignore my idea of making class methods callable. It makes no sense since it doesn't pass the class in this case. Example:

def func(cls):
    print(cls)

class MyClass:
    method = classmethod(func)

MyClass.method()
MyClass().method()
MyClass.__dict__['method']()

The last call fails because it doesn't go trought the descriptor, and so the class method doesn't get the class argument.

Output:

vstinner@apu$ python3 x.py 
<class '__main__.MyClass'>
<class '__main__.MyClass'>
Traceback (most recent call last):
  File "/home/vstinner/python/master/x.py", line 9, in <module>
    MyClass.__dict__['method']()
TypeError: 'classmethod' object is not callable
All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@gvanrossum

gvanrossum commented Apr 10, 2021

Copy link
Copy Markdown
Member

Well, ignore my idea of making class methods callable. It makes no sense since it doesn't pass the class in this case.

You should pass that in explicitly in that case. I guess calling classmethod(f)(C) could do the same thing as classmethod(f).__func__(C). But yeah, this doesn't seem as important as making staticmethod(f) callable, because there's nothing complicated there.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

gvanrossum
gvanrossum approved these changes Apr 10, 2021
View reviewed changes

@gvanrossum gvanrossum left a comment

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

But please first fix the comment about class methods being callable.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread Lib/test/test_decorators.py Outdated
Comment on lines 99 to 100

@gvanrossum gvanrossum Apr 10, 2021

Copy link
Copy Markdown
Member

There was a problem hiding this comment.

Choose a reason for hiding this comment

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

Class methods aren't callable yet.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
@bedevere-bot bedevere-bot added awaiting merge and removed awaiting core review labels Apr 10, 2021
gvanrossum
gvanrossum approved these changes Apr 10, 2021
View reviewed changes
vstinner added 2 commits April 11, 2021 23:49
@vstinner
bpo-43682: Make staticmethod objects callable
a43910e
Static methods (@staticmethod) are now callable as regular functions.
@vstinner
Update test_pydoc
65c8a6a
@vstinner

vstinner commented Apr 11, 2021

Copy link
Copy Markdown
Member Author

I fixed test_pydoc which shows a nice enhancement of this PR ;-)

         self.assertEqual(self._get_summary_lines(X.__dict__['sm']),
-                         'sm(...)\n'
+                         'sm(x, y)\n'
                          '    A static method\n')

With this PR, inspect.signature() works on a static method, and returns the same signature than the wrapped callable object.

$ ./python
Python 3.10.0a7+
>>> def func(x: int, y: int) -> float: pass
... 
>>> import inspect
>>> inspect.signature(func)
<Signature (x: int, y: int) -> float>

>>> wrapper=staticmethod(func)
>>> inspect.signature(wrapper)
<Signature (x: int, y: int) -> float>

On Python 3.9 (and in master without this change), inspect.signature(wrapper) fails with "TypeError: <staticmethod...> is not a callable object".

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner
vstinner merged commit 553ee27 into python:master Apr 11, 2021
@bedevere-bot bedevere-bot removed the awaiting merge label Apr 11, 2021
@vstinner
vstinner deleted the callable_staticmethod branch April 11, 2021 22:21
@vstinner

vstinner commented Apr 11, 2021

Copy link
Copy Markdown
Member Author

Thanks for the review @gvanrossum.

gvanrossum and methane reacted with thumbs up emoji
All reactions
  • 👍 2 reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner

vstinner commented Apr 12, 2021

Copy link
Copy Markdown
Member Author

It is possible to wrap a static method into a new static method, staticmethod(staticmethod(func)) or put two @staticmethod decorators on the same function. It is inefficient, but I don't think that the staticmethod() constructor must return the first static method unchanged, since static methods are mutable.

Python 3.10.0a7+
>>> def func(): return 5
... 
>>> wrapper1 = staticmethod(func)
>>> wrapper2 = staticmethod(wrapper1)

>>> wrapper1
<staticmethod(<function func at 0x7fffea3071d0>)>
>>> wrapper2
<staticmethod(<staticmethod(<function func at 0x7fffea3071d0>)>)>

>>> wrapper2()
5

>>> wrapper2.x=1
>>> wrapper1.x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'staticmethod' object has no attribute 'x'

Moreover, I expect that most decorators have a similar issue. Maybe a linter or a debug check can warn on that, but I don't think that staticmethod() must raise an error or return the first (static method) wrapper unchanged.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@vstinner vstinner mentioned this pull request Jun 23, 2022
Make static methods created by @staticmethod callable #87848
Closed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@gvanrossum gvanrossum gvanrossum approved these changes

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

5 participants

@vstinner @gvanrossum @serhiy-storchaka @the-knights-who-say-ni @bedevere-bot
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.