深入类和对象

鸭子类型和多态

“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

我们并不关心对象是什么类型,到底是不是鸭子,只关心行为。

实例一:

# 鸭子类型和多态简单实例

class Cat(object):
    def say(self):
        print('I am a cat')


class Dog(object):
    def say(self):
        print('I am a Dog')

class Duck(object):
    def say(self):
        print('I am a duck')


animal_list = [Dog,Cat,Duck]

for animal in animal_list:
    animal().say()

在Python中实现多态,只需要去实现相同名称的方法就可以了,这种特性是由Python自身的特性导致的。

实例二:

类只要实现了__getitem__方法,它就是可迭代的,并不关心对象的本身,只关心行为,然后就可以当做extend的参数。

# 鸭子类型和多态简单实例

class Cat(object):
    def say(self):
        print('I am a cat')


class Dog(object):
    def say(self):
        print('I am a Dog')

class Duck(object):
    def say(self):
        print('I am a duck')


class Company(object):
    def __init__(self,employee_list):
        self.employee = employee_list

    def __getitem__(self, item):
        return self.employee[item]

    def __len__(self):
        return len(self.employee)


company = Company(['Tom','Bob','Jane'])

a = ['derek1','derek2']
name_set = set()
name_set.add('tom1')
name_set.add('tom2')

#extend里面可以添加任何可迭代的参数,给类添加一个魔法函数__getitem__,类就变成可迭代的,所以可以extend进去
a.extend(name_set)
a.extend(company)
print(a) #['derek1', 'derek2', 'tom1', 'tom2', 'Tom', 'Bob', 'Jane']

抽象基类(abc模块)

抽象基类的作用类似于JAVA中的接口。在接口中定义各种方法,然后继承接口的各种类进行具体方法的实现。抽象基类就是定义各种方法而不做具体实现的类,任何继承自抽象基类的类必须实现这些方法,否则无法实例化。

实现一个抽象基类,不使用abc模块

class Base:
    def get(self,key):
        raise NotImplemented #NotImplemented故名思议,就是“未实现”

    def set(self,key,value):
        raise NotImplemented

class Test(Base):
    def get(self,key):
        return key


test = Test()
print(test.get(('key')))
print(test.set("key","value")) #当没有实现抽象基类的方法的时候,会抛出异常,但是会有一个缺点,只有在调用方法的时候,才会抛出异常

判断类时候有某种属性

#判断类是否有某种属性

class Company(object):
    def __init__(self,employee_list):
        self.employee = employee_list

    def __len__(self):
        return len(self.employee)


company = Company(['Tom','Bob','Jane'])
#hasattr判断类有没有某种属性,方法也是类的属性
print(hasattr(company,"__len__"))   #True

#虽然用hasattr可以判断,但正确的方式是定义一个抽象基类
#我们在某些情况下希望判定某个对象的类型,可以用抽象基类
from collections.abc import Sized
print(isinstance(company,Sized))
#Sized源码
class Sized(metaclass=ABCMeta):

    __slots__ = ()

    @abstractmethod
    def __len__(self):
        return 0

    @classmethod
    def __subclasshook__(cls, C):
        if cls is Sized:
            return _check_methods(C, "__len__")
        return NotImplemented

简单抽象基类实例(abc模块)

#模拟一个抽象基类
import abc
#定义一个抽象基类
class CacheBase(metaclass=abc.ABCMeta):

    @abc.abstractmethod
    def get(self,key):
        pass

    @abc.abstractmethod
    def set(self,key,value):
        pass

#子类,必须要实现抽象基类里面的方法,get和set,不写会报错!

class RedisCache(CacheBase):
    def get(self,key):
        pass

    def set(self,key,value):
        pass


redis_cache = RedisCache()

isinstance和type的区别

语法

isinstance(object, classinfo),其中,object是变量,classinfo 是类型即 (tuple,dict,int,float,list,bool等) 和 class类。

