Python定制类

[TOC] 看到类似__slots__这种形如__xxx__的变量或者函数名就要注意,这些在Python中是有特殊用途的。 __slots__我们已经知道怎么用了,__len__()方法我们也知道是为了能让class作用于len()函数。 除此之外,Python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。 __str__, __repr__ 我们先定义一个Student类,打印一个实例: >>> class Student(object): ... def __init__(self, name): ... self.name = name ... >>> print(Student('Michael')) <__main__.Student object at 0x109afb190> 打印出一堆<__main__.Student object at 0x109afb190>,不好看。 怎么才能打印得好看呢?只需要定义好__str__()方法,返回一个好看的字符串就可以了: >>> class Student(object): ... def __init__(self, name): ... self.name = name ... def __str__(self): ... return 'Student object (name: %s)' % self.name ... >>> print(Student('Michael')) Student object (name: Michael) 这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。 但是细心的朋友会发现直接敲变量不用print,打印出来的实例还是不好看: >>> s = Student('Michael') >>> s <__main__.Student object at 0x109afb310> 这是因为直接显示变量调用的不是__str__(),而是__repr__(),两者的区别是__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,也就是说,__repr__()是为调试服务的。 ...

May 24, 2016

Python多重继承

[TOC] 多重继承 一个子类同时获得多个父类的所有功能。 继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。 回忆一下Animal类层次的设计,假设我们要实现以下4种动物: Dog - 狗狗; Bat - 蝙蝠; Parrot - 鹦鹉; Ostrich - 鸵鸟。 如果按照哺乳动物和鸟类归类,我们可以设计出这样的类的层次: ┌───────────────┐ │ Animal │ └───────────────┘ │ ┌────────────┴────────────┐ │ │ ▼ ▼ ┌─────────────┐ ┌─────────────┐ │ Mammal │ │ Bird │ └─────────────┘ └─────────────┘ │ │ ┌─────┴──────┐ ┌─────┴──────┐ │ │ │ │ ▼ ▼ ▼ ▼ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Dog │ │ Bat │ │ Parrot │ │ Ostrich │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ 但是如果按照“能跑”和“能飞”来归类,我们就应该设计出这样的类的层次: ...

May 23, 2016

Python内置@property装饰器实现方法转属性

[TOC] 在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改: s = Student() s.score = 9999 这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数: class Student(object): def get_score(self): return self._score def set_score(self, value): if not isinstance(value, int): raise ValueError('score must be an integer!') if value < 0 or value > 100: raise ValueError('score must between 0 ~ 100!') self._score = value 现在,对任意的Student实例进行操作,就不能随心所欲地设置score了: >>> s = Student() >>> s.set_score(60) # ok! >>> s.get_score() 60 >>> s.set_score(9999) Traceback (most recent call last): ... ValueError: score must between 0 ~ 100! 但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。 ...

May 20, 2016

Python变量__slots__属性限制

[TOC] 正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。先定义class: class Student(object): pass 然后,尝试给实例绑定一个属性: >>> s = Student() >>> s.name = 'Michael' # 动态给实例绑定一个属性 >>> print(s.name) Michael 还可以尝试给实例绑定一个方法: >>> def set_age(self, age): # 定义一个函数作为实例方法 ... self.age = age ... >>> from types import MethodType >>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法 >>> s.set_age(25) # 调用实例方法 >>> s.age # 测试结果 25 但是,给一个实例绑定的方法,对另一个实例是不起作用的: >>> s2 = Student() # 创建新的实例 >>> s2.set_age(25) # 尝试调用方法 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'set_age' 为了给所有实例都绑定方法,可以给class绑定方法: ...

May 19, 2016

Python实例属性和类属性

