thtl

导航

类的一些方法补充

类的一些方法补充

这里得先说一下item系列的类内置方法,item系列的三个方法和attr系列的三个方法很相似,都是对类的对象进行操作的,只不过item系列的三个方法是通过类似操作字典的方式进行调用,而attr系列的三个方法是通过'.'操作的方式来调用的。

//__getitem__()方法,__setitem__()方法,__delitem__()方法

 

class Foo:
    def __getitem__(self, item):
        print('这是__getitem__()!')
        return self.__dict__[item]

    def __setitem__(self, key, value):
        print('这是__setitem__()!')
        self.__dict__[key] = value

    def __delitem__(self, key):
        print('这是__delitem__()!')
        return self.__dict__[key]

f = Foo()
f['name'] = 'djh'
print(f['name'])
print(f.__dict__)
del f['name']
print(f.__dict__)

结果
这是__setitem__()!
这是__getitem__()!
djh
{'name': 'djh'}
这是__delitem__()!
{'name': 'djh'}

 

 

//__str__()方法与__repr__()方法,这两个方法是用来显示输出的类的对象的信息时调用的函数

class Fun:
    def __init__(self, name, age):
        self.name = name
        self.age = age

f = Fun('djh', 18)
print(f) // 输出对象 其实整个过程是这样的 print(f) --> str(f) --> f.__str__()

结果
<__main__.Fun object at 0x000001BA9972B5F8>  //显示的信息,这里调用的是默认的__str__()方法
class Fun:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return "name: %s  age: %s " % (self.name, self.age)

    def __repr__(self):
        return "名字:%s 年龄:%s" % (self.name, self.age)

f = Fun('djh', 18)
print(f) // 这里调用的是自己重写的 __str__()方法,  这里的__repr__()方法和__str__()方法功能是一样的,只是如果在交互式解释器也就是python终端中,要输出对象的信息时是用__repr__()方法而
//不是__str__()方法,但是如果在IDE里面编程时,首先调用的是__str__()方法,如果没有自己定义就会去搜寻自己重写的__repr__()方法,如果自己也没有重写__repr__()方法,就会调用默认的
//__str__()方法 结果 name: djh age:
18

class Fun:
def __init__(self, name, age):
self.name = name
self.age = age

# def __str__(self):
# return "name: %s age: %s " % (self.name, self.age)

def __repr__(self):
return "名字:%s 年龄:%s" % (self.name, self.age)

f = Fun('djh', 18)
print(f)

结果
名字:djh 年龄:18

 

//__format__()方法  ---》用于定制类自己专属的 format()函数

format_dic = {    //你需要的格式的字典
'ymd' : '{0.year}{0.mon}{0.day}',
'y-m-d' : '{0.year}-{0.mon}-{0.day}',
'y:m:d' : '{0.year}:{0.mon}:{0.day}',
}
class Fun:
def __init__(self, year, mon, day):
        self.year = year
        self.mon = mon
        self.day = day

    def __format__(self, format_spec):  //fromat__spec参数接收的是你想要的格式
        if not format_spec or format_spec not in format_dic:
            fm = format_dic['ymd']
        else:
            fm = format_dic[format_spec]
        return fm.format(self)  //举个例子,这里fm是{0.year}-{0.mon}-{0.day},所以fm.format(self)就是'{0.year}-{0.mon}-{0.day}'.format(self)

f = Fun(2018,9,18)
print(format(f))
print(format(f,'y-m-d'))  //如果在format()函数前面没有字符串且传入的参数是类的对象,则实际上是使用类里面内置的 __format__()方法,所以如果要定制类自己的format()函数,则只需要自定义
//__format__()方法,方法里我用了format__dit字典,就是自定义的格式的字典
print(format(f,'y:m:d')) print(format(f,'y:m-d')) 结果 2018918 2018-9-18 2018:9:18 2018918

 

//类的数据属性 __slots__ 这个数据属性是用来省内存的。

先说个引子,咱们在用python时是不是好像没有学到类似于c++结构体的数据结构?比如你要创建一个点,点有x和y,在c++里直接定义一个point结构体,里面有 int x; int y; 但是如果在python中遇到这样的需求怎么办呢,这里就要用到__slots__数据属性了,__slots__数据属性有两个特点,最大的特点是省内存,另外一个特点是消除了类对象的__dict__,也就是说类的对象的对象属性被__slots__替代了,换句话说就是类的对象属性已经被定义好了,不可以再去在外面对对象属性进行添加,只可以用已经定义好的对象属性,这样一来是不是和c++的结构体十分相像了,我个人认为,__slots__被发明出来就是为了解决python里面没有结构体数据类型的问题,原因在下面说

