0

I have a test class with 3 attributes, a,b,and c. 3 instances of the class has been created with values set for the attributes appropriately, Its given that 'a' attribute is always unique and when two objects with same 'b' value will always have same 'c' value. if such objects are seen, display of attribute 'c' to be omitted for that object.

class test():
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    def __repr__(self):
        return 'test(%s, %s, %s)' % (self.a, self.b, self.c)


test1 = test("1", "abc", "def")
test2 = test("2", "yyy", "xxy")
test3 = test("3", "yyy", "xxy")

objList = [test1, test2, test3]

#takes out duplicate objects
new_list = [next(obj) for i, obj in
            groupby(sorted(objList, key=lambda test: test.a), lambda test: test.c)]
print(new_list)

The above code gets me output as below.

[test(1, abc, def), test(2, yyy, xxy)]

Expected output is to omit only attribute c from the object.

[test(1, abc, def), test(2, yyy, xxy),test(3, yyy)]

Pls Help!

jelat
  • 15
  • 2
  • Since you're using __repr__, you'll have to physically lose access to self.c. Or change up the __repr__ function. Do you have the ability to modify the class at all or does the solution need to work with that example class? – deseuler May 21 '20 at 12:45
  • @deseuler,its a frozen class and attributes removal doesnt work there basically with error popping for frozeninstanceerror – jelat May 21 '20 at 13:14
  • I can't think of a means to change the way the class prints out without being able to modify the __repr__ method. Thats literally what its for. Ill go ahead and post my solution, though. It might ring some bells on how to do something with your actual problem. – deseuler May 21 '20 at 13:20
  • let me add more clarity to my ask here,I need to take out only the duplicate attribute values from the object instances of a frozen class and have no issues with modifying repr method. – jelat May 21 '20 at 13:31

1 Answers1

0

Updated REPR

To be able to change what the class is printing, you have to be able to change the repr method. Mainly because what is presented is a bit too constrictive. If instead the class was as follows, then this is possible:

class test():
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

    def __repr__(self):
      expr = []
      if self.a:
        expr.append(self.a)
      if self.b:
        expr.append(self.b)
      if self.c:
        expr.append(self.c)

      return 'test(%s)' % (', '.join(expr))

The biggest hurdle is the fact that we need some control over what is displayed, therefore we need to change the repr method. Alternatively, you might be able to generate the same results by leverage the str method and converting to a string before printing.

Type independent remove duplicates function

Since I didn't know you're exact use case, and wanted to make something relevant to all types of problems, I made the following function. It will allows you to specify an equality condition, what to do when the condition is met, and how to sort the list.

(The default is to check if subsequent values objects are equal, if the are then do not include the second one in the resulting list.)

def remove_dups(old_list, 
                condition=lambda a, b: a == b, 
                remove_func=lambda x, y: None, 
                sort_key=None):
  """
    Returns a new list that has duplicates removed from `old_list`

    #Arguments
    old_list: list to have duplicates discarded
    condition: test condition between previous and next obj to test equality.
    remove_func: what to add to the list if the condition is met. If `None` is returned
      then no value is added to the `new_list`
    sort_key: how to sort the `old_list`.
  """


  old_list = sorted(old_list, key=sort_key)
  comparitor = old_list[0] #used to compare previous and current obj
  new_list = [comparitor]
  for i, obj in enumerate(old_list[1:]):
    #if previous object and current obj are the same
    if condition(comparitor, obj):

      #run removal function
      if remove_func:
        new_value = remove_func(comparitor, obj)
      else:
        new_value = None

    else: #then objects are different, add the new one
      new_value = obj

    if new_value is not None:
      new_list.append(new_value)

    comparitor = obj
    new_value = None

  return new_list

Your case (example)

We can use this in your case like follows

test1 = test("1", "abc", "def")
test2 = test("2", "yyy", "xxy")
test3 = test("3", "yyy", "xxy")

objList = [test1, test2, test3]

#in lambda functions `a` corresponds to previous object in list,  
#`b` refers to currently observed object
new_list = remove_dups(objList,
            condition=lambda a, b: a.b == b.b, #if a.b and b.b the same
            remove_func=(lambda a, b: test(b.a, b.b, None)), #then set b.c = None
            sort_key=lambda obj: (obj.a, obj.b, obj.c)
          )

print(new_list) # [test(1, abc, def), test(2, yyy, xxy), test(3, yyy)]

Edit

If you want to do this without editing the repr method, then you could do some special print statements with the new_list instead of explicitly trying to print the test objects.

Example:

objList = [test1, test2, test3]

#in lambda functions `a` corresponds to previous object in list,  
#`b` refers to currently observed object
new_list = remove_dups(objList,
            condition=lambda a, b: a.b == b.b, #if a.b and b.b the same
            remove_func=(lambda a, b: test(b.a, b.b, None)), #then set b.c = None
            sort_key=lambda obj: (obj.a, obj.b, obj.c)
          )

exprs = []
for test in new_list:
  expr = []
  if test.a:
    expr.append(test.a)
  if test.b:
    expr.append(test.b)
  if test.c:
    expr.append(test.c)

  exprs.append('test(%s)' % (', '.join(expr)))

print(exprs) # [test(1, abc, def), test(2, yyy, xxy), test(3, yyy)]
deseuler
  • 408
  • 2
  • 8