版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
2023/1/3第8章程序开发进阶2022/12/27第8章程序开发进阶目录2023/1/328.1.面向对象程序开发进阶8.2.函数式编程8.3.多线程编程目录2022/12/2728.1.面向对象程序开发进阶8.22023/1/38.1面向对象程序设计进阶在这一节中,我们将更深入地学习Python对面向对象的支持,学习一些技术以合理减少必须编写的代码总量,并扩展程序功能。2022/12/278.1面向对象程序设计进阶在这一节中,32023/1/38.1面向对象程序设计进阶8.1.1控制属性存取有时候,类属性的值在运算时被计算出来要比预先存储更加方便,下面给出一个这种类的完整实现。
classOrd:
def__getattr__(self,char):
returnord(char)
ord()是以个Python内置函数,返回代表一个Unicode字符的数字值。当Ord类被定义后,我们可以创建一个实例,ord=Ord(),这样我们就得到了内置ord()函数的替代品。例如,ord.a返回97,ord.A返回65。(但ord.!以及类似的用法会出现语法错误。)2022/12/278.1面向对象程序设计进阶8.1.142023/1/38.1面向对象程序设计进阶8.1.1控制属性存取如果直接在Python运行环境,如IDLE中输入Ord类的定义,之后试图执行ord=Ord(),则不能正确工作。这是因为实例与Ord类中使用的ord()内置函数重名,因此,ord()调用实际上变成了对ord实例的调用,并导致TypeError异常。但如果我们引入包含Ord类的模块,则不会出现这个问题。因为交互式创建的ord对象与Ord类使用的ord()函数分别存在于两个模块中,彼此不会互相替代。如果我们确实要交互式地创建一个类并需要重用某个内置对象的名称,就必须确保该类调用的是内置对象,这里可以通过导入builtins模块来提供对所有内置函数无歧义的访问。在代码中调用builtins.ord()来替代ord()。2022/12/278.1面向对象程序设计进阶8.1.152023/1/38.1面向对象程序设计进阶8.1.2函子在计算机科学中,函子(Functor)是指一个对象,该对象可以像普通的函数一样被调用。由于Python的函数本身就是对象,所以在Python中,函子可以被理解为另外一种函数对象。任何含有__call()__特殊方法的类都是函子。使用函子的主要好处是,可以维护一些状态信息。我们也可以用普通函数完成同样的任务,但如果我们同时需要存储一些状态信息,或者进行更复杂的处理,函子通常是正确的选择2022/12/278.1面向对象程序设计进阶8.1.262023/1/38.1面向对象程序设计进阶8.1.2函子下面创建了一个函子,功能是除去字符串末尾的基本标点符号。其创建和使用格式如下。strip_punctuation=Strip(",;:.!?")strip_punctuation("whoops!")#返回:'whoops'
这里我们创建了一个Strip函子的实例,并用值“,;:.!?”将其初始化。当这个实例被调用时,都会返回作为参数的字符串,并除去其末尾的标点符号。下面给出Strip类的完整实现。classStrip: def__init__(self,characters): self.characters=characters def__call__(self,string): returnstring.strip(self.characters)。2022/12/278.1面向对象程序设计进阶8.1.272023/1/38.1面向对象程序设计进阶8.1.3上下文管理器使用上下文管理器(ContextManager)可以简化代码,这是通过确保某些操作在特定代码块执行前与执行后再进行来实现的。之所以能实现这种操作,是因为上下文管理器定义了两个特殊的方法—__enter__()和__exit__(),在with语句的范围类,Python会对其进行特别处理。当一个上下文管理器在with语句中创建时,它的__enter__()方法会自动被调用。当上下文管理器超出其作用范围时,到了with语句之外时,其__exit__()方法将自动被调用。2022/12/278.1面向对象程序设计进阶8.1.382023/1/38.1面向对象程序设计进阶8.1.3上下文管理器我们可以自定义上下文管理器,或者使用预定义的上下文管理器。调用内置的open()函数所返回的文件对象就是上下文管理器。使用上下文管理器的语法如下:
withexpressionasvariable: suite2022/12/278.1面向对象程序设计进阶8.1.392023/1/38.1面向对象程序设计进阶8.1.4描述符描述符(Descriptor)也是类,用于为其他类的属性提供访问控制。实现了一个或多个描述符特殊方法(例如__get__(),__set__()和__delete__())的任何类都可以被称为(也可以用作)描述符。内置的property()与classmethod()函数都是使用描述符实现的。理解描述符的关键是:尽管在类中创建描述符的实例时将其作为一个类属性,但是Python在访问描述符时是通过类的实例进行的。2022/12/278.1面向对象程序设计进阶8.1.4102023/1/38.1面向对象程序设计进阶8.1.4描述符假定有一个类,其实例存放一些字符串。并且需要的时候可以获取字符串的XML转义处理后的版本。此处创建一个描述符,以便根据需要提供XML转义处理后的字符串,而又不需要对其进行存储。描述符的类如下:
classProduct: __slots__=("__name","__description","__price") name_as_xml=XmlShadow("name") description_as_xml=XmlShadow("description")
def__init__(self,name,description,price): self.__name=name self.description=description self.price=price2022/12/278.1面向对象程序设计进阶8.1.4112023/1/38.1面向对象程序设计进阶8.1.4描述符现在考察另外一种描述符,该描述符可用于存储某个对象的所有属性数据,对象本身则不再需要存储任何内容。在该实例中,我们仅使用一个字典来进行存储。下面给出使用了描述符修改后的Point类的开头。classPoint: __slots__=() x=ExternalStorage("x") y=ExternalStorage("y")
def__init__(self,x=0,y=0): self.x=x self.y=y
通过将_slots_设置为一个空元组,可以保证类不会存储任何数据属性。。2022/12/278.1面向对象程序设计进阶8.1.4122023/1/38.1面向对象程序设计进阶8.1.5抽象基类抽象基类(AbstractBaseClass)也是一个类,但这个类不能用于创建对象。使用抽象基类的目的是为了定义接口(Interface)。抽象基类会列出一些方法与特性,而继承自抽象基类的类必须对其进行实现。这是种很有用的机制,因为我们可以将抽象基类当作一种保证,确保任何自抽象基类衍生而来的类均会实现抽象基类指定的方法与特性。抽象基类中包含至少一种抽象方法或特性,抽象方法在定义时可以不实现(其suite为pass,或者产生一个NotImplementedError异常,以强制子类对其重新实现),也可以包含具体的实现。抽象基类也可以包含其他具体的(非抽象)方法与特性。2022/12/278.1面向对象程序设计进阶8.1.5132023/1/38.1面向对象程序设计进阶8.1.5抽象基类只有在实现了所有继承而来的抽象方法与抽象特性之后,从抽象基类衍生而来的类才可以创建实例。对与那些已经包含具体实现的抽象方法,衍生类可以简单地使用super()来调用基类的实现版本。与通常一样,任何具体方法与特性都可以通过继承获取。所有抽象基类必须包含元类(Metaclass)abc.ABCMeta(来自abc模块),或来自其某个子类。2022/12/278.1面向对象程序设计进阶8.1.5142023/1/38.1面向对象程序设计进阶8.1.6多继承多继承是指某个类继承自两个或多个类。Python完全支持多继承。多继承存在的问题是,可能导致同一个类被继承多次(比如一个类中某两个基类继承自同一个类)。这意味着,某个被调用的方法如果不在子类中,而是在两个或多个基类中(或基类的基类中),那么被调用方法的具体版本取决于方法的解析顺序,从而使得使用多继承得到的类存在模糊的可能。2022/12/278.1面向对象程序设计进阶8.1.6152023/1/38.1面向对象程序设计进阶8.1.6多继承声明多继承的方式与单继承类似。
classBase1: pass
classBase2: pass
classMultiDerived(Base1,Base2): pass
MultiDerived类继承自类Base1和Base2,并同时拥有这两个类的特性。为了保证多继承中调用方法的正确性,我们有必要考察Python中方法的解析顺序。2022/12/278.1面向对象程序设计进阶8.1.6162023/1/38.1面向对象程序设计进阶8.1.6多继承有一套规则用于找到类中方法的解析顺序,被称为方法解析顺序(MRO),MRO保证一个类总是在它的基类之前出现。一个类的MRO可以通过__mro__属性或mro()方法查看,前者返回一个元组,后者返回一个列表:
>>>MultiDerived.__mro__(<class'__main__.MultiDerived'>,<class'__main__.Base1'>,<class'__main__.Base2'>,<class'object'>)
>>>MultiDerived.mro()[<class'__main__.MultiDerived'>,<class'__main__.Base1'>,<class'__main__.Base2'>,<class'object'>]2022/12/278.1面向对象程序设计进阶8.1.6172023/1/38.1面向对象程序设计进阶8.1.7元类元类之于类,就像类之于实例。也就是说,元类用于创建类,正如类用于创建实例一样。元类最简单的用途就是自定义抽象基类,正如我们在前面所看到的那样。不同于很多编程语言,Python的类也是对象,当使用关键字class后,Python会执行它,并创建一个对象。下面的代码在内存中创建了一个名为“ObjectCreator”的类。
>>>classObjectCreator(object):pass
这个类对象自身可以创建对象,即类的实例,但它本身是一个对象,所以我们可以对其进行赋值、添加属性或将其作为函数的参数。2022/12/278.1面向对象程序设计进阶8.1.7182023/1/38.1面向对象程序设计进阶8.1.7元类更进一步,我们能够动态创建类,就如同创建其他对象一样,我们可以用class关键字来创建类。>>>defchoose_class(name):ifname=='foo':classFoo(object):passreturnFoo else:classBar(object):passreturnBar
2022/12/278.1面向对象程序设计进阶8.1.7192023/1/38.1面向对象程序设计进阶8.1.7元类因为类也是对象,所以必定存在一个工具,用来创建类。当我们使用class关键字时,Python自动创建了对象,但正如许多Python的其他特性,有一种方法也可以让我们手动创建这个对象。type()函数有一种完全不同的功能,即可以动态地创建类。我们可以将类的描述作为参数传递给type()函数,然后得到一个类的返回值。
所以,类:>>>classMyClass(object):bar=True
可以用以下的方式创建。
>>>MyClass=type('MyClass',(),{'bar':True})
2022/12/278.1面向对象程序设计进阶8.1.720目录2023/1/328.1.面向对象程序开发进阶8.2.函数式编程8.3.多线程编程目录2022/12/2728.1.面向对象程序开发进阶8.212023/1/38.2函数式编程函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成多个简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。函数式编程(FunctionalProgramming),其思想更接近数学计算,抽象度较高。函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,对任意一个函数,只要输入是确定的,输出就是确定的。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,甚至还允许返回一个函数。2022/12/278.2函数式编程函数是Python内建222023/1/38.2函数式编程8.2.1高阶函数要理解高阶函数(Higher-orderfunction),首先要对Python中的函数概念有正确的理解。由于Python中一切都是对象,所以可以让一个变量指向函数。在Python中,我们不仅可以把函数调用的结果赋值给变量,也可以把函数本身赋值给变量。
>>>f=abs>>>f<built-infunctionabs>
然后通过这个变量来调用函数:
>>>f(-10)102022/12/278.2函数式编程8.2.1高阶函数要232023/1/38.2函数式编程8.2.1高阶函数变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。一个简单的高阶函数代码如下。
defadd(x,y,f):returnf(x)+f(y)
当我们调用add(-2,6,abs)时,参数x,y和f分别接收-2,6和abs,根据函数定义,我们知道实际的计算为:abs(-2)+abs(6)=2+6=8。可以输入以下代码进行验证。
>>>add(-2,6,abs)82022/12/278.2函数式编程8.2.1高阶函数变242023/1/38.2函数式编程8.2.2闭包通常我们把定义在一个函数里的函数称为嵌套函数,下面是一个简单嵌套函数的定义。
defprint_msg(msg): """Thisistheouterenclosingfunction"""
defprinter(): """Thisisthenestedfunction""" print(msg)
returnprinter
2022/12/278.2函数式编程8.2.2闭包通常我252023/1/38.2函数式编程8.2.2闭包我们将嵌套的printer函数作为返回值返回,然后我们尝试调用这个函数。
>>>f=print_msg("Hello")>>>f()Hello
print_msg()函数被调用,并接收参数“Hello”,返回的函数被赋值给f。当我们调用f()时,传入的参数“Hello”仍然被保存着,即使print_msg()函数的执行已经结束。在外部函数(
EnclosingFunction)作用域里的数据“Hello”,即便在超出作用域后仍然被保存着。这种把某些数据与函数关联在一起的技术在Python中被称为闭包(Closure)。
2022/12/278.2函数式编程8.2.2闭包我们将262023/1/38.2函数式编程8.2.2闭包创建一个闭包所需要具备的条件:必须有一个嵌套函数。嵌套函数必须应用一个在外部函数中定义的值。外部函数必须将嵌套函数作为返回值返回。闭包可以提供一些数据隐藏的方式,也可用来减少全局数据的使用。另外,闭包为一些问题提供了更符合面向对象思想的解决方案。2022/12/278.2函数式编程8.2.2闭包创建一272023/1/38.2函数式编程8.2.3匿名函数当我们传入函数作为参数时,有些时候并不需要显式地定义函数,直接传入匿名函数更方便。匿名函数在Python中被称为lambda函数。在Python中,对匿名函数提供了有限的支持。还是以map()函数为例,为了计算f(x)=x2,除了定义一个f(x)的函数外,还可以在参数中直接传入匿名函数:
>>>list(map(lambdax:x*x,[1,2,3,4,5,6,7,8,9]))[1,4,9,16,25,36,49,64,81]
2022/12/278.2函数式编程8.2.3匿名函数当282023/1/38.2函数式编程8.2.3匿名函数关键字lambda表示匿名函数,冒号前面的x表示函数参数。使用匿名函数会受到一些限制,只能有一个表达式,但也不用写return,返回值就是该表达式的结果。因为函数没有名字,匿名函数也带来一个好处:不必担心函数名冲突。此外,匿名函数也是一个函数对象,也就是说可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
>>>f=lambdax:x*x>>>f<function<lambda>at0x101c6ef28>>>>f(5)25
同样,也可以把匿名函数作为返回值返回。
2022/12/278.2函数式编程8.2.3匿名函数关292023/1/38.2函数式编程8.2.4修饰器Python中,我们可以使用修饰器(Decorator)为现有的代码添加功能。这种使用一部分程序在编译时改变剩余部分程序的技术,被称为元编程(Metaprogramming)。为了理解修饰器,我们必须再次强调一个概念,即Python中一切都是对象,包括函数。所以函数也可以作为另一个函数的返回值或参数。函数和方法通常被称为可调用对象(Callable),事实上,任何实现了__call__()方法的对象都是可调用对象。修饰器就是一个返回值是可调用对象的可调用对象。一个基本的修饰器接收一个函数,然后向其添加功能,最后返回这个函数。2022/12/278.2函数式编程8.2.4修饰器Py302023/1/38.2函数式编程8.2.4修饰器下面的代码中定义了一个简单的修饰器make_pretty: defmake_pretty(func): definner(): print("Igotdecorated") func() returninner
defordinary(): print("Iamordinary")
2022/12/278.2函数式编程8.2.4修饰器下面312023/1/38.2函数式编程8.2.4修饰器
运行代码的结果如下:
>>>ordinary()Iamordinary
>>>pretty=make_pretty(ordinary)>>>pretty()IgotdecoratedIamordinary2022/12/278.2函数式编程8.2.4修饰器
322023/1/38.2函数式编程8.2.4修饰器通常我们会把原函数重新赋值为修饰器返回的函数:
ordinary=make_pretty(ordinary)
在Python中,有一个语法能简化这个操作,可以通过把@符号加修饰器的名称放在需要修饰的函数上方来实现,例如:@make_prettydefordinary():print("Iamordinary")
与下面的代码是等效的:defordinary():print("Iamordinary")ordinary=make_pretty(ordinary)
2022/12/278.2函数式编程8.2.4修饰器通常332023/1/38.2函数式编程8.2.5偏函数偏函数(PartialFunction)是指使用现存函数以及某些参数来创建函数,新建函数与原函数执行的功能相同,但是某些参数是固定的,因此调用者不需要传递这些参数。要注意的是,这里的偏函数和数学意义上的偏函数不一样。Python的functools模块提供了偏函数功能。在编写函数时,可以通过设定参数的默认值,降低函数调用的复杂度。使用偏函数也可以做到这一点。2022/12/278.2函数式编程8.2.5偏函数偏函342023/1/38.2函数式编程8.2.5偏函数例:int()函数可以把字符串转换为整数,默认按参数为十进制进行转换,并输出十进制的整数。
>>>int('1234')1234
int()函数还提供额外的base参数,默认值为10。如果传入base参数“base=N”就可以将参数字符串视为N进制数进行转换,并仍然输入十进制结果。
假设我们要转换大量的二进制字符串,但每次都传入int(x,base=2)非常麻烦,此时我们就可以定义一个int2()的函数,默认把base=2作为参数。
defint2(x,base=2):returnint(x,base)2022/12/278.2函数式编程8.2.5偏函数例:35目录2023/1/328.1.面向对象程序开发进阶8.2.函数式编程8.3.多线程编程目录2022/12/2728.1.面向对象程序开发进阶8.362023/1/38.3多线程编程在本节中,我们将讨论在一些在代码中进行并行处理的方法。首先我们会讨论进程和线程的概念以及两者的区别。接下来我们会介绍在Python中使用多线程编程的方法,并给出一些代码。在本章的最后,将介绍一个使用threading模块实现的Python多线程编程的例子。2022/12/278.3多线程编程在本节中,我们将讨论在372023/1/38.3多线程编程8.3.1多线程的编程动机在多线程编程出现前,计算机程序被分成多个步骤,由CPU一步一步地按序执行。在需要保证一个任务中每步都按顺序执行时,这种方法是自然而合理的。然而当一个任务可以被分成多个子任务,并且这些子任务相互独立时,我们更希望这些任务能够并行执行,这样的并行处理可以大幅度地提升整个任务的效率。这就是多线程编程的目的。2022/12/278.3多线程编程8.3.1多线程的编382023/1/38.3多线程编程8.3.2进程和线程对于操作系统来说,一个正在执行的任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程。每个进程都有自己的地址空间、内存、数据栈以及其他记录其运行轨迹的辅助数据。操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间。不过各个进程有自己的内存空间、数据栈等,所以只能使用进程间通信(IPC),而不能直接共享信息。有些时候一个进程还不止同时干一件事,比如记事本,它除了要处理用户的文字输入,还要负责文字的排版和用户搜索等工作。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据以及相互通信。在进程的整个运行过程中,每个线程都只做自己的事,在需要的时候跟其他的线程共享运行的结果。2022/12/278.3多线程编程8.3.2进程和线程392023/1/38.3多线程编程8.3.2进程和线程由于每个进程至少要干一件事,所以一个进程至少有一个线程。当然,一些复杂的进程可以有多个线程,多个线程可以同时执行,多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂地交替运行,看起来就像同时执行一样。然而,真正地同时执行多线程需要多核CPU才可能实现。2022/12/278.3多线程编程8.3.2进程和线程402023/1/38.3多线程编程8.3.3线程与PythonPython代码的执行由Python虚拟机来控制(又称为解释器主循环)。Python被设计为在主循环中,同时只有一个线程在执行,就像在单CPU的系统中运行多个进程那样,内存中可以存放多个程序,但任意时刻,只有一个程序在CPU中运行。同样地,虽然Python解释器中可以“运行”多个线程,但在任意时刻,只有一个线程在解释器中运行。2022/12/278.3多线程编程8.3.3线程与Py412023/1/38.3多线程编程8.3.3线程与Python在多线程环境中,Python虚拟机按以下方式执行:设置GIL。切换到一个线程去运行。运行以下程序。指定数量的字节码指令;线程主动让出控制(可以调用time.sleep(0))。把线程设置为睡眠状态。解锁GIL。再次重复以上所有步骤。调用外部代码时(如内置的C/C++扩展函数),GIL会被锁定,直到这个函数执行结束。不过,编写扩展函数的程序员可以主动解锁GIL,所以作为Python开发人员,不用担心我们的Python代码会被锁住。2022/12/278.3多线程编程8.3.3线程与Py422023/1/38.3多线程编程8.3.4thread模块要创建一个新的线程,最简单且有效的方法是引入Thread模块,调用其thread.start_new_thread(function,args,kwargs=None)方法。这个方法会立即返回一个新线程,并调用作为参数的function函数,args是包含位置参数的元组,使用空的元组说明调用function函数时不传递参数,kwargs是可选的包含关键字参数的字典。当函数运行完成(即返回)时,这个新线程也将结束运行。2022/12/278.3多线程编程8.3.4threa432023/1/38.3多线程编程例:importthreadimporttime#定义每个线程中执行的函数:每隔delay时间便打印当前的时间defprint_time(threadName,delay):count=0whilecount<5:time.sleep(delay)count+=1print("{}:{}".format(threadName,time.ctime(time.tine())))#创建两个新线程try:
thread.start_new_thread(print_time,("Thread-1",2,))thread.start_new_thread(print_time,("Thread-2",4,))except:print"Error:unabletostartthread"
while1: pass2022/12/278.3多线程编程例:importth442023/1/38.3多线程编程8.3.4thread模块当执行上述代码时,会得到类似下面的结果:Thread-1:ThuSep115:42:172016Thread-1:ThuSep115:42:192016Thread-2:ThuSep115:42:192016Thread-1:ThuSep115:42:212016Thread-2:ThuSep115:42:232016Thread-1:ThuSep115:42:232016Thread-1:ThuSep115:42:252016Thread-2:ThuSep115:42:272016Thread-2:ThuSep115:42:312016Thread-2:ThuSep115:42:352016
虽然thread模块能让我们快速有效地使用多线程,但其相对于更新的threading模块来说有较多的限制。在实际应用中,应该总是使用threading模块。2022/12/278.3多线程编程8.3.4threa452023/1/38.3多线程编程8.3.5threading模块threading模块在Python2.4时被引入。相对于thread模块,threading模块提供更强大也更高级的多线程支持。除了含有全部thread模块的方法外,threading模块还添加了一些实用的新方法。下面是一些比较有代表性的新方法。threading.activeCount():返回当前活动的线程对象数量。threading.currentThread():返回当前线程对象。threading.enumerate():返回当前活动线程的列表。2022/12/278.3多线程编程8.3.5threa462023/1/38.3多线程编程8.3.5threading模块下表给出Thread模块中一些常用的方法。方法名作
用run()定义线程的功能函数(一般会被重写)start()开始线程的执行join(timeout=None)程序挂起,直到该线程结束isAlive()判断一个线程是否还在运行getName()返回线程的名字setName(name)设置线程的名字2022/12/278.3多线程编程8.3.5threa472023/1/38.3多线程编程8.3.5threading模块为了创建一个新线程,首先需要定义一个继承自Thread类的子类,重写其__init__()方法和run()方法。当创建了一个新的Thread子类后,我们就可以将其实例化,并通过调用start()方法来开始一个新线程。下面的看一个例子:2022/12/278.3多线程编程8.3.5threa482023/1/38.3多线程编程importthreadingimporttime
exitFlag=0
classmyThread(threading.Thread):def__init__(self,threadID,name,counter):threading.Thread.__init__(self)self.threadID=threadID=nameself.counter=counterdefrun(self):print("Starting"+)print_time(,self.counter,5)print("Exiting"+)2022/12/278.3多线程编程importthre492023/1/38.3多线程编程defprint_time(threadName,delay,counter):whilecounter:ifexitFlag:threadName.exit()time.sleep(delay)print("{}:{}".format(threadName,time.ctime(time.tine())))counter-=1
thread1=myThread(1,"Thread-1",1)thread2=myThread(2,"Thread-2",2)
thread1.start()thread2.start()print("ExitingMainThread")2022/12/278.3多线程编程defprint_t502023/1/38.3多线程编程8.3.5threading模块在很多时候,我们希望主线程能等待所有子线程执行完后再退出,因为主线程通常会需要使用子线程的运行结果来做一些工作;或者希望线程在访问某个文件时按照一定的顺序,以防止不同的访问顺序造成结果的不一致。threading模块包含了一个易于实现的锁机制,可用于同步线程。可以通过调用Lock()方法来创建一个新的锁,这个方法将返回一个锁对象。锁对象拥有一个acquire(blocking)方法,可以强制线程进行同步的工作。其中可选的blocking参数能够控制线程在尝试获得锁时是否等待。blocking参数设置为0时,线程若无法获得锁,则立即返回0;blocking设置为1时,线程若无法获得锁,则会被阻塞等待锁的释放。线程对象可以通过release()方法来释放一个不再需要的锁。2022/12/278.3多线程编程8.3.5threa512023/1/38.3多线程编程例(print_time函数与之前定义的相同):importthreadingimporttime
classmyThread(threading.Thread):def__init__(self,threadID,name,counter):threading.Thread.__init__(self)elf.threadID=threadID
=nameself.counter=counterdefrun(self):print("Starting"+)#获得一个锁threadLock.acquire()print_time(,self.counter,3)#运行完成后释放锁threadLock.release()2022/12/278.3多线程编程例(print_tim522023/1/38.3多线程编程threadLock=threading.Lock()threads=[]
thread1=myThread(1,"Thread-1",1)thread2=myThread(2,"Thread-2",2)
thread1.start()thread2.start()
threads.append(thread1)threads.append(thread2)
#等待所有的线程运行完成fortinthreads:t.join()print("ExitingMainThread")2022/12/278.3多线程编程threadLock532023/1/38.3多线程编程8.3.5threading模块线程1和线程2按照其start()方法的调用顺序依次运行,对线程调用join()方法后,当前线程(主线程)会接在被调用join方法的线程之后运行,也就是主线程会等待线程1和线程2运行结束后再结束。运行结果如下。
StartingThread-1StartingThread-2Thread-1:ThuSep119:11:282016Thread-1:ThuSep119:11:292016Thread-1:ThuSep119:11:302016Thread-2:ThuSep119:11:322016Thread-2:ThuSep119:11:342016Thread-2:ThuSep119:11:362016ExitingMainThread2022/12/278.3多线程编程8.3.5threa542023/1/3第8章程序开发进阶2022/12/27第8章程序开发进阶目录2023/1/328.1.面向对象程序开发进阶8.2.函数式编程8.3.多线程编程目录2022/12/2728.1.面向对象程序开发进阶8.562023/1/38.1面向对象程序设计进阶在这一节中,我们将更深入地学习Python对面向对象的支持,学习一些技术以合理减少必须编写的代码总量,并扩展程序功能。2022/12/278.1面向对象程序设计进阶在这一节中,572023/1/38.1面向对象程序设计进阶8.1.1控制属性存取有时候,类属性的值在运算时被计算出来要比预先存储更加方便,下面给出一个这种类的完整实现。
classOrd:
def__getattr__(self,char):
returnord(char)
ord()是以个Python内置函数,返回代表一个Unicode字符的数字值。当Ord类被定义后,我们可以创建一个实例,ord=Ord(),这样我们就得到了内置ord()函数的替代品。例如,ord.a返回97,ord.A返回65。(但ord.!以及类似的用法会出现语法错误。)2022/12/278.1面向对象程序设计进阶8.1.1582023/1/38.1面向对象程序设计进阶8.1.1控制属性存取如果直接在Python运行环境,如IDLE中输入Ord类的定义,之后试图执行ord=Ord(),则不能正确工作。这是因为实例与Ord类中使用的ord()内置函数重名,因此,ord()调用实际上变成了对ord实例的调用,并导致TypeError异常。但如果我们引入包含Ord类的模块,则不会出现这个问题。因为交互式创建的ord对象与Ord类使用的ord()函数分别存在于两个模块中,彼此不会互相替代。如果我们确实要交互式地创建一个类并需要重用某个内置对象的名称,就必须确保该类调用的是内置对象,这里可以通过导入builtins模块来提供对所有内置函数无歧义的访问。在代码中调用builtins.ord()来替代ord()。2022/12/278.1面向对象程序设计进阶8.1.1592023/1/38.1面向对象程序设计进阶8.1.2函子在计算机科学中,函子(Functor)是指一个对象,该对象可以像普通的函数一样被调用。由于Python的函数本身就是对象,所以在Python中,函子可以被理解为另外一种函数对象。任何含有__call()__特殊方法的类都是函子。使用函子的主要好处是,可以维护一些状态信息。我们也可以用普通函数完成同样的任务,但如果我们同时需要存储一些状态信息,或者进行更复杂的处理,函子通常是正确的选择2022/12/278.1面向对象程序设计进阶8.1.2602023/1/38.1面向对象程序设计进阶8.1.2函子下面创建了一个函子,功能是除去字符串末尾的基本标点符号。其创建和使用格式如下。strip_punctuation=Strip(",;:.!?")strip_punctuation("whoops!")#返回:'whoops'
这里我们创建了一个Strip函子的实例,并用值“,;:.!?”将其初始化。当这个实例被调用时,都会返回作为参数的字符串,并除去其末尾的标点符号。下面给出Strip类的完整实现。classStrip: def__init__(self,characters): self.characters=characters def__call__(self,string): returnstring.strip(self.characters)。2022/12/278.1面向对象程序设计进阶8.1.2612023/1/38.1面向对象程序设计进阶8.1.3上下文管理器使用上下文管理器(ContextManager)可以简化代码,这是通过确保某些操作在特定代码块执行前与执行后再进行来实现的。之所以能实现这种操作,是因为上下文管理器定义了两个特殊的方法—__enter__()和__exit__(),在with语句的范围类,Python会对其进行特别处理。当一个上下文管理器在with语句中创建时,它的__enter__()方法会自动被调用。当上下文管理器超出其作用范围时,到了with语句之外时,其__exit__()方法将自动被调用。2022/12/278.1面向对象程序设计进阶8.1.3622023/1/38.1面向对象程序设计进阶8.1.3上下文管理器我们可以自定义上下文管理器,或者使用预定义的上下文管理器。调用内置的open()函数所返回的文件对象就是上下文管理器。使用上下文管理器的语法如下:
withexpressionasvariable: suite2022/12/278.1面向对象程序设计进阶8.1.3632023/1/38.1面向对象程序设计进阶8.1.4描述符描述符(Descriptor)也是类,用于为其他类的属性提供访问控制。实现了一个或多个描述符特殊方法(例如__get__(),__set__()和__delete__())的任何类都可以被称为(也可以用作)描述符。内置的property()与classmethod()函数都是使用描述符实现的。理解描述符的关键是:尽管在类中创建描述符的实例时将其作为一个类属性,但是Python在访问描述符时是通过类的实例进行的。2022/12/278.1面向对象程序设计进阶8.1.4642023/1/38.1面向对象程序设计进阶8.1.4描述符假定有一个类,其实例存放一些字符串。并且需要的时候可以获取字符串的XML转义处理后的版本。此处创建一个描述符,以便根据需要提供XML转义处理后的字符串,而又不需要对其进行存储。描述符的类如下:
classProduct: __slots__=("__name","__description","__price") name_as_xml=XmlShadow("name") description_as_xml=XmlShadow("description")
def__init__(self,name,description,price): self.__name=name self.description=description self.price=price2022/12/278.1面向对象程序设计进阶8.1.4652023/1/38.1面向对象程序设计进阶8.1.4描述符现在考察另外一种描述符,该描述符可用于存储某个对象的所有属性数据,对象本身则不再需要存储任何内容。在该实例中,我们仅使用一个字典来进行存储。下面给出使用了描述符修改后的Point类的开头。classPoint: __slots__=() x=ExternalStorage("x") y=ExternalStorage("y")
def__init__(self,x=0,y=0): self.x=x self.y=y
通过将_slots_设置为一个空元组,可以保证类不会存储任何数据属性。。2022/12/278.1面向对象程序设计进阶8.1.4662023/1/38.1面向对象程序设计进阶8.1.5抽象基类抽象基类(AbstractBaseClass)也是一个类,但这个类不能用于创建对象。使用抽象基类的目的是为了定义接口(Interface)。抽象基类会列出一些方法与特性,而继承自抽象基类的类必须对其进行实现。这是种很有用的机制,因为我们可以将抽象基类当作一种保证,确保任何自抽象基类衍生而来的类均会实现抽象基类指定的方法与特性。抽象基类中包含至少一种抽象方法或特性,抽象方法在定义时可以不实现(其suite为pass,或者产生一个NotImplementedError异常,以强制子类对其重新实现),也可以包含具体的实现。抽象基类也可以包含其他具体的(非抽象)方法与特性。2022/12/278.1面向对象程序设计进阶8.1.5672023/1/38.1面向对象程序设计进阶8.1.5抽象基类只有在实现了所有继承而来的抽象方法与抽象特性之后,从抽象基类衍生而来的类才可以创建实例。对与那些已经包含具体实现的抽象方法,衍生类可以简单地使用super()来调用基类的实现版本。与通常一样,任何具体方法与特性都可以通过继承获取。所有抽象基类必须包含元类(Metaclass)abc.ABCMeta(来自abc模块),或来自其某个子类。2022/12/278.1面向对象程序设计进阶8.1.5682023/1/38.1面向对象程序设计进阶8.1.6多继承多继承是指某个类继承自两个或多个类。Python完全支持多继承。多继承存在的问题是,可能导致同一个类被继承多次(比如一个类中某两个基类继承自同一个类)。这意味着,某个被调用的方法如果不在子类中,而是在两个或多个基类中(或基类的基类中),那么被调用方法的具体版本取决于方法的解析顺序,从而使得使用多继承得到的类存在模糊的可能。2022/12/278.1面向对象程序设计进阶8.1.6692023/1/38.1面向对象程序设计进阶8.1.6多继承声明多继承的方式与单继承类似。
classBase1: pass
classBase2: pass
classMultiDerived(Base1,Base2): pass
MultiDerived类继承自类Base1和Base2,并同时拥有这两个类的特性。为了保证多继承中调用方法的正确性,我们有必要考察Python中方法的解析顺序。2022/12/278.1面向对象程序设计进阶8.1.6702023/1/38.1面向对象程序设计进阶8.1.6多继承有一套规则用于找到类中方法的解析顺序,被称为方法解析顺序(MRO),MRO保证一个类总是在它的基类之前出现。一个类的MRO可以通过__mro__属性或mro()方法查看,前者返回一个元组,后者返回一个列表:
>>>MultiDerived.__mro__(<class'__main__.MultiDerived'>,<class'__main__.Base1'>,<class'__main__.Base2'>,<class'object'>)
>>>MultiDerived.mro()[<class'__main__.MultiDerived'>,<class'__main__.Base1'>,<class'__main__.Base2'>,<class'object'>]2022/12/278.1面向对象程序设计进阶8.1.6712023/1/38.1面向对象程序设计进阶8.1.7元类元类之于类,就像类之于实例。也就是说,元类用于创建类,正如类用于创建实例一样。元类最简单的用途就是自定义抽象基类,正如我们在前面所看到的那样。不同于很多编程语言,Python的类也是对象,当使用关键字class后,Python会执行它,并创建一个对象。下面的代码在内存中创建了一个名为“ObjectCreator”的类。
>>>classObjectCreator(object):pass
这个类对象自身可以创建对象,即类的实例,但它本身是一个对象,所以我们可以对其进行赋值、添加属性或将其作为函数的参数。2022/12/278.1面向对象程序设计进阶8.1.7722023/1/38.1面向对象程序设计进阶8.1.7元类更进一步,我们能够动态创建类,就如同创建其他对象一样,我们可以用class关键字来创建类。>>>defchoose_class(name):ifname=='foo':classFoo(object):passreturnFoo else:classBar(object):passreturnBar
2022/12/278.1面向对象程序设计进阶8.1.7732023/1/38.1面向对象程序设计进阶8.1.7元类因为类也是对象,所以必定存在一个工具,用来创建类。当我们使用class关键字时,Python自动创建了对象,但正如许多Python的其他特性,有一种方法也可以让我们手动创建这个对象。type()函数有一种完全不同的功能,即可以动态地创建类。我们可以将类的描述作为参数传递给type()函数,然后得到一个类的返回值。
所以,类:>>>classMyClass(object):bar=True
可以用以下的方式创建。
>>>MyClass=type('MyClass',(),{'bar':True})
2022/12/278.1面向对象程序设计进阶8.1.774目录2023/1/328.1.面向对象程序开发进阶8.2.函数式编程8.3.多线程编程目录2022/12/2728.1.面向对象程序开发进阶8.752023/1/38.2函数式编程函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成多个简单的任务,这种分解可以称之为面向过程的程序设计。函数就是面向过程的程序设计的基本单元。函数式编程(FunctionalProgramming),其思想更接近数学计算,抽象度较高。函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,对任意一个函数,只要输入是确定的,输出就是确定的。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,甚至还允许返回一个函数。2022/12/278.2函数式编程函数是Python内建762023/1/38.2函数式编程8.2.1高阶函数要理解高阶函数(Higher-orderfunction),首先要对Python中的函数概念有正确的理解。由于Python中一切都是对象,所以可以让一个变量指向函数。在Python中,我们不仅可以把函数调用的结果赋值给变量,也可以把函数本身赋值给变量。
>>>f=abs>>>f<built-infunctionabs>
然后通过这个变量来调用函数:
>>>f(-10)102022/12/278.2函数式编程8.2.1高阶函数要772023/1/38.2函数式编程8.2.1高阶函数变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。一个简单的高阶函数代码如下。
defadd(x,y,f):returnf(x)+f(y)
当我们调用add(-2,6,abs)时,参数x,y和f分别接收-2,6和abs,根据函数定义,我们知道实际的计算为:abs(-2)+abs(6)=2+6=8。可以输入以下代码进行验证。
>>>add(-2,6,abs)82022/12/278.2函数式编程8.2.1高阶函数变782023/1/38.2函数式编程8.2.2闭包通常我们把定义在一个函数里的函数称为嵌套函数,下面是一个简单嵌套函数的定义。
defprint_msg(msg): """Thisistheouterenclosingfunction"""
defprinter(): """Thisisthenestedfunction""" print(msg)
returnprinter
2022/12/278.2函数式编程8.2.2闭包通常我792023/1/38.2函数式编程8.2.2闭包我们将嵌套的printer函数作为返回值返回,然后我们尝试调用这个函数。
>>>f=print_msg("Hello")>>>f()Hello
print_msg()函数被调用,并接收参数“Hello”,返回的函数被赋值给f。当我们调用f()时,传入的参数“Hello”仍然被保存着,即使print_msg()函数的执行已经结束。在外部函数(
EnclosingFunction)作用域里的数据“Hello”,即便在超出作用域后仍然被保存着。这种把某些数据与函数关联在一起的技术在Python中被称为闭包(Closure)。
2022/12/278.2函数式编程8.2.2闭包我们将802023/1/38.2函数式编程8.2.2闭包创建一个闭包所需要具备的条件:必须有一个嵌套函数。嵌套函数必须应用一个在外部函数中定义的值。外部函数必须将嵌套函数作为返回值返回。闭包可以提供一些数据隐藏的方式,也可用来减少全局数据的使用。另外,闭包为一些问题提供了更符合面向对象思想的解决方案。2022/12/278.2函数式编程8.2.2闭包创建一812023/1/38.2函数式编程8.2.3匿名函数当我们传入函数作为参数时,有些时候并不需要显式地定义函数,直接传入匿名函数更方便。匿名函数在Python中被称为lambda函数。在Python中,对匿名函数提供了有限的支持。还是以map()函数为例,为了计算f(x)=x2,除了定义一个f(x)的函数外,还可以在参数中直接传入匿名函数:
>>>list(map(lambdax:x*x,[1,2,3,4,5,6,7,8,9]))[1,4,9,16,25,36,49,64,81]
2022/12/278.2函数式编程8.2.3匿名函数当822023/1/38.2函数式编程8.2.3匿名函数关键字lambda表示匿名函数,冒号前面的x表示函数参数。使用匿名函数会受到一些限制,只能有一个表达式,但也不用写return,返回值就是该表达式的结果。因为函数没有名字,匿名函数也带来一个好处:不必担心函数名冲突。此外,匿名函数也是一个函数对象,也就是说可以把匿名函数赋值给一个变量,再利用变量来调用该函数:
>>>f=lambdax:x*x>>>f<function<lambda>at0x101c6ef28>>>>f(5)25
同样,也可以把匿名函数作为返回值返回。
2022/12/278.2函数式编程8.2.3匿名函数关832023/1/38.2函数式编程8.2.4修饰器Python中,我们可以使用修饰器(Decorator)为现有的代码添加功能。这种使用一部分程序在编译时改变剩余部分程序的技术,被称为元编程(Metaprogramming)。为了理解修饰器,我们必须再次强调一个概念,即Python中一切都是对象,包括函数。所以函数也可以作为另一个函数的返回值或参数。函数和方法通常被称为可调用对象(Callable),事实上,任何实现了__call__()方法的对象都是可调用对象。修饰器就是一个返回值是可调用对象的可调用对象。一个基本的修饰器接收一个函数,然后向其添加功能,最后返回这个函数。2022/12/278.2函数式编程8.2.4修饰器Py842023/1/38.2函数式编程8.2.4修饰器下面的代码中定义了一个简单的修饰器make_pretty: defmake_pretty(func): definner(): print("Igotdecorated") func() returninner
defordinary(): print("Iamordinary")
2022/12/278.2函数式编程8.2.4修饰器下面852023/1/38.2函数式编程8.2.4修饰器
运行代码的结果如下:
>>>ordinary()Iamordinary
>>>pretty=make_pretty(ordinary)>>>pretty()IgotdecoratedIamordinary2022/12/278.2函数式编程8.2.4修饰器
862023/1/38.2函数式编程8.2.4修饰器通常我们会把原函数重新赋值为修饰器返回的函数:
ordinary=make_pretty(ordinary)
在Python中,有一个语法能简化这个操作,可以通过把@符号加修饰器的名称放在需要修饰的函数上方来实现,例如:@make_prettydefordinary():print("Iamordinary")
与下面的代码是等效的:defordinary():print("Iamordinary")ordinary=make_pretty(ordinary)
2022/12/278.2函数式编程8.2.4修饰器通常872023/1/38.2函数式编程8.2.5偏函数偏函数(PartialFunction)是指使用现存函数以及某些参数来创建函数,新建函数与原函数执行的功能相同,但是某些参数是固定的,因此调用者不需要传递这些参数。要注意的是,这里的偏函数和数学意义上的偏函数不一样。Python的functools模块提供了偏函数功能。在编写函数时,可以通过设定参数的默认值,降低函数调用的复杂度。使用偏函数也可以做到这一点。2022/12/278.2函数式编程8.2.5偏函数偏函882023/1/38.2函数式编程8.2.5偏函数例:int()函数可以把字符串转换为整数,默认按参数为十进制进行转换,并输出十进制的整数。
>>>int('1234')1234
int()函数还提供额外的base参数,默认值为10。如果传入base参数“base=N”就可以将参数字符串视为N进制数进行转换,并仍然输入十进制结果。
假设我们要转换大量的二进制字符串,但每次都传入int(x,base=2)非常麻烦,此时我们就可以定义一个int2()的函数,默认把base=2作为参数。
defint2(x,base=2):returnint(x,base)2022/12/278.2函数式编程8.2.5偏函数例:89目录2023/1/328.1.面向对象程序开发进阶8.2.函数式编程8.3.多线程编程目录2022/12/2728.1.面向对象程序开发进阶8.902023/1/38.3多线程编程在本节中,我们将
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 磁头清洗装置项目运营指导方案
- 胶面增湿器办公用品项目营销计划书
- 扫雪机的修理或维护行业市场调研分析报告
- 伤口敷料产品供应链分析
- 台球杆产品供应链分析
- 航空器用机翼项目运营指导方案
- 国际电话服务行业相关项目经营管理报告
- 卡车露营车产品供应链分析
- O2O服务行业营销策略方案
- 家用篮产业链招商引资的调研报告
- 考古与人类学习通超星期末考试答案章节答案2024年
- 2024年上海市标准房屋租赁合同经典版(三篇)
- 新目标艺术培训中心商业策划书(3篇)
- 辽宁省沈阳市2024-2025学年七年级上学期期中模拟英语试题
- 2024人教版初中八年级数学上册第十四章整式的乘法与因式分解大单元整体教学设计
- 2023年中国铁路国际有限公司招聘考试试题及答案
- 小学高年级课后服务 scratch3.0编程教学设计 二阶课程 项目3数字华容道 第2节 数字块移动教学设计
- 国资国企企业学习二十届三中全会精神专题培训
- 履职工作计划
- 火星营地登陆计划-趣味地产周年庆典市集活动策划方案
- 2024年上海市中考地理试卷(含答案解析)
评论
0/150
提交评论