9
votes

I have

class Foo():
    function bar():
        pass

    function foobar():
        pass

Rather than executing each function one by one as follows:

x = Foo()
x.bar()
x.foobar()

is there a built-in way to loop through and execute each function in the sequence in which they are written in the class?

7

7 Answers

8
votes

No. You can access Foo.__dict__, and call each value in turn (catching errors for non-callable members), but the order is not preserved.

for callable in Foo.__dict__.values():
    try:
        callable()    
    except TypeError:
        pass

This assumes none of the functions take parameters, as in your example.

12
votes
def assignOrder(order):
  @decorator
  def do_assignment(to_func):
    to_func.order = order
    return to_func
  return do_assignment

class Foo():

  @assignOrder(1)
  def bar(self):
    print "bar"

  @assignOrder(2)
  def foo(self):
    print "foo"

  #don't decorate functions you don't want called
  def __init__(self):
    #don't call this one either!
    self.egg = 2

x = Foo()
functions = sorted(
             #get a list of fields that have the order set
             [
               getattr(x, field) for field in dir(x)
               if hasattr(getattr(x, field), "order")
             ],
             #sort them by their order
             key = (lambda field: field.order)
            )
for func in functions:
  func()

That funny @assignOrder(1) line above def bar(self) triggers this to happen:

Foo.bar = assignOrder(1)(Foo.bar)

assignOrder(1) returns a function that takes another function, changes it (adding the field order and setting it to 1) and returns it. This function is then called on the function it decorates (its order field gets thus set); the result replaces the original function.

It's a fancier, more readable and more maintainable way of saying:

  def bar(self):
    print "bar"
  Foo.bar.order = 1
5
votes

Since Python stores the methods (and other attributes) of a class in a dictionary, which is fundamentally unordered, this is impossible.

If you don't care about order, use the class's __dict__:

x = Foo()
results = []
for name, method in Foo.__dict__.iteritems():
    if callable(method):
        results.append(method(x))

This also works if the function takes extra parameters - just put them after the instance of the class.

1
votes

So long as you're only interested in Python 3.x (and from the empty parentheses in your class statement I'll guess you might be), then there is actually a simple way to do this without decorators: Python 3 allows you to provide your own dictionary like object to use while the class is defined.

The following code is from PEP3115 except for the last couple of lines which I added to print out the methods in order:

# The custom dictionary
class member_table(dict):
  def __init__(self):
     self.member_names = []

  def __setitem__(self, key, value):
     # if the key is not already defined, add to the
     # list of keys.
     if key not in self:
        self.member_names.append(key)

     # Call superclass
     dict.__setitem__(self, key, value)

# The metaclass
class OrderedClass(type):

   # The prepare function
   @classmethod
   def __prepare__(metacls, name, bases): # No keywords in this case
      return member_table()

   # The metaclass invocation
   def __new__(cls, name, bases, classdict):
      # Note that we replace the classdict with a regular
      # dict before passing it to the superclass, so that we
      # don't continue to record member names after the class
      # has been created.
      result = type.__new__(cls, name, bases, dict(classdict))
      result.member_names = classdict.member_names
      return result

class MyClass(metaclass=OrderedClass):
  # method1 goes in array element 0
  def method1(self):
     pass

  # method2 goes in array element 1
  def method2(self):
     pass

x = MyClass()
print([name for name in x.member_names if hasattr(getattr(x, name), '__call__')])
1
votes

There is probably one of the shortest methods (the class name is C):

for func in filter(lambda x: callable(x), C.__dict__.values()):
    pass # here func is the next function, you can execute it here

The filter expression returns all functions of the class C.

OR in one line:

[func() for func in filter(lambda x: callable(x), C.__dict__.values())]

You can order somehow the functions, for example, by lexicographical order of their names by little more complex expression.

0
votes

This works and preserves the order:

class F:

    def f1(self, a):
        return (a * 1)

    def f2(self, a):
        return (a * 2)

    def f3(self, a):
        return (a * 3)

    allFuncs = [f1, f2, f3]

def main():
    myF = F()
    a = 10
    for f in myF.allFuncs:
        print('{0}--> {1}'.format(a, f(myF, a)))

The output would be:

10--> 10
10--> 20
10--> 30

Note: The advantage of using this instead of F.__dict__.values() is that here you can have a list of those functions that you prefer to be called, and not necessarily all of them.

0
votes

I was facing an issue which is related to yours but, might help others for the same situation as I have faced.

What I was trying:

I was learning datetime module in python and created an object of that. Now, to get the details from that object, I needed to manually you know, press the Tab key, get list of available methods/attributes and then type them and finally then after get the result. And needed to repeat for other times too!

What I needed:

Since there was a mixture of Methods (which are callable) and Attribute (which can't be called) I needed to automate all of them to be used or called (se) and see their result in just a single loop.

What I tried:

I just made a simple looking for loop which iterates over dir(datetimeobj)... Let me show you how...

# Creating an Obj
date = datetime.datetime(2020,1,29,12,5)

# Printing (But won't work, discussing 'why' in a bit)
for method in dir(date):
    if callable(method):
        print(f"{method}() -->  {date.method()}")
    else:
        print(f"{method}   -->  {date.method}")

Looks fine, right! It should work... but, no - it won't. Executing this code will result in error...

##Error: 'datetime.datetime' object has no attribute 'method'


The Problem:

Aha! We are trying to call method which is the string so, date.method() / date.method is invalid.

Towards Solution:

I will try not to discuss so long here... as the code given is self-explanatory, but just see this...

dir_without_dunders = [method for method in dir(date) if not method.startswith('_')]

for method in dir_without_dunders:
    RealMethod = eval(f'date.{method}')
    try:
        if callable(RealMethod):
            print(f"{method}() -->  {RealMethod()}")
        else:
            print(f"{method} --> {RealMethod}")
    except:
        continue

Code summary:
• Created dir_without_dunders for you know...
• Took another variable RealMethod because method will be having the string (name) just to make printing more clear and sensible
• The main solution is with eval(f'date.{method}') that is the hack.
• Why used try block? - It is because not all methods can be called without any parameters and they needed different set and number of parameters, so I called only those which can be called simply!

That's it. Works for this datetime object calls as it has majority of methods with empty set of parameters.