[TOC] 由于Python是动态语言,根据类创建的实例可以任意绑定属性。 给实例绑定属性的方法是通过实例变量,或者通过self变量: class Student(object): def __init__(self, name): self.name = name s = Student('Bob') s.score = 90 但是,如果Student类本身需要绑定一个属性呢?可以直接在class中定义属性,这种属性是类属性,归Student类所有: class Student(object): name = 'Student' 当我们定义了一个类属性后,这个属性虽然归类所有,但类的所有实例都可以访问到。来测试一下: >>> class Student(object): ... name = 'Student' ... >>> s = Student() # 创建实例s >>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性 Student >>> print(Student.name) # 打印类的name属性 Student >>> s.name = 'Michael' # 给实例绑定name属性 >>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性 Michael >>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问 Student >>> del s.name # 如果删除实例的name属性 >>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了 Student 从上面的例子可以看出,在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同名称的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。 ...

May 18, 2016

Python

[TOC] 路径和名字的获取方式 import os,sys if __name__ == '__main__': filename = __file__ print("filename : ", filename) filepath = os.path.abspath(filename) print("filepath : ", filepath) dirname = os.path.dirname(__file__) print("dirname : ", dirname) dirpath = os.path.abspath(dirname) print("dirpath : ", dirpath) 输出: filename : C:/Users/dell/Desktop/git/SimpleStorageServer-Windows10/test.py filepath : C:\Users\dell\Desktop\git\SimpleStorageServer-Windows10\test.py dirname : C:/Users/dell/Desktop/git/SimpleStorageServer-Windows10 dirpath : C:\Users\dell\Desktop\git\SimpleStorageServer-Windows10 os.walk的用法 os.walk(directorypath, topdown=True, onerror=None, followlinks=False) 这里directorypath 必须是一个目录的路径,如果是一个文件的路径,则不会得到任何东西 第一个为起始路径,第二个为起始路径下的文件夹,第三个是起始路径下的文件。 dirpath 是一个string,代表目录的路径, dirnames 是一个list,包含了dirpath下所有子目录的名字。 filenames 是一个list,包含了非目录文件的名字。 这些名字不包含路径信息,如果需要得到全路径,需要使用os.path.join(dirpath, name).通过for循环自动完成递归枚举 import os,sys if __name__ == '__main__': filename = __file__ print("filename : ", filename) filepath = os.path.abspath(filename) print("filepath : ", filepath) dirname = os.path.dirname(__file__) print("dirname : ", dirname) dirpath = os.path.abspath(dirname) print("dirpath : ", dirpath) # for dirpaths, dirnames, filenames in os.walk(filepath): # print(dirpaths, dirnames, filenames) # for dirpaths, dirnames, filenames in os.walk(dirpath, topdown=False): for dirpaths, dirnames, filenames in os.walk(dirpath): print(dirpaths, dirnames, filenames) # print(dirpaths, filenames) # for i in filenames: # print(os.path.join(dirpaths,i)) # for j in dirnames: # print(os.path.join(dirpaths, j)) # pass 输出: ...

May 17, 2016

Python获取对象信息

[TOC] 当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢? 使用type() 首先,我们来判断对象类型,使用type()函数: 基本类型都可以用type()判断: >>> type(123) <class 'int'> >>> type('str') <class 'str'> >>> type(None) <type(None) 'NoneType'> 如果一个变量指向函数或者类,也可以用type()判断: >>> type(abs) <class 'builtin_function_or_method'> >>> type(a) <class '__main__.Animal'> 但是type()函数返回的是什么类型呢?它返回对应的Class类型。如果我们要在if语句中判断,就需要比较两个变量的type类型是否相同: >>> type(123)==type(456) True >>> type(123)==int True >>> type('abc')==type('123') True >>> type('abc')==str True >>> type('abc')==type(123) False types 判断基本数据类型可以直接写int,str等,但如果要判断一个对象是否是函数怎么办?可以使用types模块中定义的常量: >>> import types >>> def fn(): ... pass ... >>> type(fn)==types.FunctionType True >>> type(abs)==types.BuiltinFunctionType True >>> type(lambda x: x)==types.LambdaType True >>> type((x for x in range(10)))==types.GeneratorType True 使用isinstance() 对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。 ...

