10
\$\begingroup\$

I'm working with Python 2.7.5 and I have written the following code:

def func(x):
 if x == 1:
 list = data.parseJSON(fileName)
 person = [l for l in list if l['worker'] == sys.argv[1]]
 displayPeople(flatten(person))
 elif x == 2:
 list = data.parseJSON(fileName)
 person = [l for l in list if sys.argv[1] in set(l['children'])]
 displayPeople(person)
 elif x == 3:
 list = data.parseJSON(fileName)
 person = [l for l in list if l['age'] == sys.argv[1]]
 displayPeople(flatten(person))
def flatten(lData):
 if len(lData) == 0
 return lData
 return reduce(lambda x,y:x+y,lData)

I realize that I am repeating myself a lot in the if else section. I would make a function that would do this but, as you can see, the way to get the person list is a little different each time. Also, you'll notice that the person list sometimes needs to be flattened before being passed in the displayPeople() method.

I was thinking that I could use a lambda to get the person, but I don't think that will work. I may need to add more cases, so I think it would be best to have a function where I could just change how the person is calculated and flatten it if necessary, and then pass it to displayPeople().

Any suggestions on how to make this code more modular?

200_success
146k22 gold badges190 silver badges479 bronze badges
asked Oct 1, 2014 at 6:36
\$\endgroup\$
3
  • 2
    \$\begingroup\$ Why is flattening sometimes needed and sometimes not? Because the JSON files have inconsistent structure? \$\endgroup\$ Commented Oct 1, 2014 at 7:01
  • \$\begingroup\$ Does Python not have switches? I would think a switch would work very well here. \$\endgroup\$ Commented Oct 1, 2014 at 18:22
  • 1
    \$\begingroup\$ @ZekeSonxx Python doesn't have switches. \$\endgroup\$ Commented Oct 1, 2014 at 19:30

3 Answers 3

12
\$\begingroup\$

The first step is to shift out duplicate code from the if branch.

def func(x):
 list = data.parseJSON(fileName)
 if x == 1:
 person = [l for l in list if l['worker'] == sys.argv[1]]
 displayPeople(flatten(person))
 elif x == 2:
 person = [l for l in list if sys.argv[1] in set(l['children'])]
 displayPeople(person)
 elif x == 3:
 person = [l for l in list if l['age'] == sys.argv[1]]
 displayPeople(flatten(person))

Secondly, we can flatten the list before putting it into the displayPeople() function.

def func(x):
 list = data.parseJSON(fileName)
 if x == 1:
 person = flatten([l for l in list if l['worker'] == sys.argv[1]])
 displayPeople(person)
 elif x == 2:
 person = [l for l in list if sys.argv[1] in set(l['children'])]
 displayPeople(person)
 elif x == 3:
 person = flatten([l for l in list if l['age'] == sys.argv[1]])
 displayPeople(person)

Thanks to Python's variable scope rules, you can access person outside the branches. We shift the duplicate code out once again:

def func(x):
 list = data.parseJSON(fileName)
 if x == 1:
 person = flatten([l for l in list if l['worker'] == sys.argv[1]])
 elif x == 2:
 person = [l for l in list if sys.argv[1] in set(l['children'])]
 elif x == 3:
 person = flatten([l for l in list if l['age'] == sys.argv[1]])
 displayPeople(person)

Since you mention the possibility that there may be more cases, we can maintain a dictionary of cases.

def func(x):
 def case_1(list):
 return flatten([l for l in list if l['worker'] == sys.argv[1]])
 def case_2(list):
 return [l for l in list if sys.argv[1] in set(l['children'])]
 def case_3(list):
 return flatten([l for l in list if l['age'] == sys.argv[1]])
 list = data.parseJSON(fileName)
 cases = {
 1: case_1,
 2: case_2,
 3: case_3
 }
 if x not in cases:
 raise ValueError
 displayPeople(cases[x](list))

Finally, we can clear up the list comprehensions. Since we're just iterating through the list and filtering everything based on a condition, we can use the function fliter().

def func(x):
 def case_1(list):
 return flatten(filter(lambda l: l['worker'] == sys.argv[1], list))
 def case_2(list):
 return filter(lambda l: sys.argv[1] in set(l['children']), list)
 def case_3(list):
 return flatten(filter(lambda l: l['age'] == sys.argv[1]], list))
 cases = {
 1: case_1,
 2: case_2,
 3: case_3
 }
 list = data.parseJSON(fileName)
 if x not in cases:
 raise ValueError
 displayPeople(cases[x](list))

Certainly much longer than Jaime's answer but personally I find that this is more expressive; having a list of values which need to have the flatten function applied before passing to displayPeople seems hackish to me. It has a few DRY violations but should be adequate.

If flatten can be applied to any list without ill effects (which should be the case), it's alright to waste a few CPU cycles if the list isn't large. In that case, we can reduce the code down to this:

def func(x):
 cases = {
 1: lambda l: l['worker'] == sys.argv[1],
 2: lambda l: sys.argv[1] in set(l['children']),
 3: lambda l: l['age'] == sys.argv[1]
 }
 list = data.parseJSON(fileName)
 if x not in cases:
 raise ValueError
 displayPeople(flatten(filter(list, cases[x])))
answered Oct 1, 2014 at 7:11
\$\endgroup\$
3
\$\begingroup\$

Something like this perhaps?

def func(x):
 funcs = {1: lambda l: l['worker'] == sys.srgv[1],
 2: lambda l: sys.argv[1] in set(l['children']),
 3: lambda l: l['age'] == sys.argv[1],
 }
 flat = set(1, 3) 
 list = data.parseJSON(fileName)
 person = [l for l in list if funcs[x](l)]
 displayPeople(flatten(person) if x in flat else person)
answered Oct 1, 2014 at 6:58
\$\endgroup\$
1
\$\begingroup\$

Dirty impelementation with classes and class method decorator

#!/usr/bin/env python
# ~*~ coding: utf-8 ~*~
class AbstractStrategy(object):
 @staticmethod
 def get_people():
 raise NotImplementedError("get_people should be implemented.");
def flatten(func):
 def wrapper(*args, **kwargs)
 data_list = func(*args, **kwargs)
 if len(data_list) == 0:
 return data_list
 return reduce(lambda x,y: x + y, data_list)
 return wrapper
class FirstStrategy(AbstractStrategy):
 @flatten
 @staticmethod
 def get_people():
 list = data.parseJSON(fileName)
 return [l for l in list if l['worker'] == sys.argv[1]]
class SecondStrategy(AbstractStrategy):
 @staticmethod
 def get_people():
 list = data.parseJSON(fileName)
 return [l for l in list if sys.argv[1] in set(l['children'])]
class ThirdStrategy(AbstractStrategy):
 @staticmethod
 @flatten
 def get_people():
 list = data.parseJSON(fileName)
 return [l for l in list if l['age'] == sys.argv[1]]
STRATEGY_MAPPER = dict(
 1: FirstStrategy,
 2: SecondStrategy,
 3: ThirdStrategy
)
def func(x):
 displayPeople(STRATEGY_MAPPER[x].get_people())

BTW: in this case it will be much better to use functions instead of classes.

answered Oct 1, 2014 at 7:12
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.