Will Dx

人世一身霜雪, 归来仍是少年.

python魔法方法专题04-面向对象与常用内置方法

Posted April 07, 2017

python魔法方法专题04-面向对象与常用内置方法

面向对象基础

面象对象编程特点

Raw
封装      多态 
继承:子类可以继承父类的属性和方法
多态:子类也可以自定义属性和方法;(鸭子类型:关注的不是对象类型本身,关注的是其属性和使用方法;)
组合:一个类可以由其他的类组成

实例化类的基本步骤

Raw
(1)调用__new__()方法来创建实例,__new__()方法继承自object;
(2)调用__init__()方法来对实例进行初始化,若类中有__init__()方法,那么调用类的__init__()方法进行初始化;如果没有则默认调用object基类的__init__()方法来初始化类;

类的说明

Raw
class_name.__doc__ 或者 help(class_name)

经典类/新式类

-python2.7版本下: 默认为经典类,继承object的类可转变为新式类; -python3.x版本下:都为新式类

区别:

写法不一样
改变了多继承下方法的执行顺序:经典类-深度优先DFS/新式类-广度优先BFS

Python
# -*- coding: utf-8 -*-
'''
File Name: 1.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 2016年07月05日 星期二 11时28分24秒
'''

"""
图解:
  A
B   C
  D        
D同时继承B,C;  B,C都继承A; 旧式类,深度优先,当调用属性或方法时,顺序为B-A-C;
新式类, 广度优先,当调用属性或方法时,顺序为B-C-A
"""

class A:
    name ="A"
class B(A):
    #name = "B"
    pass
class C(A):
    name = "C"
class D(B,C):
    #name = "D"
    pass

if __name__=="__main__":
    obj = D()
    print "obj.name:",obj.name

新式类更符合OOP编程思想,统一了python中的type机制

Python
注释:a.__class__, type(a)  查看对象类型方法

新式类:aa.__class__type(aa)一样
>>> a = 7
>>> a.__class__
<type 'int'>
>>> type(a)
<type 'int'>

旧式类: aa.__class__type(aa)却不一样
>>> class AA:
...     pass
>>> aa = AA()
>>> aa.__class__
<class __main__.AA at 0xb71f017c>
>>> type(aa)
<type 'instance'>
怎么让旧式类和新式类有一样的效果呢?
    在类前加一句 __metaclass__ = type 设定其元类为type即可

如果经典类被作为父类,子类调用父类的构造函数时会出错

Python
# -*- coding: utf-8 -*-
'''
File Name: 新式类与旧式类的调用父类构造函数异常.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 13:08:39 2016
'''
#基类(经典类)
class Person:
    def __init__(self):
        print "Hi, I am a person. "

#子类
class Student(Person):
    def __init__(self):
        super(self.__class__, self).__init__()

if __name__ == "__main__":
    student = Student()
    #出错啦!TypeError: must be type, not classobj

#使用新式类木有问题

#基类(新式类)

class Person(object):
    def __init__(self):
        print "Hi, I am a person."

#子类
class Student(Person):
    def __init__(self):
        super(self.__class__, self).__init__()

if __name__ == "__main__":
    student = Student()

类属性/实例属性/私有属性基础

类属性

不需要实例化就存在的属性,在类外对类属性进行修改(要谨慎: 类属性更改,那么所有实例的属性都会更改)

Python
# -*- coding: utf-8 -*-
'''
File Name: 类属性.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 13:13:15 2016
'''
"""
类属性:
不需要实例化就存在的属性,在类外对类属性进行修改(要谨慎: 类属性更改,那么所有实例的属性都会更改)
"""
class Test(object):
    name = 'daixiang'
    age = 24

    def get(self):
        myname = self.name #或者 myname = Test.name
        return myname

if __name__=="__main__":
    print Test.name
    t = Test()
    print t.get()

实例属性

类被实例化之后, 一般在init(self,arg,*kwargs)方法中定义,当然也可以在类的方法中定义(self.属性名=xxx) ;不在init()方法中定义的实例属性,实例化的对象在调用前得确保运行了该实例属性所在的方法,只有在方法被调用后,该实例属性才会生效;

