Python: passing functions as arguments to initialize the methods of an object. Pythonic or not?

2024/10/13 17:15:39

I'm wondering if there is an accepted way to pass functions as parameters to objects (i.e. to define methods of that object in the init block).

More specifically, how would one do this if the function depends on the objects parameters.

It seems pythonic enough to pass functions to objects, functions are objects like anything else:

def foo(a,b):return a*bclass FooBar(object):def __init__(self, func):self.func = funcfoobar = FooBar(foo)
foobar.func(5,6)# 30

So that works, the problem shows up as soon as you introduce dependence on the object's other properties.

def foo1(self, b):return self.a*bclass FooBar1(object):def __init__(self, func, a):self.a=aself.func=func# Now, if you try the following:
foobar1 = FooBar1(foo1,4)
foobar1.func(3)
# You'll get the following error:
# TypeError: foo0() missing 1 required positional argument: 'b'

This may simply violate some holy principles of OOP in python, in which case I'll just have to do something else, but it also seems like it might prove useful.

I've though of a few possible ways around this, and I'm wondering which (if any) is considered most acceptable.

Solution 1

foobar1.func(foobar1,3)# 12
# seems ugly

Solution 2

class FooBar2(object):def __init__(self, func, a):self.a=aself.func = lambda x: func(self, x)# Actually the same as the above but now the dirty inner-workings are hidden away. 
# This would not translate to functions with multiple arguments unless you do some ugly unpacking.
foobar2 = FooBar2(foo1, 7)
foobar2.func(3)# 21

Any ideas would be appreciated!

Answer

Passing functions to an object is fine. There's nothing wrong with that design.

If you want to turn that function into a bound method, though, you have to be a little careful. If you do something like self.func = lambda x: func(self, x), you create a reference cycle - self has a reference to self.func, and the lambda stored in self.func has a reference to self. Python's garbage collector does detect reference cycles and cleans them up eventually, but that can sometimes take a long time. I've had reference cycles in my code in the past, and those programs often used upwards of 500 MB memory because python would not garbage collect unneeded objects often enough.

The correct solution is to use the weakref module to create a weak reference to self, for example like this:

import weakrefclass WeakMethod:def __init__(self, func, instance):self.func = funcself.instance_ref = weakref.ref(instance)self.__wrapped__ = func  # this makes things like `inspect.signature` workdef __call__(self, *args, **kwargs):instance = self.instance_ref()return self.func(instance, *args, **kwargs)def __repr__(self):cls_name = type(self).__name__return '{}({!r}, {!r})'.format(cls_name, self.func, self.instance_ref())class FooBar(object):def __init__(self, func, a):self.a = aself.func = WeakMethod(func, self)f = FooBar(foo1, 7)
print(f.func(3))  # 21

All of the following solutions create a reference cycle and are therefore bad:

  • self.func = MethodType(func, self)
  • self.func = func.__get__(self, type(self))
  • self.func = functools.partial(func, self)
https://en.xdnf.cn/q/69507.html

Related Q&A

Encrypt and Decrypt by AES algorithm in both python and android

I have python and android code for AES encryption. When I encrypt a text in android, it decrypt on python successfully but it can’t decrypt in android side. Do anyone have an idea?Python code :impo…

How to conditionally assign values to tensor [masking for loss function]?

I want to create a L2 loss function that ignores values (=> pixels) where the label has the value 0. The tensor batch[1] contains the labels while output is a tensor for the net output, both have a …

Assign Colors to Lines

I am trying to plot a variable number of lines in matplotlib where the X, Y data and colors are stored in numpy arrays, as shown below. Is there a way to pass an array of colors into the plot function,…

How to display multiple annotations in Seaborn Heatmap cells

I want seaborn heatmap to display multiple values in each cell of the heatmap. Here is a manual example of what I want to see, just to be clear:data = np.array([[0.000000,0.000000],[-0.231049,0.000000]…

ImportError: No module named lxml on Mac

I am having a problem running a Python script and it is showing this message:ImportError: No module named lxmlI suppose I have to install somewhat called lxml but I am really newbie to Python and I don…

Pandas Rolling window Spearman correlation

I want to calculate the Spearman and/or Pearson Correlation between two columns of a DataFrame, using a rolling window.I have tried df[corr] = df[col1].rolling(P).corr(df[col2]) (P is the window size)b…

Python string splitlines() removes certain Unicode control characters

I noticed that Pythons standard string method splitlines() actually removes some crucial Unicode control characters as well. Example>>> s1 = uasdf \n fdsa \x1d asdf >>> s1.splitlines(…

Get only HTML head Element with a Script or Tool

I am trying to get large amount of status information, which are encoded in websites, mainly inside the "< head >< /head >" element. I know I can use wget or curl or python to get…

Is it possible to restore corrupted “interned” bytes-objects

It is well known, that small bytes-objects are automatically "interned" by CPython (similar to the intern-function for strings). Correction: As explained by @abarnert it is more like the inte…

Wildcard namespaces in lxml

How to query using xpath ignoring the xml namespace? I am using python lxml library. I tried the solution from this question but doesnt seem to work.In [151]: e.find("./*[local-name()=Buckets]&qu…