若参数objectclassinfo 类的实例,或者 objectclassinfo 类的子类的一个实例, 返回True
object 不是一个给定类型的的对象, 则返回结果总是False

classinfo 不是一种数据类型或者由数据类型构成的元组,将引发一个 TypeError异常。

isinstance简单用法

>>> isinstance(1,int)
True
>>> 
>>> isinstance('1',str)
True
>>> 
>>> isinstance(1,list)
False

type()isinstance()的区别

  • 共同点两者都可以判断对象类型
  • 不同点对于一个class类的子类对象类型判断,type就不行了,而isinstance 可以。
class A:
    pass

class B(A):
    pass

b = B()

#判断b是不是B的类型
print(isinstance(b,B))        #True
# b是不是A的类型呢,也是的
#因为B继承A,isinstance内部会去检查继承链
print(isinstance(b,A))        #True

print(type(b) is B)           #True
#b指向了B()对象,虽然A是B的父类,但是A是另外一个对象,它们的id是不相等的
print(type(b) is A)           #False

类变量和实例变量

Python的类变量和实例变量,顾名思义,类变量是指跟类的变量,而实例变量,指跟类的具体实例相关联的变量。

class A:
    #类变量
    bb = 11
    def __init__(self,x,y):
        #实例变量
        self.x = x
        self.y = y

a = A(2,3)
A.bb = 111111
print(a.x,a.y,a.bb)    # 2 3 111111
print(A.bb)            # 111111

a.bb = 2222     #实际上会在实例对象a里面新建一个属性bb
print(a.bb)          # 2222
print(A.bb)          # 111111

简单来说,实例变量就是和对象相关联的变量,类变量就是和类相关联的变量。

类和实例属性的查找顺序—mro查找

mro查找

class D:
    pass

class C(D):
    pass

class B(D):
    pass

class A(B,C):
    pass

#顺序:A,B,C,D
#__mro__,类的属性查找顺序
print(A.__mro__)      #(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>)

mro查找

class D:
    pass

class E:
    pass

class C(E):
    pass

class B(D):
    pass

class A(B,C):
    pass

#顺序:A,B,D,C,E
#__mro__,类的属性查找顺序
print(A.__mro__)      

#(<class '__main__.A'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.C'>, <class '__main__.E'>, <class 'object'>)

类方法、静态方法和实例方法

class Date():
    #构造函数
    def __init__(self,year,month,day):
        self.year = year
        self.month = month
        self.day = day

    #实例方法
    def tomorrow(self):
        self.day += 1

    # 静态方法不用写self
    @staticmethod
    def parse_from_string(date_str):
        year, month, day = tuple(date_str.split("-"))
        # 静态方法不好的地方是采用硬编码,如果用类方法的话就不会了
        return Date(int(year), int(month), int(day))

    #类方法
    @classmethod
    def from_string(cls, date_str):
        year, month, day = tuple(date_str.split("-"))
        # cls:传进来的类,而不是像静态方法把类写死了
        return cls(int(year), int(month), int(day))

    def __str__(self):
        return '%s/%s/%s'%(self.year,self.month,self.day)

if __name__ == "__main__":
    new_day = Date(2018,5,9)
    #实例方法
    new_day.tomorrow()
    print(new_day)       #2018/5/10

    #静态方法
    date_str = '2018-05-09'
    new_day = Date.parse_from_string(date_str)
    print(new_day)       #2018/5/9

    # 类方法
    date_str = '2018-05-09'
    new_day = Date.from_string(date_str)
    print(new_day)  # 2018/5/9

数据封装和私有属性

在Python中,实例属性如果以双下划线开头,那么这个属性就是一个私有属性。

class Test:
    def __init__(self, x):
        self.__x = x

    def the_print(self):
        print(self.__x)


t = Test(1)
print(t.__x)
t.the_print()

出现错误:

Traceback (most recent call last):
  File "demo.py", line 14, in <module>
    print(t.__x)
AttributeError: 'Test' object has no attribute '__x'