私有属性

Python
# -*- coding: utf-8 -*-
'''
File Name: 私有属性.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 13:18:09 2016
'''
#__标示,只有类对象自己可以访问,外部不能访问和修改
#_标示,仅做标示,外部可以访问和修改

class A(object):
    def __init__(self):
        self.__ab = 0 #当我们定义私有属性时,python程序会自动将程序名称转化a._A__ab() 也可以访问私有属性
····
    def info(self):
        print(self.__ab)

if __name__=="__main__":
    a = A()
    a.info()
    print a.__ab #AttributeError:私有属性无法访问
    a.__ab = 3  #手动设置实例属性,此时不时修改私有属性的值,而是新建一个名为__ab的实例属性,并没有修改  self.__ab
    a.info()
    print(a.__ab)
    a.info()

特殊属性

用于保存对象的元数据

Raw
__doc__     文档
__name__    类名称
__dict__    属性字典
__module__  类所在模块名称
__base__    类的父类

实例的dict属性 和 类的dict属性

实例的dict属性: 存储实例化的对象本身的所有属性,它是一个字典;


类的dict属性: 是类dictproxy的一个对象,类dictproxy是一个特殊的类,其对象看上去就像一个普通的字典,但和实例的dict属性不一样的是,包含了一些功能性的键值对,其中包括dict本身; 类属性不能通过类的dict的方式去变更,而直接以 类名.类属性名 的方式去更改

深入类属性/实例属性/私有属性

类属性和实例属性使用

Python
# -*- coding: utf-8 -*-
class A:
    """
    demo
    """
    a = 0
    def __init__(self,):
        self.a = 10
        self.b = 100

if __name__=="__main__":
    a = A()
    ##以实例名.属性名引用时,优先引用实例属性
    ##以类名.属性名引用时,只能引用类属性
    #print a.a
    #print A.a
    #print a.b
    #print A.b #异常
    #自省/反射:以字符串的方式访问类的属性
    #hasattr(obj_bame,'属性名')
    #setattr(obj_name,'属性名',值)
    #getattr(obj_name,'属性名')
    #print getattr(a,"a")
    #setattr(a,"a",20)
    #print  a.a
    #print hasattr(a,"a")

使用属性签名将方法包装成属性

Python
# -*- coding: utf-8 -*-
'''
File Name: 将方法包装成属性.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 14:22:29 2016
'''
class Washer:

    def __init__(self,water=10,scour=2):
        self._water = water
        self.scour = scour
        self.year = 2010

    @property
    #属性包装:将方法包装成属性,以隐藏相关实现
    #可读:@property
    #可写:@<property-name>.setter
    #可删:@<property-name>.deleter
    def water(self):
        return self._water

    @water.setter
    #用于设定属性的范围, 可写
    def water(self,water):
        if 0 < water <=500:
            self._water = water
        else:
            print("set Failure!")

    @property
    def total_year(self):
        return 2015 - self.year

    def set_water(self,water):
        self.water = water

    def set_scour(self,scour):
        self.scour = scour

    def add_water(self):
        print('Add water:',self.water)

    def add_scour(self):
        print('Add scour:',self.scour)

    def start_wash(self):
        self.add_water()
        self.add_scour()
        print('Start wash...')

if __name__ == '__main__':
    w = Washer()
    #类中定义了私有属性_water,私有实例属性只有类的内部能调用
    #所以我们可以定义内部方法去调用私有属性;
    #接着我们对方法进行属性封装,使方法对调用方式和属性的调用方式一样;
    #属性包装分为读写删 3中模式,需要分别定义;这样做的目的在于我们能对属性值做判断或其他操作;
    print(w.water)
    w.water = -123
    print(w.water)
    print(w.total_year)

属性描述符

属性描述符:描述符协议中“定义了get”、“set”或”delete” 这些特殊方法 实现get方法的对象是非数据描述符,意味着在初始化之后它们只能被读取; 而同时实现getset的对象是数据描述符,意味着这种属性是可写的;