如果要创建一个point结构体,因为python里面没有结构体数据结构,所以直接创建一个Point类
class
Point: pass p1 = Point() //创建一个点对象 p1.x = 1 //添加横坐标 p1.y = 2 //添加纵坐标 p2 = Point() p2.x = 1.1 p2.y = 2.2 print('point1 (%s,%s)' % (p1.x,p1.y)) print('point2 (%s,%s)' % (p2.x,p2.y)) 结果 point1 (1,2) point2 (1.1,2.2)

可是问题是什么呢,因为结构体是一个数据类型,是用来存储某种数据类型的数据结构,意味着这个结构体可能会大量的被创建,而每一个类的实例都有一个对象属性字典(属性字典比较耗内存),如果大量创建的话意味
着会占用大量的内存,并且,现在这样定义的point结构体是很不稳定的,因为结构体类型是已经写好了有哪些数据,是不可以随便添加和删除的,但是现在这样写是可以随便添加和删除

于是选择用 __slots__类数据属性

class Point:
    __slots__ = ['x','y']  //定死了对象只可以有这两个属性,不可以添加和删除

p1 = Point()
p1.x = 1
p1.y = 2
p2 = Point()
p2.x = 1.1
p2.y = 2.2
print('point1 (%s,%s)' % (p1.x,p1.y))
print('point2 (%s,%s)' % (p2.x,p2.y))

结果
point1 (1,2)
point2 (1.1,2.2)

现在这样定义就变得稳定多了,不可以随便添加属性和删除属性,并且,用__slots__可以节约内存,所以如果大量使用这样定义的结构体,会节约很多内存的,综上,__slots__我个人感觉就是为了结构体而发明的,
可以让结构体更稳定以及更加节约内存

此时类的对象已经没有了__dict__属性,换成了__slots__属性,但是对于类来说是没有影响的,类依然有__dict__属性

 

//__doc__属性  --》 输出类的注释信息

class Foo:
    """这是一个注释----by 一位帅哥!"""
    pass

class Bar(Foo):
    pass

f = Foo()
b = Bar()
print(f.__doc__)
print(b.__doc__) // __doc__属性是不允许被继承的,其实最底层的原理是当你创建一个对象时,python会自动帮你创建的对象添加__doc__属性并且赋值为'None',所以当你调用__doc__时,自然就是可以在
//你的对象的属性字典里找到__doc__,于是就不会调用父类的__doc__了,所以在某种层面上讲,和无法继承差不多,当然啦,如果你的类有写注释,自然就是返回你写的注释了,噢对了,这个
//__doc__属性是不允许删除的,就是为了防止一些人以为删了自己的就可以去调用父类的,天真!!! 结果 这是一个注释
----by 一位帅哥! None

// __class__属性以及__moudle__属性

#在包m中的文件test_one.py
class Test:
    pass

#在和包m同一个层次的另一个文件中
from m.test_one import Test

t = Test()
print(t.__module__)  //返回对象来自于哪个模块
print(t.__class__)   //返回对象来自于哪个类

结果
m.test_one
<class 'm.test_one.Test'>

//__del__()方法 ---》析构函数,只有当对象被回收时才会触发

class Foo:
    def __init__(self):
        self.name = 'djh'

    def __del__(self):
        print("析构函数执行!")

f = Foo()
del f.name //此时只是删除了对象的属性,对象没有被删除,所以没有触发析构函数
# del f
print('---------------------------')

结果
---------------------------
析构函数执行!  //程序执行完毕,也就是输出了行线之后,python垃圾回收机制回收对象f,触发析构函数
class Foo:
    def __init__(self):
        self.name = 'djh'

    def __del__(self):
        print("析构函数执行!")

f = Foo()
# del f.name
del f //显示删除对象f,触发析构函数
print('---------------------------')

结果
析构函数执行!
---------------------------

// __call__()方法

