Object

In python, everything is an object, from number to modules.

Objects are an encapsulation of variables and functions into a single entity. Objects get their variables and functions from classes.

Classes are essentially a template to create your objects.

An object contains both data (variables, called attributes) and code(functions, called methods). It represents a unique instance of some concrete thing. Think of objects as nouns and their methods as verbs. An object represents an individual thing, and its methods define how it interacts with other things.

When you create new objects no one has ever created before, you must create a class that indicates what they contain.

Define a class with keyword class

 

To create your own custom object in Python, you need to define a class by using the class keyword.

Empty Class:

class Emptyclass()

   pass

pass indicate that this class is empty.

Self argument

Self is not a reserved word in Python, its common usage, self argument specifies that it refers to the individual object itself.

 

class A(object):
     def __init__(self):
         self.x='Hello'
     def method_a(self,foo):
         print (self.x+' ' + foo)

The self variable represents the instance of the object itself. Most object-oriented languages pass this as a hidden parameter to the methods defined on an object; Python does not. You have to declare it explicitly. When you create an instance of the A class and call its methods, it will be passed automatically:

a=A()
a.method_a('Sailor')
 Hello Sailor
class variable vs instance variable

class variable is the default property that apply to every object.

Instance variable is the specific property that only apply to that object.

E.g.

class girl:
    gender='female'

    def __init__(self,name):
        self.name=name
r=girl('Rachel')
s=girl('Bella')
print(str(r.name)+":")
print(r.gender)
print(str(s.name)+":")
print(s.gender)

The name value that we passed in was saved with the object as an attribute.

And you can get the output:

Rachel:
female
Bella:
female
Class method vs instance method vs class definition

1.  When you see an initial self argument in methods within a class definition, it’s an instance method. These are the types of methods that you would normally write when creating your own classes. The first parameter of an instance method is self, and Python passes the object to the method when you call it.

2. In contrast, a class method affects the class as a whole. Any change you make to the class affects all of its objects.

Within a class definition, a preceding @classmethod decorator indicates that the following function is a class method. The first parameter to the method is the class itself.

The Python tradition is to call the parameter cls, because class is a reserved word and can’t be used here. Let’s define a class method for A that counts how many object instances have been made from it:

>>> class A():
...     count = 0
...     def __init__(self):
...         A.count += 1
...     def exclaim(self):
...         print("I'm an A!")
...     @classmethod
...     def kids(cls):
...         print("A has", cls.count, "little objects.")
...

>>> easy_a = A()
>>> normal_a = A()
>>> hard_a = A()
>>> A.kids()
A has 3 little objects.

Notice that we referred to A.count (the class attribute) rather than self.count (which would be an object instance attribute). In the kids() method, we used cls.count, but we could just as well have used A.count.

3. A third type of method in a class definition affects neither the class nor its objects; it’s just in there for convenience instead of floating around on its own. It’s a static method, preceded by a @staticmethod decorator, with no initial self or class parameter.

Here’s an example that serves as a commercial for the class CoyoteWeapon:

>>> class CoyoteWeapon():
... @staticmethod
... def commercial():
... print('This CoyoteWeapon has been brought to you by Acme')
...

>>> CoyoteWeapon.commercial()
This CoyoteWeapon has been brought to you by Acme

Notice that we didn’t need to create an object from class CoyoteWeapon to access this method. Very class-y.

About self

Python uses the self argument to find the right object’s attributes and methods.

Inside the girl class definition, you access the name attribute as self.name.  When you create an actual object such as s, you refer to it as s.name.

About __init__

 

The __init__ method is roughly what represents a constructor in Python.  When you define __init__() in a class definition, its first parameter should be self. Although self is not a reserved word in Python, it’s common usage.  When you call A() Python creates an object for you, and passes it as the first parameter to the __init__ method. Any additional parameters (e.g., A(24, 'Hello')) will also get passed as arguments.

Every time you call the class, the action in __init__ will be called automatically. E.g.

class who:
    def __init__(self):
        print('ooomm')
    def swim(self):
        print('I am swimming')
frank=who()

frank.swim

output:

ooomm
I am swimming

Let’s create a game, check how many times you have attacked an enemy , he drop 1 point of life when you are attacked. Then we check how many life left.

class Enemy:

    life=3

    def attack(self):

        print('ouch!')

        self.life-=1

    def checklife(self):

        if self.life <=0:

            print('I am dead')

        else:

            print(str(self.life)+ "life left...")


enemy1=Enemy()

enemy2=Enemy()

enemy1.attack()    # you attacked  enemy1 once

enemy1.attack().  # you attacked enemy1 twice

enemy2.attack()   # you attacked enemy2 once

enemy1.checklife()

enemy2.checklife()
Inheritance

If a existing class that creates objects can do almost what you need, and you only need to do minor modification, inheritance can be your choice.

Inheritance creates a new class from an existing class but with some additions or changes.

You define only what you need to add or change in the new class, and this overrides the behavior of the old class. The original class is called a parent, superclass, or base class; the new class is called a child, subclass, or derived class.

how about __init__

