PEP-542: Dot Notation Assignment In Function Header (#205)
* PEP-542 initial commit * Fixes and improvements based on pre-feedback * Fix typo
This commit is contained in:
parent
fa86704be3
commit
38a49f70d8
|
@ -0,0 +1,161 @@
|
||||||
|
PEP: 542
|
||||||
|
Title: Dot Notation Assignment In Function Header
|
||||||
|
Version: $Revision$
|
||||||
|
Last-Modified: $Date$
|
||||||
|
Author: Markus Meskanen <markusmeskanen@gmail.com>
|
||||||
|
Status: Draft
|
||||||
|
Type: Standards Track
|
||||||
|
Content-Type: text/x-rst
|
||||||
|
Created: 10-February-2017
|
||||||
|
|
||||||
|
|
||||||
|
Abstract
|
||||||
|
========
|
||||||
|
|
||||||
|
Function definitions only allow simple function names to be used,
|
||||||
|
even though functions are assignable first class objects.
|
||||||
|
|
||||||
|
This PEP proposes adding support for assigning a function to
|
||||||
|
a class or instance attribute directly in the function
|
||||||
|
definition's header by using the dot notation to separate
|
||||||
|
the object from the function's name.
|
||||||
|
|
||||||
|
Although a similar feature, this PEP does not address general
|
||||||
|
assignment to anything that supports assignment, such as dict keys
|
||||||
|
and list indexes.
|
||||||
|
|
||||||
|
Rationale
|
||||||
|
=========
|
||||||
|
|
||||||
|
Currently if a function needs to be assigned to a class or instance
|
||||||
|
attribute, it requires an additional assignment statement to be made::
|
||||||
|
|
||||||
|
class MyClass:
|
||||||
|
...
|
||||||
|
|
||||||
|
my_instance = MyClass()
|
||||||
|
|
||||||
|
def my_function(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
# Assign to class attribute
|
||||||
|
MyClass.my_function = my_function
|
||||||
|
|
||||||
|
# Or assign to instance attribtue
|
||||||
|
my_instance.my_function = my_function
|
||||||
|
|
||||||
|
While this isn't usually an inconvenience, using dot notation to
|
||||||
|
assign directly in the function's header would greatly simplify this::
|
||||||
|
|
||||||
|
class MyClass:
|
||||||
|
...
|
||||||
|
|
||||||
|
my_instance = MyClass()
|
||||||
|
|
||||||
|
# Assign to class attribute
|
||||||
|
def MyClass.my_function(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
# Or assign to instance attribute
|
||||||
|
def my_instance.my_function(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
There are multiple reasons to use this functionality over
|
||||||
|
a standard class method, for example when the class is referenced
|
||||||
|
inside the function's header (such as with decorators and typing).
|
||||||
|
This is also useful when an instance requires a callback attribute::
|
||||||
|
|
||||||
|
class Menu:
|
||||||
|
def __init__(self, items=None, select_callback=None):
|
||||||
|
self.items = items if items is not None else []
|
||||||
|
self.select_callback = select_callback
|
||||||
|
|
||||||
|
my_menu = Menu([item1, item2])
|
||||||
|
|
||||||
|
def my_menu.select_callback(item_index, menu):
|
||||||
|
print(menu.items[item_index])
|
||||||
|
|
||||||
|
As opposed to::
|
||||||
|
|
||||||
|
my_menu = Menu([item1, item2])
|
||||||
|
|
||||||
|
def select_callback(item_index, menu):
|
||||||
|
print(menu.items[item_index])
|
||||||
|
my_menu.select_callback = select_callback
|
||||||
|
|
||||||
|
Or defining them in an "unnatural" order::
|
||||||
|
|
||||||
|
def select_callback(item_index, menu):
|
||||||
|
print(menu.items[item_index])
|
||||||
|
|
||||||
|
my_menu = Menu([item1, item2], select_callback)
|
||||||
|
|
||||||
|
It reads better than the "unnatural" way, since you already know at
|
||||||
|
the time of the function definition what it's goig to be used for.
|
||||||
|
It also saves one line of code while removing visual complexity.
|
||||||
|
|
||||||
|
The feature would also avoid leaving the function's name into
|
||||||
|
the global namespace::
|
||||||
|
|
||||||
|
eggs = 'something'
|
||||||
|
|
||||||
|
def Spam.eggs(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def Cheese.eggs(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
assert eggs == 'something'
|
||||||
|
|
||||||
|
Ideally this would be just syntastic sugar::
|
||||||
|
|
||||||
|
def x.y():
|
||||||
|
...
|
||||||
|
|
||||||
|
# Equals to
|
||||||
|
|
||||||
|
def y():
|
||||||
|
...
|
||||||
|
x.y = y
|
||||||
|
|
||||||
|
Similar to how decorators are syntastic sugar::
|
||||||
|
|
||||||
|
@decorate
|
||||||
|
def f():
|
||||||
|
...
|
||||||
|
|
||||||
|
# Equals to
|
||||||
|
|
||||||
|
def f():
|
||||||
|
...
|
||||||
|
f = decorate(f)
|
||||||
|
|
||||||
|
Implementation
|
||||||
|
==============
|
||||||
|
|
||||||
|
The `__name__` would follow the principles of a normal function::
|
||||||
|
|
||||||
|
class MyClass:
|
||||||
|
def my_function1(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def MyClass.my_function2(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
assert my_function1.__name__ == 'my_function1'
|
||||||
|
assert my_function2.__name__ == 'my_function2'
|
||||||
|
|
||||||
|
The grammar would use `dotted_name` to support chaining of attributes::
|
||||||
|
|
||||||
|
def Person.name.fset(self, value):
|
||||||
|
self._name = value
|
||||||
|
|
||||||
|
Backwards Compatibility
|
||||||
|
=======================
|
||||||
|
|
||||||
|
This PEP is fully backwards compatible.
|
||||||
|
|
||||||
|
Copyright
|
||||||
|
=========
|
||||||
|
|
||||||
|
This document has been placed in the public domain.
|
Loading…
Reference in New Issue