说白了就是控制属性读写删

方法1:自定义描述符类

Python
# -*- coding: utf-8 -*-
'''
File Name: desc_01.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 四  7/21 15:12:19 2016
'''

class TypedProperty(object):
    """python2.7 必须是新式类"""
    def __init__(self, name, type, default=None):
        self.name = "_" + name
        self.type = type
        self.default = default if default else type()

    def __get__(self, instance, cls):
        print cls,type(cls)
        return getattr(instance, self.name, self.default)

    def __set__(self,instance,value):
        if not isinstance(value,self.type):
            raise TypeError("Must be a %s" % self.type) 
        setattr(instance,self.name,value)

    def __delete__(self,instance):
        #允许删除
        #del  self.name
        #print "已删除self.name"
        #不允许删除
        raise AttributeError("Can't delete attribute")

class Foo(object):
    #name = TypedProperty("name",str) 
    #num = TypedProperty("num",int,42)

    #若是实例属性,则不能这样定义描述符号
    def __init__(self):
        self.name = TypedProperty("name",str) 
        self.num = TypedProperty("num",int,42)


if __name__=="__main__":
    acct = Foo()
    acct.name = "daixiang"
    acct.num = 24
    print acct.num
    print acct.name

    acct.num = "1234" #提示必须为int类型

    #del acct.num #提示无法删除

    #del Foo.num #可以删除
    #print acct.num #无法找到

    #在__delete__方法中允许删除后
    #del acct.num #提示已删除self.name 
    #print acct.num #提示没有该属性

方法2:使用属性签名:property

Python
# -*- coding: utf-8 -*-
'''
File Name: desc_01.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 四  7/21 15:12:19 2016
'''

class Account(object):
    """必须是新式类"""
    def __init__(self, num):
        self._acct_num = num

    def get_acct_num(self):
        print "get_acct_num"
        return self._acct_num

    def set_acct_num(self, value):
        print "set_acct_num"
        self._acct_num = value

    def del_acct_num(self):
        print "del_acct_num"
        #del self._acct_num
        raise AttrbuiteError("Can't delete attribute")

    acct_num = property(get_acct_num, set_acct_num, del_acct_num, "Account number property.")

if __name__=="__main__":
    acc = Account("24")
    print acc.acct_num

方法3:使用内置装饰器方式(推荐)

Python
# 注释: 设定属性的同名函数然后用描述符装饰器装饰 来控制属性的读写
# -*- coding: utf-8 -*-
'''
File Name: desc_01.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 四  7/21 15:12:19 2016
'''

class Account(object):
    """必须是新式类"""
    def __init__(self, x):
        self._x = x

    @property
    def x(self):
        return self._x

    @x.setter
    def x(self, value):
        self._x = value

    @x.deleter
    def x(self):
        del self._x

if __name__=="__main__":
    acc = Account("24")
    print acc.x

内置slots方法控制类属性范围

只有设定的属性可以进行增删改

Python
注意__slots__只对当前类生效
>>> class Student(object):
...     __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

call方法使类的实例能够当作方法来使用

Python
# -*- coding: utf-8 -*-
'''
File Name: call方法使类的实例能够当作方法来使用.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 14:58:31 2016
'''

class Tst(object):
    def __call__(self):
        print("Call")

if __name__=="__main__":
    t = Tst()
    t()

类方法/静态方法

Python
# -*- coding: utf-8 -*-
'''
File Name: 静态方法和类方法.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 15:20:50 2016
'''
class Washer:
    company = "Le Xi"
    def __init__(self,water=10,scour=2):
        self._water = water
        self.scour = scour
        self.year = 2010

    @staticmethod
    def spins_ml(spins):
        # print("company:",Washer.company)
        # print('year:',self.year)
        return spins * 0.4

    @classmethod
    def get_washer(cls,water,scour):
        print("company:",Washer.company)
        print('year:',self.year)
        return cls(water,cls.spins_ml(scour))

    @property
    def water(self):
        return self._water

    @water.setter
    def water(self,water):
        if 0 < water <=500:
            self._water = water
        else:
            print("set Failure!")

    @property
    def total_year(self):
        return 2015 - self.year

    def set_water(self,water):
        self.water = water

    def set_scour(self,scour):
        self.scour = scour

    def add_water(self):
        print('Add water:',self.water)

    def add_scour(self):
        print('Add scour:',self.scour)

    def start_wash(self):
        self.add_water()
        self.add_scour()
        print('Start wash...')