__call__()方法,就是当类的对象加上小括号时才执行,如果在类里面没有写这个方法,对象加小括号就报错
class
Fun: def __call__(self, *args, **kwargs): print('__call__()方法执行了!') f = Fun() //其实python里一切皆对象,所以类其实也是某一个更吊的类的对象!所以这个更吊的类里面肯定是写了__call__()方法的,比如就不可以Fun()这样执行了,因为实际上Fun类也是某个更屌类的对象 f() 结果 __call__()方法执行了!

 // __iter__()方法和__ next__()方法   -----》这两个是用来搞迭代器协议的

class Foo:
    def __init__(self, n):
        self.n = n

    def __iter__(self):
        return self

    def __next__(self):
        self.n += 1
        if self.n >= 20:
            raise StopIteration
        return self.n

f = Foo(10)
for i in f:
    print(i)
//首先必须再次强调一下强大的for循环,for循环是基于迭代器协议工作的,只要对象是课迭代的边可以用for来循环,所以现在我们如果想要用for来循环我们自己写的类的对象,则必须在类里面写上__iter__()方法
//和__next__()方法,两者缺一不可,缺少一个都不行! 因为首先  for i in f: 会执行iter(f)-->f.__iter__() 通过这样来将对象先变成可迭代对象,然后再每一次执行 i = next(f)--->f.__next__()
//所以就有下面的结果啦,而且在内存里只有一个数,因为迭代器要么是生成下一个数并且删除目前的数,要么就抛出StopIteration这个停止迭代 结果
11 12 13 14 15 16 17 18 19
class Fun:
    def __init__(self):
        self.a = 1
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        tmp = self.a
        self.a, self.b = self.b, self.a + self.b
        if tmp >=100:
            raise StopIteration
        return tmp

f = Fun()
for i in f:
    print(i)

结果
1
1
2
3
5
8
13
21
34
55
89
斐波那契数列

 

//__set__()方法、__get__()方法,__delete__()方法   -------》当一个类中至少有写这三个方法中的一个时,这个类被称作    “描述符”

描述符这玩意儿究竟是啥呢?其实就是代理另一个类的对象中的某个对象属性(只要对象属性被代理了,则这个属性就不再是属于对象的了),但这个被代理的属性必须在类属性里定义,也就是说这个被代理的属性同时也是类的属性,不可以在构造函数里定义

还有一个地方要注意,如果这个类描述符不是在另一个类里面代理对象的某个属性,而是在主函数里实例某个对象,则该对象的赋值,删除,取值都不会触发这三个方法,这三个方法必须是在被代理的类里才会触发

class Foo:
    def __set__(self, instance, value):
        print('代理设置!')

    def __get__(self, instance, owner):
        print('代理获得!')

    def __delete__(self):
        print('代理删除')

class Bar:
    x = Foo()  //这里这个x属性是被Foo类代理的,其实说白了x就是Foo的一个实例,但是要切记这个x是类的属性,但是却是代理对象的属性
    def __init__(self):
        self.x = 10  //对象的变量x与类里面定义的代理属性同名,所以类里面的代理属性实际上是代理对象的这个同名属性

f = Foo()
f.x = 1
print(f.x) // 不是在类里面代理某个对象属性,所以这里的一切操作都不会触发那三个方法
b = Bar() // 在构造函数里进行了赋值 self.x = 10 因为描述符代理的是对象的属性嘛,这里对对象的这个被代理属性进行赋值所以触发了描述符的__Set__()方法
print(Bar.__dict__) //在下面可以看出类的属性字典里有x,说明x其实也是类的属性
print(b.__dict__) //因为对b.x的赋值操作是被描述符的__set__()方法代理,但是在这个方法里面却没有进行任何赋值操作(直接对b的属性字典操作,instance就是b对象,value就是值比如说在__Set__()
           //方法里 instance.__dict__['y'] = value 这样的操作就可以对b对象的属性字典进行添加属性)
print(b.x) //调用__get__()方法,这个方法无返回值,所以是None
结果

1
代理设置!
{'__module__': '__main__', 'x': <__main__.Foo object at 0x00000155D9DDB5F8>, '__init__': <function Bar.__init__ at 0x00000155DBBCCAE8>,
'__dict__': <attribute '__dict__' of 'Bar' objects>, '__weakref__': <attribute '__weakref__' of 'Bar' objects>, '__doc__': None}
{}
代理获得!
None

