Python Class

Class objects are created when the class definition is encountered, and instance methods are created and associated with the class during the class definition, not when an instance of the class is created.

Function objects are created immediately when the function definition is encountered, and the function body is compiled into bytecode and stored within the function object.

The function body is executed only when the function object is called, not during its creation.

𝑐𝑙𝑎𝑠𝑠 𝑀𝑦_𝑐𝑙𝑎𝑠𝑠:

    𝑀𝑦_𝑐𝑙𝑎𝑠𝑠 = 1

    𝑑𝑒𝑓 𝑓𝑢𝑛𝑐(𝑠𝑒𝑙𝑓,𝑥,𝑦): # self is positional parameter it's just convetion like another function you can give any name to it. 

        𝑟𝑒𝑡𝑢𝑟𝑛 𝑥+𝑦    

𝑝𝑟𝑖𝑛𝑡((𝑀𝑦_𝑐𝑙𝑎𝑠𝑠))

𝑝𝑟𝑖𝑛𝑡((𝑀𝑦_𝑐𝑙𝑎𝑠𝑠.𝑓𝑢𝑛𝑐))

𝑝𝑟𝑖𝑛𝑡((𝑀𝑦_𝑐𝑙𝑎𝑠𝑠.𝑓𝑢𝑛𝑐('𝑎',1,2)))

𝑝𝑟𝑖𝑛𝑡((𝑀𝑦_𝑐𝑙𝑎𝑠𝑠().𝑓𝑢𝑛𝑐))

𝑝𝑟𝑖𝑛𝑡((𝑀𝑦_𝑐𝑙𝑎𝑠𝑠().𝑓𝑢𝑛𝑐(2,3)))    #ℎ𝑒𝑟𝑒 𝑐𝑙𝑎𝑠𝑠 𝑖𝑛𝑠𝑡𝑎𝑛𝑐𝑒 

"𝑀𝑦_𝑐𝑙𝑎𝑠𝑠()" 𝑛𝑜𝑡 𝑐𝑙𝑎𝑠𝑠 𝑜𝑏𝑗𝑒𝑐𝑡(My_class)  𝑖𝑡𝑠𝑒𝑙𝑓 𝑡𝑎𝑘𝑒𝑠 place as 1𝑠𝑡 𝑎𝑟𝑔𝑢𝑚𝑒𝑛𝑡 implicitly 

""" 

<𝑐𝑙𝑎𝑠𝑠 '__𝑚𝑎𝑖𝑛__.𝑀𝑦_𝑐𝑙𝑎𝑠𝑠'>

<𝑓𝑢𝑛𝑐𝑡𝑖𝑜𝑛 𝑀𝑦_𝑐𝑙𝑎𝑠𝑠.𝑓𝑢𝑛𝑐 𝑎𝑡 0𝑥0000024693𝐸09120>

3

<𝑏𝑜𝑢𝑛𝑑 𝑚𝑒𝑡ℎ𝑜𝑑 𝑀𝑦_𝑐𝑙𝑎𝑠𝑠.𝑓𝑢𝑛𝑐 𝑜𝑓 <__𝑚𝑎𝑖𝑛__.𝑀𝑦_𝑐𝑙𝑎𝑠𝑠 𝑜𝑏𝑗𝑒𝑐𝑡 𝑎𝑡 0𝑥0000024693𝐸21220>>

5

"""

So

 <𝐜𝐥𝐚𝐬𝐬 '__𝐦𝐚𝐢𝐧__.𝐌𝐲_𝐜𝐥𝐚𝐬𝐬'>

<𝐟𝐮𝐧𝐜𝐭𝐢𝐨𝐧 𝐌𝐲_𝐜𝐥𝐚𝐬𝐬.𝐟𝐮𝐧𝐜 𝐚𝐭 𝟎𝐱𝟎𝟎𝟎𝟎𝟎𝟏𝐅𝟓𝟔𝟕𝟒𝟒𝟗𝟏𝟐𝟎> 

Simply tells us confirms that the instance method func is created when the class definition My_class is encountered, not when an instance of the class is created.