if __name__ == '__main__':
    """
    一般来说,要使用某个类的方法,需要先实例化一个对象再调用方法。
    而使用@staticmethod或@classmethod,就可以不需要实例化,直接类名.方法名()来调用。
    这有利于组织代码,把某些应该属于某个类的函数给放到那个类里去,同时有利于命名空间的整洁。
    
    既然@staticmethod和@classmethod都可以直接类名.方法名()来调用,那他们有什么区别呢
    从它们的使用上来看,
    @staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。
    @classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。
    如果在@staticmethod中要调用到这个类的一些属性方法,只能直接类名.属性名或类名.方法名。
    而@classmethod因为持有cls参数,可以来调用类的属性,类的方法,实例化对象等,避免硬编码。
    """
    # print(Washer.spins_ml(8))
    # w = Washer()
    # print(w.spins_ml(8))
    # w = Washer(200,Washer.spins_ml(9))
    # w.start_wash()
    w = Washer.get_washer(100,9)
    w.start_wash()

类的继承/重载

Python
# -*- coding: utf-8 -*-
'''
File Name: 类的继承和方法重载.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 15:48:15 2016
'''
#类的继承实例
class Washer:
    company = "Le Xi"
    def __init__(self,water=10,scour=2):
        self._water = water
        self.scour = scour

    @staticmethod
    def spins_ml(spins):
        return spins * 0.4

    @classmethod
    def get_washer(cls,water,scour):
        return cls(water,cls.spins_ml(scour))

    @property
    def water(self):
        return self._water

    @water.setter
    def water(self,water):
        if 0 < water <=500:
            self._water = water
        else:
            print("set Failure!")

    def set_scour(self,scour):
        self.scour = scour

    def add_water(self):
        print('Add water:',self.water)

    def add_scour(self):
        print('Add scour:',self.scour)

    def start_wash(self):
        self.add_water()
        self.add_scour()
        print('Start wash...')

class WasherDry(Washer):
    
    def dry(self):
        print('Dry clothes...')

    def start_wash(self):
        print("....")
        super().start_wash()
        print("....")

if __name__ == '__main__':
    w = WasherDry()
    w.start_wash()
    print(w.scour,w.company)
    w.dry()

特殊的类

元类

Python
# -*- coding: utf-8 -*-
'''
File Name: 类的特殊方法.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 15:52:01 2016
'''
#类也是一个对象,但具有创建其自身实例的能力。
#类可以和一个变量进行绑定。
#你可以为类增加属性。
#你可以把它作为函数的参数传递。
#类的创建和管理者(type),所有的类都是元类(type)的实例
#类的实例化过程: __new__():创建类,__init__()实例化对象
'''
class Custom:
    def __init__(self):
        print("Init")

    def __new__(cls,*args,**kwargs):
        print "New"
        return object.__new__(cls,*args,**kwargs)

if __name__=="__main__":
    Custom()
'''

#使用type创建类
'''
Hello = type('Hello',(object,),dict(helo=lambda lf:print("Hello!!!")))
h = Hello()
h.helo()
'''