May 17, 2016

Python类的继承和多态

[TOC] 继承 在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。 比如,我们已经编写了一个名为Animal的class,有一个run()方法可以直接打印: class Animal(object): def run(self): print('Animal is running...') 当我们需要编写Dog和Cat类时,就可以直接从Animal类继承: class Dog(Animal): pass class Cat(Animal): pass 对于Dog来说,Animal就是它的父类,对于Animal来说,Dog就是它的子类。Cat和Dog类似。 继承有什么好处?最大的好处是子类获得了父类的全部功能。由于Animial实现了run()方法,因此,Dog和Cat作为它的子类,什么事也没干,就自动拥有了run()方法: dog = Dog() dog.run() cat = Cat() cat.run() 运行结果如下: Animal is running... Animal is running... 当然,也可以对子类增加一些方法,比如Dog类: class Dog(Animal): def run(self): print('Dog is running...') def eat(self): print('Eating meat...') 多态 继承的第二个好处需要我们对代码做一点改进。你看到了,无论是Dog还是Cat,它们run()的时候,显示的都是Animal is running...,符合逻辑的做法是分别显示Dog is running...和Cat is running...,因此,对Dog和Cat类改进如下: class Dog(Animal): def run(self): print('Dog is running...') class Cat(Animal): def run(self): print('Cat is running...') 再次运行,结果如下: Dog is running... Cat is running... 当子类和父类都存在相同的run()方法时,我们说,子类的run()覆盖了父类的run(),在代码运行的时候,总是会调用子类的run()。这样,我们就获得了继承的另一个好处:多态。 ...

May 16, 2016

Python类的访问限制

[TOC] 在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。 但是,从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性: >>> bart = Student('Bart Simpson', 59) >>> bart.score 59 >>> bart.score = 99 >>> bart.score 99 如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改: class Student(object): def __init__(self, name, score): self.__name = name self.__score = score def print_score(self): print('%s: %s' % (self.__name, self.__score)) 改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name和实例变量.__score了: >>> bart = Student('Bart Simpson', 59) >>> bart.__name Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute '__name' 这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。 但是如果外部代码要获取name和score怎么办?可以给Student类增加get_name和get_score这样的方法: class Student(object): ... def get_name(self): return self.__name def get_score(self): return self.__score 如果又要允许外部代码修改score怎么办?可以再给Student类增加set_score方法: ...

May 15, 2016

Python类和实例

[TOC] 面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。 仍以Student类为例,在Python中,定义类是通过class关键字: class Student(object): pass class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。 定义好了Student类,就可以根据Student类创建出Student的实例,创建实例是通过类名+()实现的: >>> bart = Student() >>> bart <__main__.Student object at 0x10a67a590> >>> Student <class '__main__.Student'> 可以看到,变量bart指向的就是一个Student的实例,后面的0x10a67a590是内存地址,每个object的地址都不一样,而Student本身则是一个类。 可以自由地给一个实例变量绑定属性,比如,给实例bart绑定一个name属性: >>> bart.name = 'Bart Simpson' >>> bart.name 'Bart Simpson' 由于类可以起到模板的作用,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去: class Student(object): def __init__(self, name, score): self.name = name self.score = score 注意:特殊方法__init__前后分别有两个下划线!!! 注意到__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在__init__方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。 有了__init__方法,在创建实例的时候,就不能传入空的参数了,必须传入与__init__方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去: >>> bart = Student('Bart Simpson', 59) >>> bart.name 'Bart Simpson' >>> bart.score 59 和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。 数据封装 面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩: >>> def print_score(std): ... print('%s: %s' % (std.name, std.score)) ... >>> print_score(bart) Bart Simpson: 59 但是,既然Student实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在Student类的内部定义访问数据的函数,这样,就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的,我们称之为类的方法: ...

May 14, 2016