但是,Python实现这种私有属性的方法,仅仅是通过改变该变量的名称来达到的:__x --> _Test__x

class Test:
    def __init__(self, x):
        self.__x = x

    def the_print(self):
        print(self.__x)


t = Test(1)
print(t.__dir__())
print(t._Test__x)
t.the_print()

输出结果:

['_Test__x', '__module__', '__init__', 'the_print', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
1
1

python对象的自省机制

Python中比较常见的自省(introspection)机制(函数用法)有:dir()type()hasattr()isinstance(),通过这些函数,我们能够在程序运行时得知对象的类型,判断对象是否存在某个属性,访问对象的属性。

dir()

dir()函数可能是 Python 自省机制中最著名的部分了。它返回传递给它的任何对象的属性名称经过排序的列表。如果不指定对象,则 dir() 返回当前作用域中的名称。

class Person:
    name = "user"

class Student(Person):
    def __init__(self,school_name):
        self.school_name = school_name

if __name__ == '__main__':
    user = Student("kevin")
    print(user.__dict__)

type()

type()函数有助于我们确定对象是字符串还是整数,或是其它类型的对象。它通过返回类型对象来做到这一点,可以将这个类型对象与types模块中定义的类型相比较:

>>> type(42)
<class 'int'>
>>> type([])
<class 'list'>

hasattr()

对象拥有属性,并且 dir() 函数会返回这些属性的列表。但是,有时我们只想测试一个或多个属性是否存在。如果对象具有我们正在考虑的属性,那么通常希望只检索该属性。这个任务可以由 hasattr()getattr()函数来完成。

class Person:
    name = "user"

class Student(Person):
    def __init__(self,school_name):
        self.school_name = school_name

if __name__ == '__main__':
    user = Student("kevin")
    print(hasattr(user,'school_name')) #True

super真的是调用父类吗?

super其实是根据mro算法来调用的。

class A:
    def __init__(self):
        print('A')

class B(A):
    def __init__(self):
        print('B')
        super().__init__()


class C(A):
    def __init__(self):
        print('C')
        super().__init__()


class D(B,C):
    def __init__(self):
        print('D')
        super(D, self).__init__()

if __name__ == '__main__':
    print(D.__mro__)          #(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)
    d = D()
    
    
#执行结果
D
B
C
A

mixin继承案例-django rest framework

class Vehicle(object):
    pass

class PlaneMixin(object):
    def fly(self):
        print 'I am flying'

class Airplane(Vehicle, PlaneMixin):
    pass

可以看到,上面的Airplane类实现了多继承,不过它继承的第二个类我们起名为PlaneMixin,而不是Plane,这个并不影响功能,但是会告诉后来读代码的人,这个类是一个Mixin类。所以从含义上理解,Airplane只是一个Vehicle,不是一个Plane。这个Mixin,表示混入(mix-in),它告诉别人,这个类是作为功能添加到子类中,而不是作为父类,它的作用同Java中的接口。

使用Mixin类实现多重继承要非常小心:

  • 首先它必须表示某一种功能,而不是某个物品,如同Java中的Runnable,Callable等;
  • 其次它必须责任单一,如果有多个功能,那就写多个Mixin类;
  • 然后,它不依赖于子类的实现;
  • 最后,子类即便没有继承这个Mixin类,也照样可以工作,就是缺少了某个功能。(比如飞机照样可以载客,就是不能飞了^_^);
  • Mixin类中不要使用super方法。

python中的with语句

#上下文管理器
class Sample:
    def __enter__(self):
        print('enter')
        #获取资源
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        #释放资源
        print('exit')

    def do_something(self):
        print('doing something')

#会自动执行enter和exit方法
with Sample() as sample:
    sample.do_something()


# 运行结果
enter
doing something
exit

contextlib简化上下文管理器

import contextlib

@contextlib.contextmanager
def file_open(file_name):
    print('file_open')
    yield
    print('file end')


with file_open('kevin.txt') as f_opened:
    print('file processing')

# 运行结果
file_open
file processing
file end