#自定义元类:对创建的类进行预处理
#继承type
#自定义__new__(),__init__()方法
'''
# -*- coding: utf-8 -*-
class MyMeta(type):
    def __init__(self,name,bases,dicts):
        print('Init Instance.')

    def __new__(cls,name,bases,dicts):
        print("cls:",type(cls))
        print("name:{},type_name:{}".format(name,type(name)))
        print("bases:{},type_bases:{}".format(bases,type(bases)))
        dicts['info'] = lambda self:print('Djx.')
        res = type.__new__(cls,name,bases,dicts) 
        res.company = 'MaiZi'
        return res

class custom(metaclass=MyMeta):
    """新式类调用元类方式""
    pass

#class cus:
#    """旧式类调用元类方式""
#    __metaclass__ = MyMeta
#    pass

if __name__=='__main__':
    cus = custom()
    cus.info()
    print(cus.company)

序列类

Python
# -*- coding: utf-8 -*-
'''
File Name: 序列类.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 16:36:23 2016
'''

#__len__(self)
#__getitem__(self,key)
#__setitem__(self,key,value)
#__delitem__(self,key)

import sys

class MySeq(object):

    def __init__(self):
        self.lseq = ["I","II","III","IV"]

    def __len__(self):
        self.len(self.lseq)

    def __getitem__(self,key):
        if 0 <= key < 4:
            return self.lseq[key]

if __name__=="__main__":
    m = MySeq()
    print m,type(m)
    for i in m:
        print i
        sys.exit()

iter类

Python
# -*- coding: utf-8 -*-
'''
File Name: 构造iter类.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 16:44:00 2016
'''
#__iter__(self)
#__next__(self)

class MyIter:

    def __init__(self,start,end):
        self.count = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.count < self.end:
            r = self.count
            self.count += 1
            return r
        else:
            raise StopIteration

if __name__ == '__main__':
    for i in MyIter(1,10):
        print(i)

比较类

Python
# -*- coding: utf-8 -*-
'''
File Name: 比较类.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 16:48:08 2016
'''
class Point:
    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __lt__(self,oth):
        return self.x < oth.x

    def __gt__(self,oth):
        return self.y > oth.y

if __name__ == '__main__':
    pa = Point(0,1)
    pb = Point(1,0)
    print(pa < pb)
    print(pa > pb)

相加类

Python
# -*- coding: utf-8 -*-
'''
File Name: 相加类.py
Author: WillDX
mail: xiang.dai@shuyun.com
Created Time: 三  7/ 6 16:51:18 2016
'''
class Point:
    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __add__(self,oth):
        return Point(self.x + oth.x , self.y + oth.y)

    def info(self):
        print(self.x,self.y)

if __name__ == '__main__':
    pa = Point(1,2)
    pb = Point(3,4)
    pc = pa + pb
    pc.info()

内置方法

魔术方法 调用方式 解释
_new_(cls [,...]) instance = MyClass(arg1, arg2) new 在创建实例的时候被调用
_init_(self [,...]) instance = MyClass(arg1, arg2) init 在创建实例的时候被调用
_cmp_(self, other) self == other, self > other, 等。 在比较的时候调用
_pos_(self) +self 一元加运算符
_neg_(self) -self 一元减运算符
_invert_(self) ~self 取反运算符
_index_(self) x[self] 对象被作为索引使用的时候
_nonzero_(self) bool(self) 对象的布尔值
_getattr_(self, name) self.name # name 不存在 访问一个不存在的属性时
_setattr_(self, name, val) self.name = val 对一个属性赋值时
_delattr_(self, name) del self.name 删除一个属性时
_getattribute_(self, name) self.name 访问任何属性时
_getitem_(self, key) self[key] 使用索引访问元素时
_setitem_(self, key, val) self[key] = val 对某个索引值赋值时
_delitem_(self, key) del self[key] 删除某个索引值时
_iter_(self) for x in self 迭代时
_contains_(self, value) value in self, value not in self 使用 in 操作测试关系时
_concat_(self, value) self + other 连接两个对象时
_call_(self [,...]) self(args) “调用”对象时
_enter_(self) with self as x: with 语句环境管理
_exit_(self, exc, val, trace) with self as x: with 语句环境管理
_getstate_(self) pickle.dump(pkl_file, self) 序列化
_setstate_(self) data = pickle.load(pkl_file) 序列化
_instancecheck_(self, instance) - 检查一个实例是不是你定义的类的实例
_subclasscheck_(self, subclass) - 检查一个类是不是你定义的类的子类
_dict_ obj.dict 获取实例化对象的属性字典,参数传递时比较有用

参考资料

python魔法方法