You can override any methods, including __init__(). When you define an __init__() method for your class, you’re replacing the __init__() method of its parent class, and the latter is not called automatically anymore. As a result, we need to call it explicitly.

super()

What if you want to call the method that has been overrided?  super() is the solution.

Use super() when the child is doing something its own way but still needs something from the parent (as in real life).

#InheritanceTest

class Parent():

    def print_last_name(self):

        print('My old last name: Roberts')


class child(Parent):   # name in the parensis is the class you want to inheritant

    def print_first_name(self):

        print('Frank')

    def print_last_name(self):   # if you define a same function as one in the parent class, it will overide the parent's function.

        print('New last name: Snitzleburg')
        super().print_last_name(). # call the print_last_name() from class Parent()

Frank=child()

Frank.print_first_name()

Frank.print_last_name()

Output:

Frank

New last name: Snitzleburg
My old last name: Roberts
Multiple inheritance

To let a new class use all the methods and properties of existing class, you can use multiple inheritance, simply add two or more comma separated class names in the parenthesis.

Get and set attribute values with Properties()

Python does not need getters or setters to read and write the values such as private attributes because all attributes and methods are public in Python.

Python use property() to write getters and setters, . The first argument to property() is the getter method, and the second is the setter.

>>> class Duck():
... def __init__(self, input_name):
...     self.hidden_name = input_name

... def get_name(self):
...     print('inside the getter')
...     return self.hidden_name
... def set_name(self, input_name):
...     print('inside the setter')
...     self.hidden_name = input_name

... name = property(get_name, set_name)

Any code that retrieves the value of name will automatically call get_name(). Similarly, any code that assigns a value to name will automatically call set_name().

You can use following methods to assign a value to the name attribute:

#Without property, traditional way

fowl=Duck('frank')

the_name=fowl.get_name()
print(the_name)

the_name4=Duck('Peter')    #We want to use set_name method, so we need to create a object from class first. But we must assign a argument, so we pick a "Peter" randomly.
print(the_name4.get_name()) #verify the "Peter" has been assigned to the property of the_name4
the_name4.set_name('who Ever') # call the method set_name
print(the_name4.get_name())

#with Property, the Pythonic way
the_name2=fowl.name
print(the_name2)

the_name3=Duck
the_name3.name='Bella'
print(the_name3.name)

The output:

inside the getter

frank

peter

inside the setter

inside the getter

who knows



inside the getter

frank

Bella
inside the getter

A property can refer to a computed value as well.

E.g.

>>> class Circle():
... def __init__(self, radius):
... self.radius = radius
... @property
... def diameter(self):
... return 2 * self.radius
...
We create a Circle object with an initial value for its radius:
>>> c = Circle(5)
>>> c.radius
5

We can refer to diameter as if it were an attribute such as radius:

>>> c.diameter
10

Here’s the fun part: we can change the radius attribute at any time, and the diameter property will be computed from the current value of radius:

>>> c.radius = 7
>>> c.diameter
14

If you don’t specify a setter property for an attribute, you can’t set it from the outside.
This is handy for read-only attributes:

>>> c.diameter = 20
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
Another way to define properties is with decorators

We will use two decorators:

• @property,  which goes before the getter method
• @name.setter,  which goes before the setter method

>>> class Duck():
...     def __init__(self, input_name):
...         self.hidden_name = input_name
...     @property
...     def name(self):
...         print('inside the getter')
...         return self.hidden_name
...     @name.setter
...     def name(self, input_name):
...         print('inside the setter')
...         self.hidden_name = input_name

But there are no visible get_name() or set_name() methods:

>>> fowl = Duck('Howard')
>>> fowl.name
inside the getter
'Howard'
>>> fowl.name = 'Donald'
inside the setter
>>> fowl.name
inside the getter
'Donald'
Name Mangling for Privacy

Python has a naming convention for attributes that should not be visible outside of their class definition: begin by using with two underscores (__).

>>> class Duck():
... def __init__(self, input_name):
... self.__name = input_name
>>> fowl = Duck('Howard')

If you try to access the __name attribute, you will get an error:

>> fowl.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Duck' object has no attribute '__name'

This is only used to prevent accidental or intentional direct access to the attribute,  it does not make it a private attributes, you can still access it by following way object._Classname__attributeName

print(fowl._Duck__name)
Duck typing

Python let you run methods of any object that have them.

>>> class BabblingBrook():
...     def who(self):
...         return 'Brook'
...     def says(self):
...         return 'Babble'
...
>>> brook = BabblingBrook()

Then we define a function can use the object as argument:

>>> def who_says(obj):
... print(obj.who(), 'says', obj.says())
...
>>> who_says(brook)
Brook says Babble

 

Special methods

Without special methods, if we want to compare two text while don’t care about the case, we need the code:

>>> class Word():
... def __init__(self, text):
...     self.text = text
...
... def equals(self, word2):
...     return self.text.lower() == word2.text.lower()
...

Then, make three Word objects from three different text strings:

>>> first = Word('ha')
>>> second = Word('HA')
>>> third = Word('eh')

To test the result:

>>> first.equals(second)
True

>>> first.equals(third)
False

With the special methods:

We change the equals() method to the special name __eq__() (you’ll see why in a moment):

>> class Word():
...     def __init__(self, text):
...         self.text = text
...     def __eq__(self, word2):
...         return self.text.lower() == word2.text.lower()
...

Let’s see if it works:

>>> first = Word('ha')
>>> second = Word('HA')
>>> third = Word('eh')
>>> first == second
True
>>> first == third
False
Other methods:

comparison:

__eq__(self, other) self==other
__ne__(self, other) self!=other
__lt__(self, other) self<other
_gt__(self, other) self>other
__le__(self, other) self<=other
__ge__(self,  other) self>=other

Math methods:

__add__(self, other) self + other
__sub__(self, other) self – other
__mul__(self, other) self * other
__floordiv__(self, other) self //other
__truediv__ ( self, other) self / other
__mod__(self, other) self % other
__pow__(self, other) self ** other

Miscellaneous methods

__str__(self) str(self)
__repr__(self) repr(self)
__len__(self) len(self)

repr(): Return a string containing a printable representation of an object.

class word():

    def __init__(self,text):
        self.text=text
    def __eq__(self,word2):
        return self.text.lower()==word2.text.lower()

    def __str__(self):
        return self.text
    def __repr__(self):

        return 'Word("'+self.text+'")'




first=word('ha')

second=word('Ha')

third=word('haha')

print(first==second)

print(first==third)

print(first)

print(first.__repr__())
Composition

Inheritance is a good technique to use when you want a child class to act like its parent class most of the time (when child is-a parent). It’s tempting to build elaborate inheritance hierarchies, but sometimes composition or aggregation (when x has-a y) make more sense.

E.g. A duck is-a bird, but has-a tail. A tail is not a kind of duck, but part of a duck. In this next example, let’s make bill and tail objects and provide them to a new duck object:

>>class Bill():
 ... def __init__(self, description):
 ... self.description = description
 ...
 >>> class Tail():
 ... def __init__(self, length):
 ... self.length = length
 ...
 >>> class Duck():
 ... def __init__(self, bill, tail):
 ... self.bill = bill
 ... self.tail = tail
 ... def about(self):
 ... print('This duck has a', bill.description, 'bill and a',
 tail.length, 'tail')
 ...
 >>> tail = Tail('long')
 >>> bill = Bill('wide orange')
 >>> duck = Duck(bill, tail)
 >>> duck.about()
 This duck has a wide orange bill and a long tail
When to Use Classes and Objects versus Modules

Here are some guidelines for deciding whether to put your code in a class or a module:

• Objects are most useful when you need a number of individual instances that have similar behavior (methods), but differ in their internal states (attributes).

• Classes support inheritance, modules don’t.

• If you want only one of something, a module might be best. No matter how many times a Python module is referenced in a program, only one copy is loaded.

• If you have a number of variables that contain multiple values and can be passed as arguments to multiple functions, it might be better to define them as classes. For example, you might use a dictionary with keys such as size and color to represent a color image. You could create a different dictionary for each image in your program, and pass them as arguments to functions such as scale() or transform().
This can get messy as you add keys and functions. It’s more coherent to define an Image class with attributes size or color and methods scale() and trans form(). Then, all the data and methods for a color image are defined in one place.

• Use the simplest solution to the problem. A dictionary, list, or tuple is simpler, smaller, and faster than a module, which is usually simpler than a class.

 

Named tuples

 

A named tuple is a subclass of tuples with which you can access values by name (with .name) as well as by position (with [ offset ]).

We’ll call the namedtuple function with two arguments:

• The type name of the tuple.

• A string of the field names, separated by spaces

Named tuples are not automatically supplied with Python, so you need to load a module (namedtuple) before using them.

 >>> from collections import namedtuple
 >>> Duck = namedtuple('Duck', 'bill tail').  # Duck is the type name of the tuple, bill and tail are field names. 
 >>> duck = Duck('wide orange', 'long')
 >>> duck
 Duck(bill='wide orange', tail='long')
 >>> duck.bill
 'wide orange'
 >>> duck.tail
 'long'

You can also make a named tuple from a dictionary:

>> parts = {‘bill’: ‘wide orange’, ‘tail’: ‘long’}
>>> duck2 = Duck(**parts)
>>> duck2
Duck(bill=’wide orange’, tail=’long’)

In the preceding code, take a look at **parts. This is a keyword argument. It extracts the keys and values from the parts dictionary and supplies them as arguments to Duck(). It has the same effect as:

>> duck2 = Duck(bill = ‘wide orange’, tail = ‘long’)

Named tuples are immutable, but you can replace one or more fields and return another named tuple:

>> duck3 = duck2._replace(tail=’magnificent’, bill=’crushing’)
>>> duck3
Duck(bill=’crushing’, tail=’magnificent’)

You can not add fields to a named tuple, because it’s immutable:

> duck.color = ‘green’
Traceback (most recent call last):
File “<stdin>”, line 1, in <module>
AttributeError: ‘dict’ object has no attribute ‘color’

reference

Property: https://www.programiz.com/python-programming/property

Duck typing: http://www.voidspace.org.uk/python/articles/duck_typing.shtml