在这里必须再说一下,其实这个描述符是定义在类属性里,但是却是代理类的对象的同名属性,也就是说如果直接通过类名来对这个定义在类里面的代理属性进行操作(也就是这里的代理属性x),其实也就是直接对类的属性字典本身进行操作,在类的属性字典里面,x的值是Foo类的对象,所以如果进行 Bar.x 这样的操作,肯定就是去调用描述符的__get__()方法,为什么呢?因为在类里面x属性是Foo的对象,直接调用Bar.x就是返回x的值,那就调用描述符__get__()方法来返回一个值咯,如果是 Bar.x = 1 这样对类的属性重新赋值或者 del Bar.x 删除类的属性,本质上就是对类的属性字典进行操作嘛,如果是 Bar.x = 1 那类属性的x的值就变成 1 咯,因为是重新赋值嘛,这个时候x就不是Foo的对象了,就无法再代理对象的 self.x 属性了,所以着时候如果定义 self.x = 1 的话,是不会调用描述符的__set__()方法,当然如果是 del Bar.x 也一样,类里面就没有了属性x自然也就代理不了对象的属性了,当然,如果在描述里面没有__set__()方法,则例如 self.x = 1 这样的操作,它是会去找描述符的__set__()方法,因为你始终是被代理的嘛,但是在找的时候发现找不到__set__()方法,那自然就直接在对象属性里定义x属性咯,这个时候的x属性就不是被代理的属性了,如果x属性是也就确定是被代理的了,则这个属性是不属于对象的,调用时先去找__get__()方法,找不到这个方法的话再去找类的x属性,因为这个属性是属于类的不属于对象的。

其实描述符是很吊的一个东西,描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.这些以后再讲,类的装饰器在写这篇博文时我还没学,因为python里面对数据的类型几乎是没要求的,变量可以是随便一个类型,所以先写一个用描述符来限制变量的类型的程序

class Typed:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):  //这一步就是限制变量的属性
            raise TypeError('所输入的类型与%s不符!' % str(self.expected_type))
        instance.__dict__[self.name] = value

    def __get__(self, instance, owner):
        if not instance:  //这里是为了防止之间通过类来调用代理属性而导致错误,比如 People.name ,因为这样传过来的 instance 的值为None,则instance.__dict__[self.name] 会出错
return self return instance.__dict__[self.name] def __delete__(self, instance): instance.__dict__.pop(self.name) class People: name = Typed('name', str) age = Typed('age', int) def __init__(self, name, age): self.name = name self.age = age

#首先讲一下为什么要 name = Typed('name',str)而不是xsq = Typed('aaa',str),因为你在构造函数里写的是self.name = name,意味着你想在对象的属性字典里添加一个key值为'name'的对象属性,但
#现在是要对对象属性'name'进行限制类型,所以在写类里面代理属性时选择变量名'name'这样的话self.name就被代理了,再看为什么要构造Typed的对象name时,传入的参数是('name',str)首先因为是要限制是
#str类型,所以传入str,传入'name'的原因是真正添加对象属性是在__set__()方法里直接对对象的属性字典进行操作,首先你传入'name'时意味着Typed的name对象的对象属性self.name的值为'name',然后在
#__set__()方法里, instance.__dict__[self.name] = value 这一步操作就相当于 p1.__dict__['name'] = 'name' 所以就在p1对象的属性字典里加上了key值为'name'的键值对,也就是说满足了
#你在People构造函数里想要在对象里添加key值为'name'的键值对!!!其实真正来讲是为了在添加,删除,获取时,可以用类似 instance.__dict__[self.name] 这样的操作来弄,本质就是希望self.name的
#值和你在函数外面操作的对象属性的名字相同,比如 p1.name 则self.name = ‘name' 或者 p1.age 则 self.name = 'age' 自己意会一下,表达能力有限


p1
= People('djh',18) print(p1.name) print(p1.age) print(p1.__dict__) # p1.name = 123 这个这样会报错,因为也就限制了p1.name只可以是str类型 print(People.name) del p1.age print(p1.__dict__) 结果 djh 18 {'name': 'djh', 'age': 18} <__main__.Typed object at 0x00000250E90A0F98> {'name': 'djh'}

 

posted on 2018-10-09 21:27  thtl  阅读(53)  评论(0编辑  收藏  举报