欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

cookbook_类与对象

程序员文章站 2022-04-30 22:33:19
1修改实例的字符串表示 可以通过定义__str__()和__repr__()方法来实现 对于__repr__(),标准的方法是让他产生的字符串文本能够满足eval(repr(x)) == x __str__()则产生一段有意义的文本 2自定义字符串的输出格式 我们想让对象通过format()函数和字 ......

1修改实例的字符串表示

可以通过定义__str__()和__repr__()方法来实现

class pair:
    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __str__(self):
        return "(%s,%s)"%(self.x,self.y)

    def __repr__(self):
        return "pair(%s,%s)"%(self.x,self.y)

p = pair(2,5)
print(p)
print("p is {0!r}".format(p))

 

对于__repr__(),标准的方法是让他产生的字符串文本能够满足eval(repr(x)) == x

__str__()则产生一段有意义的文本

 

2自定义字符串的输出格式

 

我们想让对象通过format()函数和字符串方法来支持自定义的输出格式

要自定义字符串的输出格式,可以在类中定义__format__()方法 

_formats = {
    "ymd":"{d.year}-{d.month}-{d.day}",
    "mdy":"{d.month}/{d.day}/{d.year}",
    "dmy":"{d.day}/{d.month}/{d.year}"
}

class date:
    def __init__(self,year,month,day):
        self.year = year
        self.month = month
        self.day = day

    def __format__(self,code):
        if code == "":
            code = "ymd"
        fmt = _formats[code]
        return fmt.format(d = self)

d = date(2018,9,26)
print(format(d))
print(format(d,"dmy"))
print(format(d,"mdy"))

 

 

3让对象支持上下文管理协议

 

我们想让对象支持上下文管理协议,即可以通过with语句触发。

想让对象支持上下文管理协议,对象需实现__enter__()和__exit__()方法,比如实现网络连接的类。

from socket import socket,af_inet,sock_stream

class lazyconnection:
    
    def __init__(self,address,family = af_inet, type = sock_stream):
        self.address = address
        self.family = family
        self.type = type
        self.sock = none

    def __enter__(self):
        if self.sock is not none:
            raise runtimeerror("already connected")
        self.sock = socket(self.family,self.type)
        self.sock.connect(self.address)
        return self.sock

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.sock.close()
        self.sock = none

conn = lazyconnection("www.baidu.com")

with conn as s:
    s.send(b'hahhahah')

 

 

4当创建大量实例时如何节省内存

当我们的程序需要创建大量的实例(百万级),这样会占用大量的内存。

#对于那些主要用作简单数据结构的类,通常可以在类定义中增加__slot__属性,以此来大量减少对内存的使用。

class date:
    __slots__ = ["year","month","day"]

    def __init__(self,year,month,day):
        self.year = year
        self.month = month
        self.day = day

 

当定义了__slots__属性时,python会采用一种更加紧凑的内部表示,会将实例的属性添加到一个小型数组里,不再为每个实例创建__dict__。

副作用是我们不能为实例添加新的属性。是一种优化手段

 

5将名称封装到类中

在python中,以单下划线_开头的属性被认为是一种私有属性

class a:
    def __init__(self):
        self._name = "jiaojianglong"
        self.age = 24

    def _internal_method(self):
        print("i am a internal method")


a = a()
print(a._name) #jiaojianglong

 

 

python并不会阻止访问属性,但编译器不会做提示。如果强行访问会被认为是粗鲁的。

在类的定义中也见到过双下划线__开头的名称,以双下划线开头的名称会导致出现名称重组的行为

class b:
    def __init__(self):
        self.__name = "jiaojianglong"

b = b()
# print(b.__name)#attributeerror: 'b' object has no attribute '__name'
print(b._b__name)#jiaojianglong

 

这样的行为是为了继承,以双下划线开头的属性不会被子类通过继承而覆盖。

class c(b):
    def __init__(self):
        super().__init__()

c = c()
print(c._b__name)#jiaojianglong

 

 

大部分情况下我们使用单下划线,涉及到子类继承覆盖的问题时使用双下划线

当我们想定义一个变量,但是名称可能会与保留字段冲突,基于此,我们在名称后加一个单下划线以示区别。lambda_

 

6创建可管理的属性

#在对实例的获取和设定上,我们希望增加一些额外的处理过程。

class person:
    def __init__(self,first_name):
        self.first_name = first_name

    @property
    def first_name(self):
        return self._first_name

    @first_name.setter
    def first_name(self,value):
        if not isinstance(value,str):
            raise typeerror("excepted a string")
        self._first_name = value



p = person("jiao")
print(p.first_name)

 

在创建实例时,__inti__()中我们将name赋值到self.first_name,实际会调用setter方法,所以name实际还是储存在self._first_name中

 

7调用父类中的方法

#我们想调用一个父类中的方法,这个方法在子类中已经被覆盖了。

class a:
    def spam(self):
        print("a.spam")

class b(a):
    def spam(self):
        print("b.spam")
        super().spam()

b = b().spam()#b.spam,a.spam

print(b.__mro__)#(<class '__main__.b'>, <class '__main__.a'>, <class 'object'>)

 

 

争对每一个类,python都会计算出一个称为方法解析顺序(mro)的列表,mor列表只是简单的对所有的基类进行线性排列。

 

8在子类中扩展属性

我们想在子类中扩展某个属性的功能,而这个属性是在父类中定义的

 

9创建一种新形式的类属性或实例属性

如果想定义一种新形式的实例属性,可以以描述符的形式定义其功能。

class integer():

    def __init__(self,name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is none:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value,int):
            raise typeerror("expected an int")
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]


class point:
    x = integer("x")
    y = integer("y")

    def __init__(self,x,y):
        self.x = x
        self.y = y

p = point(2,3)
print(p.x)#2
p.y = 5
print(p.y)#5
# p.x = "a"#typeerror: expected an int
print(point.x)#<__main__.integer object at 0x00000141e2abb5f8>

 

__get__()方法看起来有些复杂的原因是实例变量和类变量的区别,如果是类变量则简单的返回描述符本身,如果是实例变量返回定义的值

 

关于描述符,常容易困惑的地方就是他们只能在类的层次上定义,不能根据实例来产生,下面的代码是无法工作的

class point:

    def __init__(self,x,y):
        self.x = integer("x")
        self.y = integer("y")
        self.x = x
        self.y = y

p = point(2,"c")
print(p.x)#2
print(p.y)#c

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


    def __get__(self, instance, owner):
        if instance is none:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value,self.expected_type):
            raise typeerror("expected %s"%self.expected_type)
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]

def typeassert(**kwargs):
    def decorate(cls):
        for name,expected_type in kwargs.items():
            setattr(cls,name,typed(name,expected_type))
        return cls
    return decorate

@typeassert(name=str,shares = int,price=float)
class stock:
    def __init__(self,name,shares,price):
        self.name = name
        self.shares = shares
        self.price = price

 

对于少量的定制还是使用property简单些,如果是大量的定制则使用描述符要简单些