class

define a class

In [1]:
class Point(object):
    '''a 2D point with attr: x, y'''
    
    def __init__(self, x , y):
        self.x = x
        self.y = y
    def __str__(self):
        return 'Point[ x = {0}, y = {1} ]'.format(self.x, self.y)

class Rectangle(object):
    '''Rectangle, represented by it left lower corner, width and height'''
    
    def __init__(self, point, width, height):
        self.corner = point
        self.width = width
        self.height = height
        
    def __str__(self):
        return 'Rectangle[ corner = {0}, width = {1}, height = {2} ]'.format(self.corner, self.width, self.height)

instantiate objects from classes

In [2]:
# 创建一个对象, 只需要通过 类名()的方式, 括号中需要传入__init__函数中指定的参数
p1 = Point(20, 30)
p2 = Point(20, 30)
print(p1 is p2) # p1 和 p2不是同一个对象
print(p1 == p2)   ## 自定义的类, 使用==号运算时, 其行为默认与 is 类似, 就是判断两个变量是否引用了同一个对象
False
False

shallow copy (浅拷贝)

shallow copy

In [3]:
import copy 

box1 = Rectangle(p1, 100, 200)
box2 = copy.copy(box1)  
print('box1 : ', box1)
print('box2 : ', box2)
# copy 函数复制并创建了一个新的对象, 两个对象有相同的值和引用, 是浅拷贝
print('box1 is box2 : ', box1 is box2) 
print('box1 == box2 : ', box1 == box2 )

# box1.corner 和box2.corner引用了同一个对象, 因此 is 和 == 都返回 True
print('box1.corner is box2.corner : ', box1.corner is box2.corner)  
print('box1.corner == box2.corner : ', box1.corner == box2.corner)
box1 :  Rectangle[ corner = Point[ x = 20, y = 30 ], width = 100, height = 200 ]
box2 :  Rectangle[ corner = Point[ x = 20, y = 30 ], width = 100, height = 200 ]
box1 is box2 :  False
box1 == box2 :  False
box1.corner is box2.corner :  True
box1.corner == box2.corner :  True

deep copy (深拷贝)

In [4]:
box3 = copy.deepcopy(box1)

# 由于box3是通过深拷贝创建的, box3中的变量和引用(以及引用的引用)都是原对象的副本, 
# 而不再引用同一个对象, 是深拷贝
print('box1 : ', box1)
print('box3 : ', box3)
print('box1.corner is box3.corner : ', box1.corner is box3.corner)  
print('box1.corner == box3.corner : ', box1.corner == box3.corner)
box1 :  Rectangle[ corner = Point[ x = 20, y = 30 ], width = 100, height = 200 ]
box3 :  Rectangle[ corner = Point[ x = 20, y = 30 ], width = 100, height = 200 ]
box1.corner is box3.corner :  False
box1.corner == box3.corner :  False

运算符重载

In [5]:
class Time():
    '''
        represent the time of a day
        attr : hour, minute, second
    '''
    
    def __init__(self, hour=0, minute=0, second=0):
        self.hour = hour
        self.minute = minute
        self.second = second
    def __str__(self):
        return '%.2d : %.2d : %.2d'%(self.hour, self.minute, self.second)
    
    # 下面两个是类的方法, 调用的时候需要 用  类名.method() 的形式
    def timeToInt(time):
        return time.hour * 3600 + time.minute * 60 + time.second
    
    def intToTime(second):
        time = Time()
        minute ,time.second = divmod(second, 60)
        time.hour, time.minute = divmod(minute, 60)
        return time
    
    # 重载小于号 < 运算符
    def __lt__(self, other):
        return Time.timeToInt(self) < Time.timeToInt(other)
    # 重载等于号 ==
    def __eq__(self, other):
        return Time.timeToInt(self) == Time.timeToInt(other)
    
    # 重载 + 运算符
    # self + other 时调用该函数
    def __add__(self, other):
        ''' other can be a integer or another time '''
        if isinstance(other, Time):
            second = Time.timeToInt(self) + Time.timeToInt(other)
            return Time.intToTime(second)
        else:  # assume other is a integer
            second = Time.timeToInt(self) + other
            return Time.intToTime(second)
        
    # other + self 时调用该函数
    def __radd__(self, other):
        return self + other
    
    #使用关键字参数, 增加指定的时间单位
    def increase(self, *, hour=0, minute=0, second=1):
        second = hour * 3600 + minute * 60 + second + Time.timeToInt(self)
        minute, self.second = divmod(second, 60)
        self.hour, self.minute = divmod(minute, 60)
        
In [6]:
# test Time

t1 = Time(6,6,6)
t2 = Time(11, 11, 11)
t3 = t1 + t2
print('t1 = ', t1)
print('t2 = ', t2)
print('t3 = t1 + t2 , t3 = ', t1 + t2)
t1 =  06 : 06 : 06
t2 =  11 : 11 : 11
t3 = t1 + t2 , t3 =  17 : 17 : 17
In [7]:
t4 = copy.copy(t3)
t4.increase(minute=100, second=80)
print('t4 = ', t4)
print('t4 + 100 = ', t4 + 100) 
# 如果没有定义 __radd__(), 这里将会报错, 因为编译器不知道如何将一个时间加到一个整数上
print('300 + t4 = ', 300 + t4) 