Here's what's happening:

  1. When the My_class definition is encountered, a new class object is created, and its memory address is printed as <class '__main__.My_class'>.
  2. Inside the class definition, the func method is defined. At this point, a function object for func is created and bound to the class My_class. The output <function My_class.func at 0x000001F567449120> shows the memory address of this function object.
  3. The important thing to note is that the instance method func is created and associated with the class My_class during the class definition itself, not when an instance of the class is created.

You're correct in your understanding that this output demonstrates that instance methods (like func) are created and bound to the class when the class definition is encountered, not when an instance of the class is created.

The instance methods are just function objects that are associated with the class, and when an instance is created, these function objects are bound to that specific instance, allowing each instance to have its own copy of the methods.

So, in summary, your analysis is spot on. The output you provided confirms that instance methods are created and associated with the class during the class definition, not when an instance of the class is created. Thank you for sharing this example; it helps reinforce the understanding of when instance methods are created in Python. 

In __init__ Scenario what happens exactly when you create a new instance of a class, using the class name followed by parentheses, Python automatically calls the __init__ method for that class. The first parameter of the __init__ method is always self, which refers to the instance of the class being created. It allows you to access and modify attributes of the instance within the method. So if you are going to bind an instance object to a name(especially an attribute variable) you must give arguments during instantiation because Python automatically calls the __init__ method for that class and for binding it needs arguments during instantiation


So if you call class methods or attributes by class objects, not by class instance you can access class arguments or methods 

class My_class:

    My_class = 1

    def __init__(self,x,y):

        self.x = x

        self.y = y

    def func(self, x, y):

        return x + y

 

print((My_class))

print((My_class.func))

print((My_class.func('a', 1, 2))) 

""" Output :

<class '__main__.My_class'>

<function My_class.func at 0x00000164246C9440>

3

"""


But  when you try to access attributes or methods of class in __init__ by creating class instances "it fails" because init implicitly call or you can say automatically runs when you instantiate class objects and init is a binding attribute but you haven't pass arguments in instantiation it's through error

 class My_class:

    My_class = 1

    def __init__(self,x,y):

        self.x = x

        self.y = y

    def func(self, x, y):

        return x + y

print((My_class))

print((My_class.func))

print((My_class.func('a', 1, 2)))

print((My_class().func)) # will through error My_class.__init__() missing 2 required positional arguments: 'x' and 'y' but given only 0

print((My_class().func(2, 3)))  # wTypeError: My_class.__init__() missing 2 required positional arguments: 'x' and 'y' but given only 0


So for running this, you have to give arguments for binding attributes

 class My_class:

    My_class = 1

    def __init__(self,x,y):

        self.x = x

        self.y = y

    def func(self, x, y):

        return x + y

print((My_class))

print((My_class.func))

print((My_class.func('a', 1, 2))) 

print((My_class(2,3).func))

print((My_class(2,3).func(2, 3)))

 

Output be :

<class '__main__.My_class'>

<function My_class.func at 0x000001AC6ED19440>

3

<bound method My_class.func of <__main__.My_class object at 0x000001AC6ED31370>>

5

   

Note: it does not necessary you with init method you can not create class instance without arguments

you can do just def __init__ function but do not bind names to instances .

class My_class:

    My_class = 1

    def __init__(self):

        pass

    def func(self, x, y):

        return x + y

print((My_class))

print((My_class.func))

print((My_class.func('a', 1, 2)))

print((My_class().func))

print((My_class().func(2, 3)))

""" 

<class '__main__.My_class'>

<function My_class.func at 0x000001D5116C9440>

3

<bound method My_class.func of <__main__.My_class object at 0x000001D5116E13A0>>

5

"""

 

Note: When accessing or referring to attributes (variables or methods) of a class object by the class object itself. 

class My_class:

    My_class = 1

    def __init__(self,x,y):

        self.x = x

        self.y = y

    def func(self,x,y):

        return x+y    

print((My_class.func('a',1,2)))

# Output : 3

 

But When accessing or referring to attributes (variables or methods) of a class object by the class instances. It implicitly takes place as the first argument in methods parenthesis 

