-1

I made a class. I think that I create a member variable named "myTup" in the initializer/constructor. I try to make sure that I actually did create myTup by making the following call right at the very end of the initializer:

assert(hasattr(self, 'myTup'))

However, later, the interpreter says that my instance/object doesn't have a member variable named "myTup."

Where did myTup go? Come back to me... please Q_Q

The following script is only long due to all of the little debug statements. Without the debugging, it's actually quite short and simple.

Code:

import inspect
def lineno():
    # Returns the current line number in our program.
    return inspect.currentframe().f_back.f_lineno

class DemoClass:

    def __init__(self, initial_data):

        went_in = False # did not enter the if-elif-else block

        if isinstance(initial_data, str):
            print('line number:', lineno())
            t = tuple(initial_data)
            self = DemoClass(t)
            went_in = True        
        elif isinstance(initial_data, DemoClass):
            print('line number:', lineno())
            self = initial_data
            went_in = True
        elif isinstance(initial_data, tuple):
            print('line number:', lineno())
            self.myTup = initial_data
            went_in = True
        else:
            print('line number:', lineno())
            went_in = True
            assert(False)

        assert(hasattr(self, 'myTup'))
        print('line number:', lineno())
        print('went_in:', went_in)
        print('myTup:', self.myTup)

    def __str__(self):
        return "DemoClass" + str(self.myTup)

obj = DemoClass("s")
print(obj)

Output:

line number: 13
line number: 22
line number: 31
went_in: True
myTup: ('s',)
line number: 31
went_in: True
myTup: ('s',)
Traceback (most recent call last):

  [... truncated / abridged ...]

  File "E:/awesome_folder_name/missing_member_var.py", line 36, in __str__
    return "DemoClass" + str(self.myTup)

AttributeError: 'DemoClass' object has no attribute 'myTup'
smci
  • 32,567
  • 20
  • 113
  • 146
Toothpick Anemone
  • 4,290
  • 2
  • 20
  • 42
  • 1
    Why do you redefine `self`? – bereal Sep 09 '17 at 15:36
  • The line `self = DemoClass(t)` is your issue. Your code wants to overload `__init__()` to deal with different types for initial_data (which is fine), but don't try to recursively call `__init__` from itself, and never try to overwrite `self` anywhere inside an object or its methods. – smci Jun 01 '18 at 09:00
  • The whole `line_no`,`went_in` inspection code is wack, it just confuses things. You don't need an if-else ladder for handling multiple types for `initial_data`. – smci Jun 01 '18 at 09:03
  • You want to allow `DemoClass.__init__()` to be called with three different possibilities for `initial_data` : a string, a tuple, or an existing instance of DemoClass. This last one is weird: are you trying to copy or clone an object? (use copy()). There's no point in doing what you do: wrapping the given instance of DemoClass inside a new instance of DemoClass. You couldn't access its `initial_data` except by writing `obj. initial_data.initial_data` – smci Jun 01 '18 at 09:06
  • You might find [CodeReview.SE](https://codereview.stackexchange.com/) helpful – smci Jun 01 '18 at 09:09

1 Answers1

3

You can't replace the instance created in __init__. The following replaces only the self reference in the __init__ method, not the previous instance that self was bound to:

if isinstance(initial_data, str):
    # ...
    t = tuple(initial_data)
    self = DemoClass(t)

When __init__ returns, the obj = DemoClass("s") line still bound the first instance, not the one you created with your recursive call. That first instance has no myTup attribute.

You'd have to call self.__init__(t) instead to operate on the same instance:

if isinstance(initial_data, str):
    # ...
    t = tuple(initial_data)
    self.__init__(t)

This then calls __init__ again, with the same instance bound, and thus will set myTup on the right object.

Note that I'd avoid using recursion here. There is no need, just set a tuple in the different branches and then assign myTup at the end:

def __init__(self, initial_data):
    if isinstance(initial_data, str):
        initial_data = tuple(initial_data)
    elif isinstance(initial_data, DemoClass):
        initial_data = initial_data.myTup
    elif not isinstance(initial_data, tuple):
        raise ValueError('Invalid data')

    self.myTup = initial_data

If you must control what instance is returned when trying to create a new one with DemoClass(), then you can only do so with the __new__ method; it is responsible for producing the instance in the first place (after which the default implementation would call __init__ on that new instance).

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343