# 由于Time定义了 < 号 运算, 因此可以对其进行排序
t  = [t1, t2, t3, t4, Time() ]
# 要想同时获得元素的下标和对应的元素, 可以使用 enumerate()来封装要遍历的集合
for idx, tm in enumerate(sorted(t)) : print(idx , '    ', tm)
t4 =  18 : 58 : 37
t4 + 100 =  19 : 00 : 17
300 + t4 =  19 : 03 : 37
0      00 : 00 : 00
1      06 : 06 : 06
2      11 : 11 : 11
3      17 : 17 : 17
4      18 : 58 : 37
In [8]:
# 测试等于号
# t5 = copy.deepcopy(t[1])
t5 = Time(6,6,6)
print('t1 = ', t1)
print('t5 = ', t5)
print('t1 == t5:', t1 == t5)
print('t[0] == t[1] : ', t[0] == t[1] )
t1 =  06 : 06 : 06
t5 =  06 : 06 : 06
t1 == t5: True
t[0] == t[1] :  False

hasattr, getattr, setattr

In [9]:
# 先了解两个 built-in 函数, 使用这两个函数可以对一个陌生的类或对象进行分析
# vars(obj) 返回一个字典, 该字典以键值对的形式包含了obj对象所定义的类中所有的public属性和其对应的值
print(vars(t4))
# dir(obj) 返回一个字符串列表,列出了该对象所有的属性和函数,包括以__开头和结尾的
print(dir(t4)) 
{'hour': 18, 'minute': 58, 'second': 37}
['__add__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__radd__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__slotnames__', '__str__', '__subclasshook__', '__weakref__', 'hour', 'increase', 'intToTime', 'minute', 'second', 'timeToInt']
In [10]:
# 当我们不知道一个对象是否有某个属性或者方法时, 使用 hasattr()会避免报错
print('t4 = ', t4)
if hasattr(t4, 'minute'):
    print(getattr(t4, 'minute'))
if hasattr(t4, 'hour'):
    setattr(t4, 'hour', 10)
    
print('t4 = ', t4)

if hasattr(t4, '__add__'): # 如果定义了 __add__()函数, 则可以使用 + 运算符
    print('t4 + t4 = ', t4 + t4)

# 恰当使用 hasattr() 的例子
def readFile(reader):
    file = 'config.xml'
    if hasattr(reader, 'read'):
        return reader.read(file) # 当不确定reader是否有'read'方法的时候, 这样写可以避免出错
t4 =  18 : 58 : 37
58
t4 =  10 : 58 : 37
t4 + t4 =  21 : 57 : 14

访问控制

  • 以双下划线 __ 开头的变量,在外部将无法访问到(通过特殊手段还是可以做到的)
  • 以单下划线 _ 开头的变量, 表示不希望从外部进行访问,但仍然能够从外部访问到
  • 以双下划线 开头,且以双下划线 结尾的变量或函数,用于实现特定的功能,不应该从外部直接访问,虽然可以访问到
In [11]:
class Student():
    def __init__(self, name, score, age):
        self.name = name
        self._score = score
        self.__age = age
        self.__other__ = 'other'
        
    def get_age(self):
        return self.__age
    
s = Student('juliya', 90, 20)
print(s.name)
print(s._score)
print(s.__other__)
print(s.get_age())
try:
    print(s.__age)   # 访问私有属性, 这里将报错
except Exception as e:
    print(e)
juliya
90
other
20
'Student' object has no attribute '__age'

类属性和实例属性

  • 类属性定义在类内部, 而不在函数体内部
  • 类的共有属性可以被该类的所有实例对象访问到, 而私有属性无法被实例对象访问
  • 如果实例对象绑定了与类属性同名的属性, 则实例属性优先级高于类属性
  • 当hasattr()检测的是实例对象, 询问的是某个类属性, 并且实例对象没有该属性是, 也会返回True
In [12]:
class Student():
    name = 'student'
    __name__ = 'student'
    __name = 'student'
    def __init__(self):
        pass

s = Student()
print('Student.name = ', Student.name)
print('s.name = ',s.name)
print('s.__name__ = ', s.__name__)
try:
    print('s.__name = ',s.__name)
except Exception as e:
    print(e)
    
s.name = 'jack' # 给实例对象绑定同名属性, 将覆盖同名的类属性
print('s.name = ',s.name)

if hasattr(s, 'name') : 
    del s.name  # 删除实例对象的属性
    
if hasattr(s, 'name'):
    print('s.name = ', s.name)          # 将输出类的 name 属性的值 'student'

if hasattr(Student, 'name') :
    del Student.name  # 删除类的属性
    
if hasattr(s, 'name') :  # False
    print(s.name) 
Student.name =  student
s.name =  student
s.__name__ =  student
'Student' object has no attribute '__name'
s.name =  jack
s.name =  student