I'll give a high level answer to your questions but I recommend to take a look at the Python documentation to get a more in-depth answer.
1) There are essentially two types of attributes an object can have. Objects can have instance attributes that are assigned on a per object basis and class attributes that all instances of that object have access to.
class Employee(object):
has_benefits = True # class attribute
def __init__(self, name, badge_number):
self.name = name # instance attribute
self.badge_number = badge_number # instance attribute
>> e1 = Employee('bob', 123)
>> e1.name
>> 'bob'
>> e1.has_benefits
>> True
>>
>> e2 = Employee('sarah', 456)
>> e2.name
>> 'sarah'
>> e2.has_benefits
>> True
In this example, every Employee instance created has instance attributes name and badge_number which are specific to that instance, but notice how every Employee instance has access to the class attribute has_benefits which is set to True for all Employees.
A function is some code that can be called by it's name, whereas a method is some code that can be called by it's name that is associated with an object. There is also an instance method which accepts the self argument as it's first parameter
def show_something():
print('Show something')
class MyClass(object):
# method
def display_something():
print('Display something')
# instance method
def print_something(self):
print('Print something')
>> show_something()
>> 'Show something'
>>
>> MyClass.display_something()
>> 'Display something'
>>
>> mc = MyClass()
>> mc.print_something()
>> 'Print something'
Notice how the function show_something is just some code that we could call by name. Also, notice how the method display_something is bound to our class object and the instance method print_something is bound to an instance of our object. mc.print_something() is the same as MyClass.print_something(mc)
2) Running dir() on an object returns a list of all of the object's attributes. Some attributes are considered special attributes that are surrounded by double underscores. Some attributes are callable which we can determine by passing the attribute to the built-in function callable() which will return True if the attribute is callable, False otherwise.
Attributes without the surrounding double underscores are also attributes and methods can be attributes too. __abs__ gets called when you run the abs() built-in function which returns the absolute value of the object passed-in. bit_length is a method that returns the number of bits which represent the passed-in object. And yes, you access these attributes with the . (dot) syntax.
>> my_num = 5
>> my_num.real
>> 5
>> callable(my_num.bit_length)
>> True
>> my_num.bit_length()
>> 3
>> my_num.__class__
>> <class 'int'>
3) Lastly, we can define an object that implements some special methods that get used by Python when we run specific actions such as membership testing and getting the length of a collection. I'll show some basic examples:
class MyCollection(object):
def __init__(self, data):
self.my_data = data
def __contains__(self, item):
return item in self.my_data
def __len__(self):
return len(self.my_data)
def __iter__(self):
for item in self.my_data:
yield item
def __add__(self, rhs):
if not isinstance(rhs, MyCollection):
return NotImplemented
return self.my_data + rhs.my_data
def __eq__(self, rhs):
if not isinstance(rhs, MyCollection):
return NotImplemented
return self.my_data == rhs.my_data
def __ne__(self, rhs):
if not isinstance(rhs, MyCollection):
return NotImplemented
return self.my_data != rhs.my_data
Give it a try:
>> mc1 = MyCollection([1,2,3,4,5])
>> mc2 = MyCollection([6,7,8,9])
>> mc3 = MyCollection([1,2,3,4,5])
>>
>> 3 in mc1 # calls __contains__
>> True
>>
>> len(mc1) # calls __len__
>> 5
>>
>> [item for item in mc1] # calls __iter__
>> [1,2,3,4,5]
>>
>> mc1 + mc2 # calls __add__
>> [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>
>> mc1 == mc2 # calls __eq__
>> False
>>
>> mc1 != mc2 # calls __ne__
>> True
>>
>> mc1 == mc3 # calls __eq__
>> True
I hope this helped!