Home > Software engineering >  Can I use dot operator in Python to put a class on another one?
Can I use dot operator in Python to put a class on another one?

Time:08-27

my question may sound a bit weird. In JS we can put an object on another one, how does it work in Python? what is the name of this functionality?

Example:

  class A:
        def __init__(self):
            print('I am A')
    
    class B:
        def __init__(self):
            print('I am B')
    
    def i_am_a_function(x):
        print('I am function')
        print(x)
    
    # What if I create a method using dot:
    A.holder_for_b = B
    
    # OR:
    A.some_method = i_am_a_function
    
    ########
    print(A.holder_for_b) 
    print(A.some_method('foo'))

result:

<class '__main__.B'>
I am function
foo
None

and as a final question, what is that "None" ?

CodePudding user response:

A class defines a namespace. A def creates a function object and assigns it to a variable in the active namespace. If that def is in the class namespace (one indentation in from the class definition), the variable is assigned to the class namespace. In your example, both __init__ functions are assigned to variables called "__init__" in the classes. i_am_a_function is not in a class namespace so it is assigned to the module ("global") namespace.

Normally, accessing a variable in a class namespace is just a name lookup like any other variable.

print(A.holder_for_b)

simply looked up the object in A.holder_for_b, which is a class object. You could add

 print(A.holder_for_b())

and get an instance of that class. Similarly A.some_method is just looking up the variable on A. When you call it, you are just calling a function. You saw the print in the function itself and its None return value.

But python does something different if you reference a variable off of a class instance object (as opposed to the class object itself). If you try calling a variable (and it is a function object), python will convert that function object into a method and will automatically add a reference to the the instance as a so-called "self" object.

a = A()
print(a)
print(a.some_method())

prints

I am A
<__main__.A object at 0x7fb6c0ab61c0>
I am function
<__main__.A object at 0x7fb6c0ab61c0>

Here, since you call a variable of an instance object, its first parameter that you called "x" is now the instance object (the "self" parameter). Python didn't really care what you called that variable, its just the first in the parameter list.

CodePudding user response:

I think you mean:

class A:
    def __init__(self):
        print('I am A')
  
class B:
    def __init__(self):
        print('I am B')
  
def i_am_a_function(x):
    print('I am function')
    print(x)


a = A()
b = B()

# What if I create a method using dot:
a.holder_for_b = B

# OR:
a.some_method = i_am_a_function
  
########

print(a.holder_for_b) 
print(a.some_method('foo'))

## outputs:
I am A
I am B
<class '__main__.B'>
I am function
foo
None

So it seems that you can assign a function to an object. This is called in Python "monkey patching". But the disadvantage of this is - I think:

  • It applies only to the object a - so you can't inherit it to another object (different than in javascript I guess - prototype).
  • You can't interact with any other property or method in the class, because you don't have self (in js this) available in that monkey-patched method. (If I am wrong, please tell me.)

And it is bad style, because you should have in the class definition everything what you need. It would be hard to understand for anyone who wants to maintain the code.

Also the a.holder_for_b = B is unnecessary. For what you would need a.holder_for_b? You can't anyway not inherit such a monkey-patched method to another object in Python.

Python's class definitions are lexical.

None

The None is the return value of the function call.

x = a.some_method('foo')
## I am function
## foo

x
## Nothing returned

x is None
## returns: True

print(x)
## None

As you can see: x returns nothing. The None would not be visible if you would not have used print() around the call a.some_method('foo'). So the function is called - which prints I am a function and foo. But the return value (None) gets returned from this call - and the print() prints it.

Putting an object to another

Do you mean "attaching"? This is very well possible in Python - by monkey patching.

a = A()
a.b = B()

Now, b is an attribut/property of the object a, which holds the object generated by B(). You could also just assign any already generated object to a in a similar way.

But this applies only to a. And in Python, you cannot generate another object based on a. So it is a little bit pointless in my view.

Perhaps more pythonic ways would be:

# everytime you generate an instance of the A class, generate an instance of B
# and attache it to newly generated instance of the A class as `b`:
class A:
  def __init__(self):
      print('I am A')
      self.b = B()

# generate an instance of A, but attach as `b` property an existing object:
class A:
    def __init__(self, obj):
        print('I am A')
        self.b = obj

b = B()
# ...
a = A(b)

# a.b is then identical to the generated `b` - it is a reference to it.
# So everything to do to it will be done to `b` too!
# This can be very confusing and create bugs.

a.b.my_new_property = 1

a.b.my_new_property  ## 1
b.my_new_property    ## 1

But I think your problem is that you are trying to apply JavaScript's prototypical OOP system habits to Python. In Python, you should better define everything in your class definitions, give other objects or classes as arguments to the constructor methods (__init__ or __new__). And if you want to modify class definitions in a re-usable manner, probably use decorators or decorator classes. Or design patterns.

  • Related