Domanda

The Question

I want to be able to initialize an object with a function that references the instance's attributes. What I want I tried to capture in this snippet, which produces a NameError: "global name 'self' is not defined":

class Test(object):
    def __init__(self, function = None):
        self.dicty = {1:{'height': 4, 'width': 2}, 2:{'height': 1, 'width': 2} }
        if function == None:
            self.function = lambda x : self.dicty[x]['height']
        else:
            self.function = function

if __name__ == '__main__':
    def func1(x):
        return self.dicty[x]['width']
    def func2(x):
        return self.dicty[x]['width']**2
    G = Test(function = func1)
    H = Test(function = func2)

I could solve the problem by creating a bunch of subclasses to Test, but that doesn't seem readable.

The Motivation

I am using NetworkX to do Python modeling and experiments. I was looking at the classic Albert-Barabasi Model and creating subclasses of the DiGraph class that included a Preference(self, node), Attachment(self, parent, child), and then a Grow(self, max_allowable_nodes). Instead of creating a whole bunch of subclasses like I mentioned before, I would love to be able to create an instance that modifies preference(). This would allow me to run numerical experiments without my code looking too much like Frankenstein. Looking forward to learning something new.

Edit:

Didn't know about the types class or the general idea of reflection. Obviously, still pretty new here. Really appreciate everyone answering my questions and pointing me in the right direction!

È stato utile?

Soluzione

Given that the lambda you create in your __init__ refers to the instance (self), it looks like you want to attach a method to your instance, whereas here you're attaching a function. You need to create a method from the function and attach it to the instance:

import types

class Test(object):
    def __init__(self, function = None):
        self.dicty = {1:{'height': 4, 'width': 2}, 2:{'height': 1, 'width': 2} }
        if function == None:
            function = lambda self, x: self.dicty[x]['height']
        self.function = types.MethodType(function, self)

A method is basically a function that is always passed the instance as the first argument, so you need to ensure any function you pass into your initialiser has self as the initial argument.

>>> t1 = Test()
>>> t1.function(1)
4

>>> t2 = Test(lambda self, x: self.dicty[x]['width'])
>>> t2.function(1)
2

Altri suggerimenti

When you define func1, there is no such thing as self. It's not an argument to the function, and it's not in any higher scope.

You could, instead, define a function that takes the dict you use as an argument and operates on that. In the Test class, you can then call the function on self.dicty. This would require you to change your lambda to also take dicty and x instead of just x.

def func1(dicty, x):
    return dicty[x]['width']

...and in Test...

class Test(object):

    # ... current code but with lambda tweak:
    # lambda dicty, x: dicty[x]['height']

    def do_something(self, x):
        self.function(self.dicty, x)

Without seeing the rest of your code, it's hard to know what further simplifications you could make. But since all the functions seem to be using dicty[x] anyway, you could just write them to take that directly.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top