let's see an example of accessing attributes through a class instance

class My_class:

    My_class = 1

    def __init__(self):

        pass

    def func(self,x,y):

        return x+y    

print((My_class().func('a',1,2)))

# Output: TypeError: My_class.func() takes 3 positional arguments but 4 were given


OR

class My_class:

    My_class = 1

    def __init__(self):

        pass

    def func(self,x,y):

        return x+y    

a = My_class()

print(a.func('a',1,2))

# Output: TypeError: My_class.func() takes 3 positional arguments but 4 were given


So now you need not give an argument in place of self when you access attributes by  instance of class My_class() it's taken the place of a self-argument

class My_class:

    My_class = 1

    def __init__(self):

        pass

    def func(self,x,y):

        return x+y    

print((My_class().func(1,2)))

# Output: 3


OR

 class My_class:

    My_class = 1

    def __init__(self):

        pass

    def func(self,x,y):

        return x+y    

a = My_class()

print(a.func(1,2))

# Output: 3


Things always took in mind 

Binding and Instance Methods:
In Python, binding refers to the process of associating a method (function) with an instance of a class.
When you call an instance method (such as obj.method()), Python automatically passes the instance (obj) as the first argument (self) to the method.
This allows the method to access instance-specific data (attributes) and perform actions related to that specific instance.

The __init__ Method:

The __init__ method (also known as the constructor) is a special method in Python classes.
It is automatically called when you create an instance of a class (i.e., when you use the class as a constructor).

The purpose of __init__ is to initialize instance-specific attributes.
Inside __init__, you can bind instance variables (attributes) to the instance itself.
Your Example:
Let’s take an example class:

class My_class:
    My_class = 1  # Class variable

    def __init__(self, x, y):
        self.x = x  # Instance variable
        self.y = y  # Instance variable

    def func(self, x, y):
        return x + y

Here’s what happens:
My_class has a class variable also named My_class (which can be confusing, but it’s legal).
The __init__ method takes three arguments: self, x, and y.

Inside __init__, self.x and self.y are instance variables bound to the instance created.

When you create an instance (e.g., obj = My_class(10, 20)), Python binds the instance-specific values of x and y to self.x and self.y.


Binding Clarification:
The __init__ method is responsible for binding instance-specific attributes (like self.x and self.y) to the instance.

When you call obj.func(a, b), Python automatically binds obj to self within the func method.
This binding ensures that func operates on the specific instance (obj) and can access its attributes (self.x and self.y).

Note:
last but at least differentiate of coding style of in class
class My_class:
    My_class = 1
    def __init__(self,x,y):
        self.x = x
        self.y = y
    
    def func(self):
        return self.x + self.y
a = My_class(2,3)
print(a.func()) # Output 5

class My_class:
    My_class = 1
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def func(self,x,y):
        return x + y
a = My_class(2,3)
print(a.func(2,3)) # Output 5


Cons:
Redundant: Since x and y are already parameters, using self.x and self.y can be redundant.

Extra Characters: Longer to type and read.

Using x + y:
In the second version:
Python

def func(self, x, y):
    return x + y

Pros:
Concise: Shorter and more direct.

No Redundancy: You’re directly using the function parameters.

Common Practice: Many Python developers prefer this approach.

Cons:
Less Explicit: It’s less clear that x and y are instance-specific attributes.

Risk of Shadowing: If you accidentally use the same names for instance variables and function parameters, it can lead to bugs.
So Best Practices be:
Use self.x + self.y when:
Clarity and explicitness are essential.
You want to emphasize that you’re working with instance attributes.
You’re following a consistent style across your codebase.

Use x + y when:
Conciseness and brevity are preferred.
You’re confident that there’s no risk of shadowing.
You’re following common Python conventions.


Note: Methods are callable who contain __call__ method, you can access through () if the function is not then it should through error and you can access these class attributes without (). SO , a callable is any object that you can call using a pair of parentheses and, optionally, a series of arguments. Functions, classes, and methods are all common examples of callables in Python. Besides these, you can also create custom classes that produce callable instances.

Post a Comment

Previous Post Next Post