cookbook_类与对象

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简单些,如果是大量的定制则使用描述符要简单些

posted @ 2019-07-28 17:37  克里夫妇  阅读(222)  评论(0编辑  收藏  举报