-1

I have a list of values that I want to use for a Builder object implementation that is in the works.

For example:

val_list = ["abc", "def", "ghi"]

What I want to do is dynamically create methods in a class that will allow for these to be callable and retrieved in an instance.

I'm vaguely familiar with doing this with setattr(...) but the next step Im stuck at is being able to do some processing inside the method. In the example below, if I was to do this with my ever growing list, it would a WHOLE BUNCH of code that does literally the same thing. It works for now but I want this list to be dynamic, as well as the class.

For example

def abc(self, value):
     self.processing1 = value + "workworkwork"
     return self

def def(self, value):
     self.processing1 = value + "workworkwork"
     return self

def ghi(self, value):
     self.processing1 = value + "workworkwork"
     return self
  • 1
    Why does the name of the method need to be definable? If the functions would all be doing the same basic thing, why not pass the value for val_list as an argument to a common method? Then you can evaluate the argument to differentiate what happens? Did that make any sense? – pedwards Jun 02 '21 at 21:09
  • 1
    Python will allow you to dynamically create new methods on objects, but I struggle to see what their interfaces and implementations should be. Can you clarify _why_ you want to do this? What problem do you hope to solve? – Nelewout Jun 02 '21 at 21:53
  • It's hard to see what you are trying to do if `abc`, `def`, and `ghi` all do *exactly* the same thing. – chepner Jun 02 '21 at 22:03
  • @N.Wouda and others: Are you all familiar with the Builder design pattern? Im implementing this in my code. Please ignore the logic of the function and please assist in regards to dynamically creating methods based on a list of fields. Thanks. – Deontaé Le Pew Jun 03 '21 at 02:26
  • Does this answer your question? [Python How to create method of class in runtime](https://stackoverflow.com/questions/43836585/python-how-to-create-method-of-class-in-runtime) – Nelewout Jun 03 '21 at 07:54

2 Answers2

0

I haven't tried this before, but I wonder if it would work using lambdas

self.my_methods = {}

val_list = []

def new_method(self,method_name):
   self.my_methods[method_name] = "lambda: self.general_method(some_value)"

def general_method(self, value):
   print(value)

Honestly, I'm sure that won't work as written, but hopefully you see the train of thought if it looks of possible interest. Since I can't visualize the overall concept, it's a little tough.

But since it seems that the method name seems important, I'm not sure what to do. Perhaps this is an XY type question? Getting stuck on the how instead of the results?

I would think there has to be a way to make this work:

[Class definition]
...

def method(self,secret_method_name,arg1):
   # do something based on method name if necessary
   # do something based on args
pedwards
  • 413
  • 3
  • 9
  • The goal is to implement a Builder object. The values in the list will be used as properties for the resulting object instance. I want to be able to do ```obj_instance.abc(value1).def(value1).ghi(value1)``` – Deontaé Le Pew Jun 02 '21 at 21:35
  • I could be wrong, but I don't think you can dynamically create object definitions. I know what you're trying to do, as I've tried it myself. But I think I always ended up passing the "name" I wanted to create as a value. That way I could perform operations based on the name itself. So in this case, perhaps passing 2 values instead of one. I'll edit above... – pedwards Jun 02 '21 at 21:43
  • Methods are just ordinary functions assigned to ordinary class attributes. The only thing special about them is due to the descriptor protocol, which turns an access like `foo.bar` into `type(foo).bar.__get__(foo, type(foo))`. It's the definition of things like `function.__get__`, `classmethod.__get__`, `property.__get__`, and `staticmethod.__get__` that make them behave in significantly different ways. – chepner Jun 02 '21 at 22:06
0

You can't call a non-existing method on a object without wrapping it first, e.g:

# A legacy class
class dummy:
    def foo(self):
        return "I'm a dummy!"

obj = dummy()
obj.a("1")    # You can't

You can do it using a wrapper class first, here's just a idea of how you can get it done:

# Creates a object you can append methods to
def buildable(cls):
    class fake:
        # This method will receive the class of the object to build
        def __init__(self, cls):
            self.cls = cls
        
        # This will simulate a constructor of the underlying class
        # Return the fake class so we can call methods on it
        def __call__(self, *args, **kwargs):
            self.obj = self.cls(*args, **kwargs)
            return self
        
        # Will be called whenever a property (existing or non-existing)
        # is called on a instance of the fake class
        def __getattr__(self, attr):
            # If the underlying object has the called attribute,
            # just return this attribute
            if hasattr(self.obj, attr):
                return getattr(self.obj, attr)
            
            # Call the respective function on globals with the provided 
            # arguments and return the fake class so we can add more methods
            def wrapper(*args, **kwargs):
                globals()[attr](self.obj, *args, **kwargs)
                return self
                
            return wrapper
            
    return fake(cls)

So, how does this work?

  1. Decorate your legacy class:
@buildable
class dummy:
    def foo(self):
        return "I'm a dummy!"
  1. Create the build methods that'll modify dummy:
def a(self, some):
    self.a = some + 'a'
    
def b(self, some):
    self.b = some + 'b'
    
def c(self, some):
    self.c = some + 'c'
  1. Modify it:
obj = dummy()
obj.a("1").b("2").c("3")
  1. See the brand new attributes (and the old ones too!):
print(obj.a)      # 1a
print(obj.b)      # 2b
print(obj.c)      # 3c
print(obj.foo())  # I'm a dummy!

Note that this has some important drawbacks, such as:

  • Calling a non-existing attribute on dummy will not raise AttributeError:
print(obj.nini)  # <function buildable.<locals>.fake.__getattr__.<locals>.wrapper at 0x7f4794e663a0>
  • You can't do it with multiple objects:
obj1 = dummy()
obj1.a("1").b("2")

print(obj1.a)  # 1a
print(obj1.b)  # 2b

obj2 = dummy()
obj2.c("3")

print(obj2.c)  # 3c

print(obj1.a)  # <function buildable.<locals>.fake.__getattr__.<locals>.wrapper at 0x7f524ae16280>
print(obj1.b)  # <function buildable.<locals>.fake.__getattr__.<locals>.wrapper at 0x7f524ae16280>
  • The type of obj will not be dummy:
print(type(obj))      # <class '__main__.buildable.<locals>.fake'>
print(type(obj.obj))  # <class '__main__.dummy'>
  • You can't call a build method with the same name of an already existing method:
def foo(bar):
    self.foo = 'foo' + bar
    
obj.foo("bar")
print(obj.foo())
# raises TypeError: foo() takes 1 positional argument but 2 were
list = buildable(list)
obj = list()
obj.a("4").b("5").c("6")
# raises AttributeError: 'list' object has no attribute 'a'
enzo
  • 9,861
  • 3
  • 15
  • 38