Python function attributes - uses and abuses
I typically use function attributes as storage for annotations. Suppose I want to write, in the style of C# (indicating that a certain method should be part of the web service interface)
class Foo(WebService):
@webmethod
def bar(self, arg1, arg2):
...
then I can define
def webmethod(func):
func.is_webmethod = True
return func
Then, when a webservice call arrives, I look up the method, check whether the underlying function has the is_webmethod attribute (the actual value is irrelevant), and refuse the service if the method is absent or not meant to be called over the web.
python: how to access attributes of functions
Because of how instancemethod
objects are implemented. They use a non-standard attribute-getter which doesn't allow access to non-standard attributes.
Python - Function attributes or mutable default values
I use closure instead, no side effects.
Here is the example (I've just modified the original example of Felix Klings answer):
def replaceNthWith(n, replacement):
c = [0]
def replace(match):
c[0] += 1
return replacement if c[0] == n else match.group(0)
return replace
And the usage:
# reset state (in our case count, c=0) for each string manipulation
re.sub(pattern, replaceNthWith(n, replacement), str1)
re.sub(pattern, replaceNthWith(n, replacement), str2)
#or persist state between calls
replace = replaceNthWith(n, replacement)
re.sub(pattern, replace, str1)
re.sub(pattern, replace, str2)
For mutable what should happen if somebody call replace(match, c=[])?
For attribute you broke encapsulation (yes i know that python didn't implemented in classes from diff reasons ...)
Strange syntax with commas
State is, in this case, not a decorator directly, but rather a meta-decorator, or a decorator-generating function: It is not applied to a function directly, but applied to some other arguments, which it will use to return a "real" decorator:
def a(myargs): # applied to some arguments
def b(func): # decorator
do_smth(func, myargs)
return b # calling a will return the decorator
@a("world")
def hello(): # do_smth(hello, "world") is called
pass
When you type
@state(["something"])
def foo():
pass
this will invoke the state function using ["something"] as the argument, which will in turn return the decorator function, which is finally applied to the function foo, setting the __fsm_state__
and __fsm_allowed__
attributes, depending on the parameters originally passed to @state.
When you instead use
@state()
def foo():
pass
allowed (and, in turn, __fsm_allowed__
) will be set to the default value of ["*"]
, which you can see in the declaration of the state function.
Buf if you miss the brackets, that is,
@state # <- no () there
def foo():
pass
The function foo is taken to be the parameter to state (so allowed
is now foo instead of that list it's actually supposed to be), which might lead to subtle bugs - which is why in the definition of state, there is the check
if callable(allowed):
which catches the mistake of passing foo directly, and just assumes you meant the default arguments (allowed=["*"]
)
The following code,
func, allowed = allowed, ['*']
return decorator(func)
Which can be slightly simplified to
func = allowed
allowed = ["*"]
return decorator(func)
- Saves the function to func
- Sets the arguments to the default value and
- Applies the "real" decorator to the function,
Which effectively means that @state and @state() now do exactly the same thing.
In my opinion, the check should rather be an assertion, so you can quickly find and fix such inconsistencies in your code, but whoever wrote that decided to just ignore them silently.
How to set a repr for a function itself?
I think a custom decorator could help:
import functools
class reprable:
"""Decorates a function with a repr method.
Example:
>>> @reprable
... def foo():
... '''Does something cool.'''
... return 4
...
>>> foo()
4
>>> foo.__name__
'foo'
>>> foo.__doc__
'Does something cool.'
>>> repr(foo)
'foo: Does something cool.'
>>> type(foo)
<class '__main__.reprable'>
"""
def __init__(self, wrapped):
self._wrapped = wrapped
functools.update_wrapper(self, wrapped)
def __call__(self, *args, **kwargs):
return self._wrapped(*args, **kwargs)
def __repr__(self):
return f'{self._wrapped.__name__}: {self._wrapped.__doc__}'
Demo: http://tpcg.io/uTbSDepz.
Python Language Question: attributes of object() vs Function
The reason function objects support arbitrary attributes is that, before we added that feature, several frameworks (e.g. parser generator ones) were abusing function docstrings (and other attribute of function objects) to stash away per-function information that was crucial to them -- the need for such association of arbitrary named attributes to function objects being proven by example, supporting them directly in the language rather than punting and letting (e.g.) docstrings be abused, was pretty obvious.
To support arbitrary instance attributes a type must supply every one of its instances with a __dict__
-- that's no big deal for functions (which are never tiny objects anyway), but it might well be for other objects intended to be tiny. By making the object
type as light as we could, and also supplying __slots__
to allow avoiding per-instance __dict__
in subtypes of object
, we supported small, specialized "value" types to the best of our ability.
Related Topics
How to Check Mousebuttonpress Event in Pyqt6
Inverse Dictionary Lookup in Python
Differencebetween Drawing Plots Using Plot, Axes or Figure in Matplotlib
How to Expand a List to Function Arguments in Python
Why Does Python Return 0 for Simple Division Calculation
Class Method Decorator with Self Arguments
Using Python Iterparse for Large Xml Files
How to Convert an Xml String to a Dictionary
How to Locate Element of Credit Card Number Using Selenium Python
Insert a Row to Pandas Dataframe
Read and Write CSV Files Including Unicode with Python 2.7
Iterating Each Character in a String Using Python
Matplotlib Scatter Plot Legend
Understanding Matplotlib.Subplots Python
How to Convert This List of Dictionaries to a CSV File
How to Obtain the Element-Wise Logical Not of a Pandas Series