Python Notes - OOPs - Udemy course

 Oops:

  • In python there is no such concept of access modifiers(like public,private,protected etc).By default all are variables inside of the class are  public, we can add _(under score) prefix infront of name to inform developers it is non public i.e it should be accessed outside of the class.Technically developers can even access that outside of the class.
  • Name mangling is also same when you want to access the variables inside a class. Here we will use 2 underscores before attribute and while calling we need to use(_className__attributeName).Even thecnically this can be used otuside of class but we should not use it.
  • decorator is a function that takes a function as argument to extend its functionality without actually modifying it.

    This is the typical syntax of a decorator function:

    You can see a Python equivalent that illustrates why @property is implemented as a decorator in this article:

  • Methods and functions are not same in python. Blog 

Difference between is and == in python:

is is bascially used to compare objects references i.e if 2 refeences point to same object and == compares the contents.

Eg: list1 = [1,2,3]
      list2 = [1,2,3]
 
     list1 is list2 -> False(since both are different objects)
     list1 == list2 -> True(since the values of both the strings are same)

This not works for user defined objects:

In the previous video, you learned that, in the case of lists and tuples, the == operator compares the values, not if they are the same object. But take a look at this example:

They comparison operator doesn't return True, even if their instance attributes have the same value. Why is this?

Objects created from user-defined classes have to meet two conditions for the expression obj1 == obj2 to evaluate to True.

  • They have to refer to the same object (x is y has to evaluate to True)

  • The expression hash(x) == hash(y) has to evaluate to True.

The hash() function maps the object to a unique integer. For more information on the hash() built-in function, please refer to this article.

According to the Python Documentation:

".... all objects compare unequal (except with themselves) ... x == y implies both that x is y and hash(x) == hash(y)."source

To find more information on this specific quote of the documentation, please refer to the article.

This is why comparing an object from a user-defined class with itself using the == operator returns True:

As you can see, the values returned by hash() are not equal for the two objects:

So when the hash values are different, this expression hash(a) == hash(b) will be False and the expression a is b and hash(a) == hash(b) returns False, so a == b returns False

There are some unexpected behaviours:

For memory optimization python has done some thing which will leads to some unexpected results.

1. small integers

Eg: a = 1
      b = 1
   
  print(a is b) -> True

c= 257
d=257
print(a is b) -> False


Reason: Numbers from  [-5,256] will use same object(For memory optimization).This range amy differ based on the environment.In pycharm it will optimize memory fully so for all integers it will use same exist onject and we will gte true always

2. strings

Eg: str1 = "Bhuvan"
       str2 = "Bhuvan"
print(str1 is str2) -> True 

Reason: Since strings are immutable in python.It makes no sense to create a string with same value at different location.

Note:is may work differently in shell vs script also on different enviroments based on python versions.So it is higly recommended no to use string until and unless you want to compare with None

mutation: here
aliasing : here

Be Careful: Built-in Methods can Mutate Objects

You have to be very careful with built-in methods because some of them mutate the original object.

For example:

Here you can see (above) that the .sort() method mutated the original list.

To achieve this same functionality without mutating the original object, you should use the sorted() function.

As you can see, this function returns a sorted "version" of the list (a copy) without modifying the original list.

You can check if a method mutates the object in the official Python documentation. 👍


Common Bug: Be Careful with Mutable Data Types as Default Arguments

Now you will learn why you should avoid using mutable data types such as lists as default arguments:

Default arguments are initialized when the methods are initially processed, so there is only one copy of each default argument. They are not created when you call the method, they are created when the program starts to run.

1️⃣ Example

If you use a list as a default argument, the same list (reference) will be reused as the default argument for every method call.

Below, you can see how we use an empty list as the default argument for the clients parameter in __init__().

You would expect this to work normally, creating an empty list (a new object) for the default argument every time that you create an instance. But this is not what happens...

When we start adding elements to the list, you can see that the two instances were modified (please see the example below).

What truly happens behind the scenes is that when this line is executed: self.clients.append(client), the new client is added to the same list! Not to a separate list that corresponds to each instance.

You can check that self.clients references the same list in the two instances with the id() function. Notice how the ids are equal.

2️⃣ Solution

The solution to this problem is to avoid using the list directly as a default argument, and use this instead:

None is used as the default argument so you can omit the argument when you create the instance. If the value of clients is None, the attribute is initialized as an empty list. Otherwise, the value passed as argument is assigned.

And this will generate the behavior that you expect:

Now the instances reference two separate lists and modifying one doesn't modify the other.



shallow copy vs deep copy : here






Comments