Python Function Attributes - Uses and Abuses

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)
  1. Saves the function to func
  2. Sets the arguments to the default value and
  3. 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



Leave a reply



Submit