编程基础:Java、C# 和 Python 入门(二)
原文:Programming Basics: Getting Started with Java, C#, and Python
六、现在是完全不同的东西:高级 Python
在前一章中我们已经深入了解了 Java 的世界,现在是时候用 Python 做同样的事情了。我们将从文件操作开始,转到多线程和本章后面的其他更高级的主题。这里的目的是为您提供 Python 中一些更深层机制的坚实基础,以便您以后根据需要进行构建。
Python 文件操作
为了在 Python 中打开文件,我们使用了名副其实的 open( ) 函数。它使用以下语法:【文件对象名=打开(文件名,访问模式,缓冲)。最后两个属性是可选的。Python 的 open()函数的一个简单示例如下:
happyfile = open("happytext.txt")
现在,Python 使用所谓的访问模式进行文件操作。不是所有的文件都需要写入或附加到文件中,有时你需要做的只是从一个文件中读取。Python 的文件访问模式列表见表 6-1 。
表 6-1
十二种 Python 文件访问模式
| r | 以只读模式打开文件。这是 Python 中的默认访问模式 | w+ | 以读/写模式打开文件;如果文件已经存在,则覆盖它 | | 元素铷的符号 | 以只读二进制模式打开文件 | wb+ | 以读/写二进制模式打开文件;如果文件已经存在,则覆盖它 | | r+ | 以读/写模式打开文件 | a | 打开要追加到的文件。如果文件不存在,则创建一个 | | rb+ | 以读/写二进制模式打开文件 | 腹肌 | 打开一个二进制文件以追加到。如果文件不存在,则创建一个 | | w | 以只写模式打开文件;如果文件已经存在,则覆盖它 | a+ | 打开文件进行追加和读取。如果文件不存在,则创建一个 | | 世界银行 | 以只写二进制模式打开文件;如果文件已经存在,则覆盖它 | ab+ | 以二进制模式打开文件进行追加和读取。如果文件不存在,则创建一个 |缓冲在 Python 的文件操作上下文中,指的是将文件的一部分存储在临时内存区域,直到文件被完全加载的过程。基本上,零(0)值关闭缓冲,而一(1)值启用缓冲,例如, somefile = open("nobuffer.txt "," r ",1) 。如果没有给定值,Python 将使用系统的默认设置。通常保持缓冲是一个好主意,这样可以提高文件操作的速度。
Python 中的文件属性
Python 中基本上有四个主要的文件属性(其中一个大部分是历史感兴趣的,即 softspace )。如果您的项目包含哪怕是最少量的文件操作,您可能会对它们都非常熟悉。参见表 6-2 了解 Python 中这些属性的概要。
表 6-2
四个 Python 文件对象属性
|属性
|
描述
|
例子
| | --- | --- | --- | | 。名字 | 返回一个文件名 | happyfile = open("happytext.txt")打印(happyfile.name) | | 。方式 | 返回文件访问模式 | another file = open(" whacky text . txt ")打印(另一个文件.模式) | | 。关闭的 | 如果文件关闭,返回“真” | apess _ file = open(" super text . txt ")apress_file.close()如果打印(apress_file.closed):print("文件关闭!") | | 。软空间 | 如果 print 语句要在第一项前插入一个空格字符,则返回“false”。历史:从 Python 3.0 开始过时 | file5 = open("jollyfile.txt ")print("软空间集?"文件 5 .软空间) |
实用文件存取
文件需要在 Python 中保持打开状态才能被操作;设置为关闭的文件无法写入或检查其属性。参见清单 6-1 中关于文件访问以及如何用 Python 读取文件属性的演示。
# Import a special module, time, for the sleep() method
import time
file1 = open("apress.txt", "wb") # Create/open file
print("File-name: ", file1.name) # Start reading file attributes
print("Opening mode: ", file1.mode)
print("Is file closed? ", file1.closed)
time.sleep(1) # Sleep/delay for one second, for the suspense
file1.close() # Close file
print("Now we closed the file..");
time.sleep(1.5) # Sleep/delay for 1.5 seconds
print("File is closed now? ", file1.closed)
Listing 6-1A listing in Python demonstrating some basic file operations
Python 中的目录操作
目录或文件夹是任何操作系统的文件管理系统的重要组成部分;Python 也提供了处理它们的方法。接下来让我们看看一些目录操作(参见清单 6-2 )。
import os # import the os module for directory operations
print("Current directory:", os.getcwd()) # Print current directory using getcwd()
print("List of files:", os.listdir()) # List files in directory using listdir()
os.chdir('C:\\') # Change to the (most common) Windows root-directory
print("New directory location:", os.getcwd()) # Print current directory again
print("Let's make a new directory and call it JollyDirectory")
os.mkdir('JollyDirectory') # Make a new directory using mkdir()
print("List of files in JollyDirectory:", os.listdir('JollyDirectory'))
Listing 6-2A listing in Python demonstrating directory operations
至于重命名目录,我们会使用 os.rename("Somedirectory "," Newname") 。一旦一个目录不再需要,并且首先是空的文件,只需要*OS . rmdir(" some directory ")*就可以删除它。
文件名模式匹配
我们有办法在 Python 中找到匹配特定命名模式的文件。从清单 6-3 中可以明显看出,这些实现起来相当简单。
import os
import fnmatch
# Display all files in Windows root with .txt/.rtf extension
for filename in os.listdir('C:\\'):
if filename.endswith('.txt') or filename.endswith('.rtf'):
print(filename)
# Display all files with .txt extension starting with 'a'
for filename in os.listdir('C:\\'):
if fnmatch.fnmatch(filename, 'a*.txt'):
print(filename)
Listing 6-3A listing in Python demonstrating file name pattern matching
在清单 6-3 中,我们引入了两个方便的函数来定位具有特定命名约定的文件,即 endswith() 和 fnmatch() 。后者提供了所谓的基于通配符的 Python 项目搜索(例如,文件)。txt* 还是*????name.txt* )。
用 Glob 搜索文件?
术语 globbing 指的是在特定目录(或者 Python 的当前工作目录,如果没有指定的话)中执行高度精确的文件搜索。术语 glob ,是 global 的缩写,起源于基于 Unix 的操作系统世界。在清单 6-4 中,您将看到这种方法得到了很好的应用。
import glob
# Using * pattern
print("\nGlobbing with wildcard pattern * (*.py)")
for name in glob.glob("*.py"):
print(name)
# Using ? pattern
print("\nGlobbing with wildcard ? and * (??????.*)")
for name in glob.glob("??????.*"):
print(name)
# Using [0-9] pattern
print("\nGlobbing with wildcard range [0-9]")
for name in glob.glob("*[0-9].*"):
print(name)
# Using [b-x] pattern
print("\nGlobbing with wildcard range [b-x]")
for name in glob.glob("*[b-x].*"):
print(name)
Listing 6-4A listing in Python demonstrating file searches by globbing
Python 中的日期
您可能还记得上一章中用 Java 显示时间和日历数据的强大工具。Python 在计时方面也提供了同样多的功能。这些功能驻留在 datetime 模块中(参见清单 6-5 )。
import datetime
from datetime import timedelta
time1 = datetime.datetime.now() # Create a datetime-object for right now
print("The time is:", time1) # Display current time unformatted
# Display formatted day and month (%A for day of the week, %B for month)
print("In other words it's a", time1.strftime("%A in %B"))
# Reduce time1.year-variable by ten
print("Ten years ago it was", time1.year-10)
# Use timedelta to move thirty days into the future
futuredate = datetime.timedelta(days=30)
futuredate += time1 # Add the current date to futuredate
print("Thirty days into the future it'll be", futuredate.strftime("%B"))
Listing 6-5A listing demonstrating some time and calendar functions in Python
虽然清单 6-5 相当简单,但我们应该更好地看看 Python 中的一些格式标记,以满足所有与日期相关的需求(见表 6-3 )。
表 6-3
Python 中一些常见的日期格式标记
| %A | 一周中的一整天(例如,星期一) | %B | 月份的全名(例如,三月) | | %a | 一周中较短的一天(例如,星期一) | %b | 月份的简称(例如,Mar) | | %Z | 时区(例如,UTC) | %H | 24 小时制(例如,18) | | %p | 上午/下午 | %I | 小时,12 小时制 |正则表达式的威严
一个正则表达式,通常简称为 RegEx ,是一个字符序列,构成了字符串的搜索模式。导入一个简单的叫做 re 的代码模块允许我们在 Python 中执行正则表达式工作。您可以使用正则表达式来查找具有特定搜索模式的文件,以及在众多类型的文本文件中查找特定的术语。参见清单 6-6 首次演示它们是如何工作的。
import re
text1 = "Apress is the best publisher"
regex1 = re.findall("es", text1) # Create a RegEx-object with search-pattern
print("Looking for all instances of 'es'")
print("We found", len(regex1), "matches in", text1)
Listing 6-6A simple example of using regular expressions in Python
在清单 6-6 中,我们调用 findall 方法在字符串变量 text1 中寻找“es”的实例。我们还使用 Python 的 len()方法对存储在 list regex1 中的实例进行计数。现在,是时候看看 RegEx 魔术的另一个演示了(见清单 6-7 )。
正则表达式实际上可以追溯到 1951 年,当时是由美国数学家斯蒂芬·科尔·克莱尼(1909–1994)提出的。如今,正则表达式是许多流行的编程语言中的主要部分,包括 Perl、C# 和 Java。后两种语言中的正则表达式实现将在本书的后面部分探讨。
import re
# Summon search-method from the regex module, re
match1 = re.search('Apress', 'Apress is the best')
if match1: # This is shorthand for "if match1 == True:"
print(match1) # Display object contents if match found
happytext = "My name is Jimmy" # Create a string variable
match2 = re.search('Jimmy', happytext)
if match2:
print(match2)
# Use fullmatch-method from re on string "happytext"
match3 = re.fullmatch('Jimmy', happytext)
if match3:
print("Match found for 'Jimmy'!") # This message will not display
else:
print("No Match for 'Jimmy'")
match3 = re.fullmatch('My name is Jimmy', happytext)
if match3:
print(match3)
# Use match-method
match4 = re.match('the', 'Apress is the best')
if match4:
print(match4) # This message will not display
# match() only looks for patterns from the beginning of a string
else:
print("No Match for 'the'")
match5 = re.match('Apress', 'Apress is the best')
if match5:
print(match5) # This message will display
Listing 6-7A listing in Python demonstrating search( ) and fullmatch( ) RegEx methods in Python
在清单 6-7 中,我们使用了 search()、match()和 fullmatch()。你可能会问它们之间有什么区别。search 方法遍历给定模式的整个字符串,而 fullmatch 仅在字符串完全反映模式时返回 true。match 方法只查找字符串开头的模式。
元字符
正则表达式最好与元字符一起使用。这些基本上是更高级的字符串相关搜索的构建块。参见表 6-4 中一些重要元字符的概要,以及清单 6-8 中它们用法的一点演示。
表 6-4
一些重要的 Python 元字符
| \w | 任何消息。通常指字母数字 | \s | 空白 | | \W | 任何非单词 | \S | 非空白 | | \d | 任何数字 | 。 | 任何单个字符 | | \D | 任何非数字 | * | 零个或多个字符 |import re
match1 = re.search('.....', 'Hello there!')
if match1: # This is shorthand for "if match1 == True:"
print(match1) # Displays "Hello"
match2 = re.search('\d..', 'ABC123')
if match2:
print(match2) # Displays "123"
match3 = re.search('\D*', 'My name is Reginald123456.')
if match3:
print(match3) # Displays "My name is Reginald"
match4 = re.search('y *\w*', 'Hello. How are you?')
if match4:
print(match4) # Displays "you"
match5 = re.search('\S+', 'Hello. Whats up?')
if match5:
print(match5) # Displays "Hello."
Listing 6-8A listing in Python demonstrating the use of metacharacters in regular expressions
在清单 6-8 中,对于 match4 ,我们使用元字符 \w ,它指的是寻找任何完整单词的匹配。如果没有这个字符,我们会看到输出“y”而不是“you”
让我们学习更多关于 Python 中元字符的知识。参见表 6-5 中的八个更重要的正则表达式标记,以及清单 6-9 中的第二个演示。
表 6-5
一些更重要的 Python 元字符
| \. | 文字点(例如,句号字符) | $ | 匹配行尾 | | ? | 零或一个字符 | { n } | 出现 n 次 | | + | 一个或多个字符 | [a-z] | 字符集 | | ^ | 匹配行首 | [0-9] | 数字字符集 |import re
string1 = 'Beezow Doo-doo Zopittybop-bop-bop'
patterns = [r'Do*', # D and zero or more o's (*)
r'Be+', # B and one or more e's (+)
r'Do?', # D and zero or one o's (?)
r'it{2}', # i and two t's
r'[BDZ]\w*', # Look for full words starting with B, D, or Z
r'^Be\w*', # Look for a full word starting with "Be"
r'...$' # Look for the three last digits in the string
]
def discover_patterns(patterns, string1): # Create our method
for pattern in patterns:
newpattern = re.compile(pattern) # Summon compile()
print('Looking for {} in'.format(pattern), string1)
print(re.findall(newpattern, string1)) # Summon findall()
discover_patterns(patterns, string1) # Execute our method
Listing 6-9Another listing in Python demonstrating the use of metacharacters in regular expressions
清单 6-9 中的列表结构模式包含七个搜索,而字符串 1 存储我们的源材料。这两个数据结构将被输入到我们接下来创建的方法 discover_patterns 中。
在我们的这个新方法中,我们使用了 Python 的两个 RegEx 函数: compile( ) 和 findall( ) 。对于前者,我们将 RegEx 模式转换成模式对象,然后用于模式匹配。这种方法在重复使用搜索模式的情况下最为有效,比如数据库访问。
Findall 用于发现字符串中搜索模式的所有匹配项。清单中的 r' 让 Python 知道一个字符串被认为是“原始字符串”,这意味着其中的反斜杠将在没有特殊函数的情况下被解释。例如, \n 不会表示原始字符串中的换行符。
正则表达式带来更多欢乐
接下来让我们探索 RegEx 的更多高级特性。清单 6-10 展示了两个新方法的使用: group( ) 和 sub( ) (为了方便起见,以粗体显示)。此外,我们将和我们的老朋友在 RegEx 中使用一项新技术 search()。
import re
string1 = "Today's dessert: banana"
# Summon search() with four options for a match
choice1 = re.search(r"dessert.*(noni-fruit|banana|cake|toilet-paper)", string1)
if choice1:
print("You'll be having this for", choice1.group(), "!")
string2 = "Have a great day"
string2 = re.sub('great', 'wonderful', string2)
print(string2) # Outputs: Have a wonderful day
string3 = 'what is going on?'
# Replace all letters between a and h with a capital X
string3 = re.sub('([a-h])s*', 'X', string3)
print(string3) # Outputs: wXXt is XoinX on?
Listing 6-10A listing in Python demonstrating the sub( ) method
清单 6-10 中的搜索方法用于比较和定位现在作为方法参数列出的字符串。换句话说,在字符串 1 中一共搜索了四种水果。它们由逻辑 or 运算符分隔,用竖线字符表示(即|)。这个方法是用来寻找这些字符串/水果的,但是它们只应该出现在字符串“dessert”的旁边。如果 string1 和 choice1 中的一个条目匹配,程序就会显示出来。
Python 中的并发和并行
并行处理是指同时执行一个以上的计算或指令。你可能还记得上一章中多线程的概念。像 Java 和 C# 一样,Python 能够处理多个执行线程。然而,还是有一些主要的区别。Python 的多线程实际上并不是以并行的方式运行线程。相反,它伪并发地执行这些线程。这源于 Python 实现了一个全局解释器锁(GIL) 。该机制用于同步线程,并确保整个 Python 项目仅在单个 CPU 上执行;它只是没有利用多核处理器的全部魅力。
虽然并发和并行是相关的术语,但它们不是同一个术语。前者指的是同时运行独立任务的方法,而后者是将一个任务分成子任务,然后同时执行。
多重处理与多线程
Python 中线程的实际并行处理是通过使用多个进程实现的,所有进程都有自己的解释器和 GIL。这被称为多重处理。
Python 对并发性的理解可能有点复杂(比如说,与 Java 相比)。现在,Python 中的进程不同于线程。尽管两者都是独立的代码执行序列,但还是有一些不同之处。一个进程往往比一个线程使用更多的系统内存。流程的生命周期通常也更难管理;它的创建和删除需要更多的资源。螺纹和工艺对比见表 6-6 。
表 6-6
Python 中线程和进程的主要区别(例如,多重处理和多线程)
| |过程
|
线
| | --- | --- | --- | | 使用单个全局解释器锁(GIL) | 不 | 是 | | 多个 CPU 内核和/或 CPU | 支持 | 不支持 | | 代码复杂性 | 不太复杂 | 更复杂 | | RAM 占用空间 | 巨大的 | 驳船 | | 可以被打断/杀死 | 是 | 不 | | 最适合 | CPU 密集型应用、3D 渲染、科学建模、数据挖掘、加密货币 | 用户界面、网络应用 |
Python 有三个用于同时处理的代码模块:多处理、异步和线程。对于 CPU 密集型任务,多处理模块工作得最好。
用 Python 实现多线程
正如本章前面所讨论的,Python 中的多线程与全局解释器锁(GIL)机制密切相关。这种方法利用了互斥的原理(见图 6-1 )。由于 Python 多线程中的调度是由操作系统完成的,所以一些开销(即延迟)是不可避免的;这是多重处理通常不会遇到的问题。
图 6-1
使用三个线程的 Python 多线程的部分可视化
是时候实际一点了。让我们看看多线程是如何在 Python 中实现的(参见清单 6-11 )。
import threading
def happy_multiply(num, num2):
print("Multiply", num, "with", num2, "=", (num * num2))
def happy_divide(num, num2):
print("Divide", num, "with", num2, "=", (num / num2))
if __name__ == "__main__":
# Create two threads
thread1 = threading.Thread(target=happy_multiply, args=(10,2))
thread2 = threading.Thread(target=happy_divide, args=(10,2))
# Start threads..
thread1.start()
thread1.join() # ..and make sure thread 1 is fully executed
thread2.start() # before we start thread 2
thread2.join()
print("All done!")
Listing 6-11A listing in Python demonstrating elementary use of the threading module
我们在清单 6-11 中创建了两个函数,分别是 happy_multiply 和 happy_divide 。每个都有两个参数。对于前者,我们用 num 乘以 num2 ,而后者用 num 除以 num2 。然后结果被简单地打印在屏幕上。
现在,清单 6-11 中有一个函数您应该密切关注;Python 的 join( ) 方法确保线程在继续处理清单之前已经完全完成了处理。如果您删除了行 thread1.join( ) ,清单 6-4 的输出将会是一片混乱。
并发处理环境中的竞争条件是指两个或多个进程同时修改资源(如文件)的场景。最终结果取决于哪个进程先到达那里。这被认为是不理想的情况。
在 Python 中实现多重处理
图 6-2
使用三个进程的 Python 多重处理的部分可视化
尽管多处理方法通常会产生 CPU 效率高的代码,但仍然会出现一些小的开销问题。如果 Python 中的多处理项目有这些问题,它们通常发生在进程的初始化或终止期间(见图 6-2 )。
现在,为了简单演示 Python 的多处理模块,请参见清单 6-12 。
import time
import multiprocessing
def counter(): # Define a function, counter()
name = multiprocessing.current_process().name
print (name, "appears!")
for i in range(3):
time.sleep(1) # Delay for one second for dramatic effect
print (name, i+1,"/ 3")
if __name__ == '__main__': # Define this listing as "main", the one to execute
counter1 = multiprocessing.Process(name='Counter A', target=counter)
counter2 = multiprocessing.Process(name='Counter B', target=counter)
counter3 = multiprocessing.Process(target=counter) # No name given..
counter1.start()
counter2.start()
counter3.start() # This nameless counter simply outputs "Process-3"
Listing 6-12A listing in Python demonstrating elementary use of the multiprocessing module
Python 中的每个进程都有一个名称变量,如清单 6-12 所示。如果没有定义,将自动分配一个通用标签进程-[进程号] 。
迭代器、生成器和协同程序
Python 中有三种重要的数据处理机制,它们有时会混淆: iterables、生成器和协程。列表(例如 happyList[0,1,2] )是简单的可迭代列表。它们可以根据需要随时阅读,不会出现任何问题。列表中的所有值都会保留,直到明确标记为删除。生成器是迭代器,只能被访问一次,因为它们不在内存中存储内容。生成器的创建类似于 Python 中的常用函数,只是它们用关键字 yield 代替了 return。
当读取大文件的属性时,比如它们的行数,生成器是很有用的。用于这种用途的生成器将产生不是最新的数据,因此不需要,从而避免内存错误并使 Python 程序更有效地运行。
协程是一种独特的函数类型,它可以将控制权让给调用函数,而无需结束它们在进程中的上下文;协程在后台平稳地维护它们的空闲状态。换句话说,它们被用于合作的多任务处理。
阿辛西奥:出类拔萃的那个?
Asyncio 是异步输入/输出的缩写,是 Python 中为编写并发运行代码而设计的模块。尽管与线程和多处理有相似之处,但它实际上代表了一种不同的方法,称为协同多任务处理。asyncio 模块使用在单线程中运行的单个进程提供了一种伪并发性。
在 Python 中使用异步代码意味着什么?用这种方法编写的代码可以安全地暂停,以便项目中的其他代码片段执行它们的任务。异步进程为其他进程提供停机时间来运行它们的进程;这就是如何使用 asyncio 模块实现一种类型的并发性。
异步事件循环
异步 Python 项目的主要组件是*事件循环;*我们从这个结构中运行我们的子流程。任务被安排在一个事件循环中,由一个线程管理。基本上,事件循环是为了协调任务,确保我们在使用 asyncio 模块时一切顺利。让我们用清单 6-13 来看看如何实际实现 asyncio 模块。
import asyncio
async def prime_number_checker(x): # Define a function which takes x as input
# Go to sleep for one second, potentially letting other functions
# do their thing while we're asleep
await asyncio.sleep(1.0)
message1 = "%d isn't a prime number.." % x # Set default message
if x > 1:
for i in range(2, x):
# Apply the modulo-operator (%) on variable x.
# If the ensuing remainder does not equal zero, update "message1"
if (x % i) != 0:
message1 = "%d is a prime number!" % x
break # Break out of the loop
return message1
async def print_result(y): # Define a function which takes y as input
result = await prime_number_checker(y) # Await for other function to finish
print(result) # Print results from function prime_number_checker()
happyloop = asyncio.get_event_loop()
# See if 2, 11, and 15 are prime numbers
happyloop.run_until_complete(print_result(2))
happyloop.run_until_complete(print_result(11))
happyloop.run_until_complete(print_result(15))
happyloop.close()
Listing 6-13A listing in Python demonstrating asynchronous chained coroutines and the use of an event loop
在清单 6-13 中,我们寻找质数(即只有两个因子的数:它们本身和 1)。我们定义了两个异步函数/协程, prime_number_checker(x) 和 print_result(y) ,它们都带有关键字 async def ,因为这就是 asyncio 模块的工作方式。
现在,我们定义的第一个函数从行开始,等待 asyncio.sleep(1.0) 。与 Python 中常规的 sleep 方法相比,这种异步方法不会冻结程序;相反,它为其他任务在后台完成提供了一个指定的时间段。
第二个协程中的行*result = await prime _ number _ checker(y)*用于确保第一个函数已完成执行。在异步方法中, Await 确实是一个不言自明的关键关键词。
为了让我们的异步清单完全运行,我们需要使用事件循环。为此,我们创建了一个新的循环对象, *happyloop,*并调用了一个名为 asyncio.get_event_loop() 的函数。尽管事件循环管理有多种多样的函数,但为了清楚起见,我们在这个例子中只讨论前面提到的方法。
接下来,在清单 6-13 中,函数 run_until_complete 实际上执行我们的 print_result(y) 函数,直到其各自的任务全部完成。最后,我们通过调用 close 方法来结束我们的事件循环。
Python 中的并发性:概述
Python 提供的所有处理方法可能会让你感到困惑。因此,让我们回顾一下所涉及的一些核心概念来结束本章(见表 6-7 )。
表 6-7
Python 中并发编程的主要方法
| **多重处理** | 同时运行多个进程;为每个进程创建一个单独的带全局解释器锁(GIL)的 Python 解释器 | 可以在一个系统中使用多个 CPU 和/或 CPU 内核 | | **多线程** | 由操作系统确定优先级的计划任务执行。受限于单一的 GIL | 也叫*抢占式多任务处理。*在单个 CPU/内核上运行 | | **异步处理** | 由任务决定的日程安排受限于单一的 GIL | 又称*协同多任务处理。*运行在单个 CPU/内核上。在 Python 版中引入 |Python Lambda 函数
把一个 lambda 函数想象成一个一次性函数。Lambda 函数是无名的,而且通常规模很小。它们的语法简单如下:
- λ(自变量):(表达式)
下面是一个简单的 lambda 函数: sum1 = lambda x,y : x + y 。对于稍微复杂一点的例子,参见清单 6-14 。
def happymultiply(b):
return lambda a : a * b
result1 = happymultiply(5)
result2 = happymultiply(10)
result3 = happymultiply(50)
print("Result 1:", result1(6))
print("Result 2:", result2(6))
print("Result 3:", result3(6))
Listing 6-14A listing demonstrating lambda functions in Python
现在,Python 中有三种方法可以很好地处理 lambda 函数: map( ) 、 filter( ) 和 reduce( ) 。Filter()获取一个列表,并创建一个包含所有返回 true 的元素的新列表。Map()还从它处理的 iterables 中创建新的列表;可以把它想象成一个无循环的迭代器函数。最后,我们有 reduce(),它将一个函数应用于一个列表(或其他可迭代的对象)并返回一个值,这有时也将我们从循环中解放出来(参见清单 6-15 )。
from functools import reduce # Import code module needed for reduce()
# Create a list containing seven values
middle_aged = [6, 120, 65, 40, 55, 57, 45]
# Display list before manipulations
print("Ages of all people in the study:", middle_aged)
# Filter list for ages between 40 and 59
middle_aged = list(filter(lambda age: age>=40 and age<60, middle_aged))
print("Middle aged people:", middle_aged)
# Summon map() and double the values in the list "middle_aged"
elderly = list(map(lambda x: x * 2, middle_aged))
print("They will live to:", elderly)
# Summon reduce() to add all elements in "middle_aged" together
total_years = reduce(lambda x, y: x + y, middle_aged)
print("Their combined time spent alive so far:", total_years, "years")
Listing 6-15A listing with a lambda function applied on a filtered list in Python
在 Python 中压缩
以著名的服装闭合机制拉链命名,Python 中的 zip 指的是一种将两个或多个可重复元素结合起来的方法。例如,在用 Python 创建字典数据结构时,压缩是一个很好的工具。不,这种 zip 与流行的归档文件格式 ZIP 没有任何关系,我们将在本章的后面回顾这种格式。
现在,Python 中的 zip 函数接受两个可迭代对象(例如列表)并返回一组元组(参见清单 6-16 )。
# Define two lists
letters1 = ['z', 'a', 'r', 'd', 'o', 'z']
numbers1 = [1, 2, 3, 4, 5, 6]
# Create zip object, zip1, using "letters1" and "numbers1"
zip1 = zip(letters1, numbers1)
for i in zip1: # Display zip contents
print(i)
# Define new list, "letters2"
letters2 = ['Z', 'A', 'R', 'D', 'O', 'Z']
# Create second zip object, zip2, using "letters1", "numbers1", and "letters2"
zip2 = zip(letters2, numbers1, letters1)
for i in zip2: # Display second zip contents
print(i)
Listing 6-16A listing in Python demonstrating the zip function
关于拉链的更多信息
在很多情况下,基本的拉链都不够用。其中之一涉及具有不完整元组的拉链。幸运的是,我们有一个叫做 zip_longest 的方法。这个方法在代码模块 itertools 中找到,用您选择的占位符数据填充任何缺失的元素。参见清单 6-17 进行演示。
from itertools import zip_longest
letterlist = ['a', 'b', 'c']
numberlist = [1, 2, 3, 4]
maximum = range(5) # Define zip length and store it into "maximum"
# Summon zip_longest()
zipped1 = zip_longest(numberlist, letterlist, maximum, fillvalue='blank')
# Display zip contents
for i in zipped1:
print(i)
Listing 6-17A listing in Python demonstrating the zip_longest method
你可能想知道 Python 里的一个拉链能不能拉开。答案是响亮的“是”(见清单 6-18 )。
letters = ['A', 'B', 'C', 'D']
numbers = [1, 2, 3, 4]
zipped1 = zip(letters, numbers) # Zip the data
zipped_data = list(zipped1)
print("Here's the zip:", zipped_data)
a, b = zip(*zipped_data) # Unzip the data using the asterisk-operator
print("Next comes the data, unzipped!")
print('Letters:', a)
print('Numbers:', b)
Listing 6-18A listing in Python demonstrating unzipping.
使用另一个拉链
正如本章前面提到的, zip 是一个在计算环境中至少有两种流行含义的词。我们探讨了 Python 中的压缩(和解压缩),涵盖了第一个含义。现在是时候在任何 Python IDE 中使用另一种 ZIP 文件了,这是一种流行的归档文件格式。
现在,用 ZIP 软件压缩的目录(及其子目录)最终成为一个文件,其大小也往往比未压缩的内容小得多。对于基于网络的文件传输,这显然是一个很好的解决方案。自然,有几种文件压缩解决方案可用,但截至 2021 年,ZIP 仍然是一种无处不在的文件存档格式,可用于所有流行的操作系统。
ZIP 是由菲利普·卡兹和 ?? 的加里·康威在 1989 年创建的。ZIP 压缩技术最近被用作 Java Archive (JAR) 文件格式 — 和许多其他格式的基础。
Python 也喜欢它的 ZIP 文件。我们实际上可以在 Python IDE 中操作它们。有一个代码模块被恰当地称为 zipfile 。参见清单 6-19 中关于如何创建 ZIP 文件以及如何在 Python 中显示其内容的演示。
import os
from os.path import basename
from zipfile import ZipFile # Include ZipFile module
lookfor = "thread" # Create a variable for the string we plan to look for
# Define a new function, make_a_zip
def make_a_zip(dirName, zipFileName, filter):
# Create a new ZipFile-object with attribute w for (over)writing
with ZipFile(zipFileName, 'w') as zip_object1:
# Start iteration of files in the directory specified
for folderName, subfolders, filenames in os.walk(dirName):
for file in filenames:
if filter(file):
# Create full file-path
filePath = os.path.join(folderName, file)
# Add file to zip using write() method
zip_object1.write(filePath, basename(filePath))
# Summon make_a_zip() and give it the current directory to work with
# Also only look for filenames with the string "thread" in them
make_a_zip('..', 'happy.zip', lambda name: lookfor in name)
print("All files containing <<", lookfor, ">> in their name are now inside happy.zip!")
# Create a ZipFile Object and load happy.zip into it (using attribute r for reading)
with ZipFile('happy.zip', 'r') as zip_object2:
# Summon namelist() and store contents in "file_in_zip" variable
files_in_zip = zip_object2.namelist()
# Iterate through files_in_zip and print each element
print("\nThese files were stored in happy.zip:")
for i in files_in_zip:
print(i)
Listing 6-19A listing demonstrating the use of ZIP file archives from inside Python
在清单 6-19 中,您将看到 Python 的 ZIP 支持的两个主要机制在起作用:创建一个新的文件归档(即 happy.zip )和检索存储在其中的文件名。该示例还向您展示了在将文件收集到归档文件时如何使用基于通配符的功能。在这种情况下,我们将只查找文件名中带有字符串“thread”的文件。
与 ZipFile 一起使用的 with 语句基本上是为了让我们的清单更加清晰。首先,它们让我们不用使用关闭方法(例如, happyfile.close( ) ),这些方法通常在文件操作完成后才需要。
清单 6-19 将在您当前的 Python 目录中执行,因此您的结果可能会有所不同。
我们将在本书的后面回到 Python 语言的复杂性。
最后
读完这一章,你将有望学到以下内容:
-
如何使用 Python 打开、创建和删除文件
-
Python 中的基本目录管理
-
正则表达式(RegEx)指什么
-
如何以及何时为正则表达式调用 match()、search()和 fullmatch()
-
如何在 Python 中实现多线程和多处理,它们的主要区别是什么
-
全局解释器锁(GIL)指的是什么
-
使用 asyncio 的 Python 异步编程基础
-
lambda 函数有什么用
-
Python 中压缩和使用 ZIP 文件归档的基础知识
第七章将会看到我们探索 C# 语言更高级的一面。
七、C# 中的日历、区域性和多线程
这一章是献给最通用的语言之一:强大的 C#。到目前为止,您应该已经熟悉了这种语言的基本语法和集成开发环境的设置。我们将在此继续探索与这种健壮语言相关的一些更高级的主题,包括日历工作和多线程的基础知识。
C# 中的日期
与 Java 和 Python 一样,C# 提供了处理日期和日历所需的一切。让我们来看一个访问日期和日历的程序,好吗?(参见清单 7-1 。)
using System;
public class Example
{
public static void Main()
{ // Create a new DateTime-object, "happydate"
DateTime happydate = new DateTime(1966, 2, 6, 5, 20, 0);
// Display object data
Console.WriteLine("Our special date is {0}", happydate.ToString());
// Fetch and display year and day of the week for our date-object
Console.WriteLine("Back in {0} it was {1}!", happydate.Year, happydate.DayOfWeek);
// Create a second DateTime-object
DateTime happydate2 = DateTime.Now;
Console.WriteLine("As for right now, it's a {1} in {0}!", happydate2.Year, happydate2.DayOfWeek);
}
}
Listing 7-1A listing in C# demonstrating the use of DateTime objects (the date in question is recording artist Rick Astley's date of birth)
我们通过创建一个我们选择称为 happydate 的对象来开始列出 7-1 。它应该是 C# DateTime 结构的一个实例,并使用特定的构造函数来设置年、月和时间。然后我们使用控制台显示这个日期。WriteLine 内部带有一个 {0} 格式标记,用于包含第一个参数,并且仅在这种情况下包含第一个参数。happydate 对象也使用 ToString 方法转换为当前活动的特定于文化的格式约定。
在 C# 中,DateTime 结构是系统名称空间的一部分,因此不需要以任何其他方式将它们包含在我们的项目中。
C# 中的 DateTime 结构包括大量的属性,用于访问从年到毫秒的日历相关数据。纲要见表 7-1 。
表 7-1
C# 中 DateTime 结构包含的一些核心属性
| 现在 | 一天 | 毫秒 | | 日期 | 小时 | 时间日 | | 年 | 分钟 | 星期几 | | 月 | 第二 | 年复一天 |系统。全球化名称空间
C# 非常重视全球文化这一概念。全球化是指为全球任何地方的用户设计应用程序。本地化指的是定制应用程序以符合特定文化需求的过程。在实践中,这些概念处理我们星球提供的不同日历、货币、语言和位置。在 C# 中,我们有大量的方法和属性用于显示本地化信息。他们中的许多人来自系统。全球化名称空间。
使用世界日历
C# 中的 Calendar 类包含总共 14 种不同的日历,以满足您的本地化需求。见表 7-2 浏览其中的八个。
表 7-2
C# 中包含的一些日历
| *公历日历* | 世界上使用最广泛的日历 | *JulianCalendar* | 俄罗斯东正教使用的 | | 希伯来语日历 | 以色列官方日历。也被称为犹太历 | 波斯历 | 伊朗和阿富汗官方日历 | | *女儿传说* | 也被称为伊斯兰日历 | *泰国历* | 在泰国使用。比公历早了 543 年 | | 日本历 | 类似于公历,根据日本现任天皇在位的年份添加年份指定 | *一种烷基化* | 在沙特阿拉伯使用,类似于回历 |在清单 7-2 中,您会发现一个程序,它采用公历中设定的日期(即 2021 年 12 月 31 日)并将其转换成其他四种日历。我们将使用 GetYear()和 GetMonth()等方法来访问与日期相关的数据。我们还使用 ToString 方法为 Gregorian DateTime 对象指定了一个自定义格式模式。
using System;
using System.Globalization;
public class CalendarsExample
{
public static void Main()
{
// Create a new Gregorian DateTime-object, date1
DateTime date1 = new DateTime(2021, 12, 31, new GregorianCalendar());
// Create four more objects for the four other types of calendars
JapaneseCalendar japanese = new JapaneseCalendar();
PersianCalendar persian = new PersianCalendar();
HijriCalendar hijri = new HijriCalendar();
ThaiBuddhistCalendar thai = new ThaiBuddhistCalendar();
Console.WriteLine("When the Gregorian Calendar says it's {0}..", date1.ToString("dd.MM.yyyy"));
Console.WriteLine("> The Japanese Calendar says it's year {0}.", japanese.GetYear(date1));
Console.WriteLine("> The Persian Calendar says it's the year {0} and the {1}th month of the year.", persian.GetYear(date1), persian.GetMonth(date1));
Console.WriteLine("> The Hijri Calendar says it's the year {0} and it's {1}.", hijri.GetYear(date1), hijri.GetDayOfWeek(date1));
Console.WriteLine("> The Thai Buddhist Calendar says it's the year {0} and the {1}th month of the year.", thai.GetYear(date1), thai.GetMonth(date1));
}
}
Listing 7-2A listing demonstrating displaying information using five different calendars in C#
CultureInfo 类
CultureInfo 类为我们提供了在 C# 中本地化程序的方法。这包括显示适合特定地区的时间、日历和货币信息。要授予我们访问 CultureInfo 类的权限,我们需要让我们的程序再次使用名称空间系统。全球化。
C# 使用语言文化代码进行本地化。在下面的清单中,我们使用了四个,即 en-US 、 fi-FI 、 se-SE 和 es-ES 。此代码中的前两个字母代表一种语言,而第二个大写的字母对代表一个特定的地区。例如,德语( de) 可以用附加的文化代码表示,例如, de-AT ( 奥地利)、 de-CH (瑞士)和 de-LI (列支敦士登)。现在,请看清单 7-3 的演示。
C# 中的语言文化代码基于 ISO 3166-1 国家列表。截至 2021 年,总共支持 249 种语言文化代码。
using System;
using System.Globalization;
public class CultureInfoExample
{
public static void Main()
{
// Create an array of four CultureInfo-objects called "jollycultures"
CultureInfo[] jollycultures = new CultureInfo[] {
new CultureInfo("en-US"), // US
new CultureInfo("fi-FI"), // Finnish
new CultureInfo("se-SE"), // Sami in Northern Sweden
new CultureInfo("es-ES")}; // Spanish
// Create a new DateTime-object, "date1", assigning calendar-information into it
DateTime date1 = new DateTime(1952, 12, 1, 15, 30, 00);
// Display unformatted date
Console.WriteLine("The special date is " + date1 + " in your current locale.\n");
// Use the foreach-keyword to loop through all of the jollycultures-objects
// and display their contents, using "i" as a temporary iterator-variable
foreach (CultureInfo i in jollycultures)
{
Console.WriteLine("This date in {0} is.. {1} ({2})", i.EnglishName, date1.ToString(i), i.Name);
}
}
}
Listing 7-3A listing demonstrating the use of localization in C# using the CultureInfo class
在清单 7-3 中,我们创建了 CultureInfo 类的四个实例来展示美国、芬兰、萨米(在瑞典北部)和西班牙的地区,因为它们与 C# 相关。
我们采用的解决方案是在显示 CultureInfo 对象时使用一个 foreach 元素。C# 中的 foreach 是 for 循环的替代方法,更适合在数组中迭代。例如,这个元素与 CultureInfo 的实例配合得非常好。
现在是时候看一个货币格式化的小例子了(参见清单 7-4 )。
using System.Globalization;
public class JollyCurrencies
{
public static void Main()
{
int cash = 10000;
// Display cash amount without currency-format
Console.WriteLine("No currency-format: " + cash.ToString());
// Set CurrentCulture to Finnish in Finland.
Thread.CurrentThread.CurrentCulture = new CultureInfo("fi-FI");
// Add the appropriate currency-format using "c"
Console.WriteLine("Finnish currency-format: " + cash.ToString("c"));
// Set CurrentCulture to Belarusian in Belarus and display string
Thread.CurrentThread.CurrentCulture = new CultureInfo("be-BY");
Console.WriteLine("Belarusian currency-format: " + cash.ToString("c"));
// Set CurrentCulture to Chinese in People's Republic of China
Thread.CurrentThread.CurrentCulture = new CultureInfo("zh-CN");
Console.WriteLine("Chinese currency-format: " + cash.ToString("c"));
}
}
Listing 7-4A listing demonstrating currency formats in the C# CultureInfo class
在清单 7-4 中,我们分别为芬兰语、白俄罗斯语和汉语创建并显示了三种不同的货币格式。方法 ToString("c") 用于指定一种货币。
C# 中的 File 类
C# 通过一个简单的叫做 File 的类为所有类型的文件操作提供了通用的工具。我们可以读取和显示文本文件,创建新文件,并检索多种类型的属性,如创建日期等等。
在清单 7-5 中,我们从 C# 中创建了一个新文件,我们称之为 apress.txt 。在里面,我们会写一条小信息(“Apress 是最好的出版商!”).不止于此,我们将继续读取并显示我们刚刚创建的文件的内容。
要使用 File 类,我们需要系统。我们程序中的 IO 命名空间。
using System;
using System.IO;
class HappyTextWriter
{
static void Main(string[] args)
{
using (TextWriter happywriter = File.CreateText("c:\\apress.txt"))
{
happywriter.WriteLine("Apress is the best publisher!");
}
Console.WriteLine("Textfile created in C:\n\nIt reads:");
// Open the file we just created and display its contents
using (TextReader happyreader1 = File.OpenText("c:\\apress.txt"))
{
Console.WriteLine(happyreader1.ReadToEnd());
}
}
}
Listing 7-5A listing demonstrating the TextWriter and TextReader classes in C#
除了文本文件,还有另一种文件格式可供我们使用。在清单 7-6 中,我们将使用另一个主要类型:二进制文件。这种格式非常通用,可以存储文本、数字以及我们可能用到的任何东西。在下一个清单中,我们将创建一个二进制文件,并在其中存储一个浮点数、一个文本字符串和一个布尔变量。同样,我们需要使用系统来利用文件类。IO 命名空间。
using System;
using System.IO;
class JollyBinaryTest {
static void Main(string[] args)
{
string filename1 = "c:\\binarystuff.dat"; // Define our path and filename
/* Create a new BinaryWriter-object (writer1) and save three lines/types of information into it */
using (BinaryWriter writer1 = new BinaryWriter(File.Open(filename1, FileMode.Create)))
{
writer1.Write(55.52F); // Write a floating point number
writer1.Write("Oranges are great"); // Write a string
writer1.Write(true); // Write a boolean (true/false) variable
}
Console.WriteLine("Data written into binary file " + filename1 + "!\n");
/* Create a new BinaryReader-object (reader1) and use it to decipher the binary data in our file using the appropriate methods, e.g. ReadSingle() for the floating point */
using (BinaryReader reader1 = new BinaryReader(File.Open(filename1, FileMode.Open)))
{
Console.WriteLine("First line: " + reader1.ReadSingle() ); // Read a floating point
Console.WriteLine("Second line: " + reader1.ReadString() ); // Read a string
Console.WriteLine("Third line: " + reader1.ReadBoolean() ); // Read a boolean variable
}
}
}
Listing 7-6A listing demonstrating the BinaryWriter and BinaryReader classes in C#
当处理非特定的二进制文件时,习惯上使用文件扩展名*。数据中的 dat* — 。
FileInfo 类
File 类在 C# 中有一个替代项;FileInfo 提供了更多的控制,在某些情况下更有用。让我们用清单 7-7 来看看如何用 FileInfo 访问文件属性。
using System;
using System.IO;
class FileInfoAttributeFun
{
public static void Main()
{
string fileName1 = @"C:\apress.txt"; // Set our target file
FileInfo ourfile = new FileInfo(fileName1);
string na = ourfile.FullName;
Console.WriteLine("Attributes for " + na + ":");
string ex = ourfile.Extension;
Console.WriteLine("File extension: " + ex);
bool ro = ourfile.IsReadOnly;
Console.WriteLine("Read-only file: " + ro);
long sz = ourfile.Length;
Console.WriteLine("Size: " + sz + " bytes");
DateTime ct = ourfile.CreationTime;
Console.WriteLine("Creation time: " + ct);
DateTime la = ourfile.LastAccessTime;
Console.WriteLine("Last access: " + la);
}
}
Listing 7-7A listing demonstrating file-attribute access using the C# FileInfo class
清单 7-7 希望您在 Windows 硬盘的根目录下有一个文本文件 apress.txt 。您可以修改文件名 1 指向不同的位置和/或不同的文本文件,让清单 7-7 发挥它的魔力。
现在是时候看看 FileInfo 可以访问的一些最常用的属性了。纲要见表 7-3 。
表 7-3
C# 中 FileInfo 类包含的一些属性。
| 名字 | 返回文件的名称 | 存在 | 用于确定特定文件是否存在 | | 工作表名称 | 返回文件的全名和目录路径(例如,C:\MyFolder\myfile.txt) | 长度 | 以字节为单位返回文件的大小 | | 创造时间 | 返回或更改文件的“出生日期” | 目录 | 返回父目录的实例 | | 最后访问时间 | 返回或更改文件或目录的上次访问时间 | 属性 | 返回或更改文件的属性 |接下来让我们来看一个更全面的 C# 文件操作的例子。在清单 7-8 中,我们更广泛地使用了 FileInfo 类。这包括使用 CopyTo 方法复制文件以及使用 Delete 方法删除文件。
using System;
using System.IO;
class FileInfoExample
{
public static void Main()
{
string filename1 = @"C:\wackyfile.txt"; // Path to main file
string file2 = @"c:\wacky_copy.txt"; // Path to copy of main file
FileInfo ourfile = new FileInfo(filename1); // Create new FileInfo-object, "ourfile"
FileInfo ourfile_copy = new FileInfo(file2); // Do the same for "ourfile_copy"
// Use Exists-property to determine if file has already been created
if(!ourfile.Exists) {
File.Create(filename1).Dispose(); // Create file and disable file-lock on "ourfile"
Console.WriteLine("File not found. Creating " + filename1);
} else Console.WriteLine("File found.");
// Display full name of our file
Console.WriteLine("Attempting to make a copy of " + ourfile.FullName);
// See if copy exists. If it doesn't, duplicate file
if(!ourfile_copy.Exists) { ourfile.CopyTo(file2);
Console.WriteLine("{0} has been copied as {1}", ourfile, file2);
} else Console.WriteLine("File not copied. Duplicate already found.");
// Prompt user for input
Console.WriteLine("Would you like to delete these files? (y/n)");
char input1 = (char)Console.Read(); // Assign a Console.Read() to character "input1"
if(input1=='y') { // If user inputs 'y' delete both files
Console.WriteLine("Deleting files..");
ourfile.Delete();
ourfile_copy.Delete();
} else Console.WriteLine("Files in C:\\ not deleted.");
}
}
Listing 7-8A listing in C# demonstrating some file operations found in the FileInfo class
让我们详细看一下清单 7-8 。首先,我们定义两个字符串:文件名 1 和文件 2 。这些字符串包含完整的文件路径,并指向 Windows 根目录 C:中的两个文件。我们也可以只保留路径,只保留文件名(即 wackyfile.txt 和 wacky_copy.txt )。
接下来,我们使用 FileInfo 类创建一个对象,将其命名为 ourfile 。然后为文件副本创建另一个对象 ourfile_copy 。我们需要实例化 FileInfo,以便稍后使用类方法。
字符串值前的 at 符号,即@,表示 C# 中的文字字符串。这意味着后面的字符串不会被解析为任何转义序列,比如"\\"中的反斜杠。比较下面几行(最上面一行是文字字符串):
string file1 = @"c:\user\xerxes\documents\text\nincompoop.txt";
string file1 = "c:\\user\\xerxes\\documents\\text\\nincompoop.txt";
这句台词如果(!ourfile 。 Exists) 是检查对象 ourfile 的不言自明的 Exists 属性。前面有一个感叹号,条件句是“如果我们的文件不存在。”继续前进,行*档。创建(文件名 1)。dispose();*包含两条指令。首先,Create 方法在您的存储系统上创建一个实际的文件。Dispose 方法主要释放文件的访问状态。如果没有它,我们可能会在程序后期操作这个文件时遇到问题。
现在,回到我们的条件句。它以关键字 else,结束,显示“如果我们的文件确实存在”,在这个场景中,它输出消息“找到文件”接下来,我们试图复制对象我们的文件。又到了一个条件从句的时候了。如果(!ourfile_copy 。 存在){ ourfile ***。****copy to(file 2);*是否再次检查文件的存在,这一次关注我们之前定义的另一个文件, ourfile_copy 。如果不存在,我们调用 CopyTo 方法将我们的文件(即 wackyfile.txt )复制到 file2(即 wacky_copy.txt )上。在条件子句的末尾,我们又有了一个可选的 else 关键字。这一次,它显示消息“找不到文件。已找到重复项。
我们现在转到一些用户交互。如果用户希望删除原始文件及其副本,系统会提示用户按“y”(并点击 return)。这是通过 Delete 方法实现的。如果用户输入任何其他字符而不是“y”,文件将保持不变,并显示一条消息“C:\中的文件未删除”而是显示。
File 与 FileInfo:有什么区别?
C# 的 File 和 FileInfo 类非常相似。然而,它们在语言中保持独立是有原因的。当需要对一个文件进行多个操作时,fileInfo 类是更好的选择,而 File 最适合单个操作。FileInfo 还通过字节大小的文件操作提供了更多的手动控制。
File 中的方法是静态的,而 FileInfo 中的方法是基于实例的。静态方法需要更多的参数,比如完整的目录路径。反过来,在特定的场景下,比如为基于网络的环境编码,稍微更费力的文件类实际上可能产生更有效的结果(见表 7-4 )。
表 7-4
C# 中 File 和 FileInfo 类的主要区别
|文件
|
文件关于
| | --- | --- | | 使用静态方法 | 使用基于实例的方法 | | 不需要实例化 | 需要实例化 | | 提供更多方法 | 提供较少的方法 | | 在某些情况下可以提供更快的性能,例如网络流量 | 对文件读写操作有更好的控制 |
要观察 FileInfo 提供的一些手动控制,请参见清单 7-9 。
using System;
using System.IO;
// Create a new FileInfo object, "fileobj1"
FileInfo fileobj1 = new FileInfo(@"c:\apress.txt"); // This text-file should contain something
// Open the file for reading: this line works with FileInfo only; not with File
FileStream filestream1 = fileobj1.Open(FileMode.Open, FileAccess.Read);
// Create a byte array sharing the length of our filestream
byte[] bytearray1 = new byte[filestream1.Length];
// Set variable for a byte-counter and feed the length of our file into it
int howmanybytes = (int)bytearray1.Length;
int bytesread = 0;
// Read our file, a byte at a time using a while-loop
while (howmanybytes > 0)
{
int i = filestream1.Read(bytearray1, bytesread, howmanybytes);
if (i == 0) break; // Once we have zero bytes left, break loop
howmanybytes-=i;
bytesread+=i;
}
// Convert bytes into string which uses UTF-8 character encoding
string string1 = Encoding.UTF8.GetString(bytearray1);
Console.WriteLine(string1); // Display string
Listing 7-9A listing demonstrating manual control for reading data using FileInfo and FileStream classes in C#
FileStream 类允许我们在逐字节的基础上读写文件。这是使用清单 7-9 中的 Open 方法完成的。这是一个多用途的方法,需要几个属性;为了以只读模式打开我们的文件,我们输入参数 FileMode。打开并文件访问。读入 Open 方法。接下来,我们使用关键字 byte[ ]创建一个字节数组。提醒一下,数组是值的集合;它们可能由数字、字符、字符串或字节组成。在 C# 中,数组是用方括号定义的(第二个提醒是,每个字节由 8 位组成,经常用来表示字母字符)。
接下来,使用行 int howmany bytes =(int)bytearray 1。长度;我们创建了一个整数,howmanybytes,在其中我们应用了一个长度方法,以便找出我们的文件的字节大小。在随后的 while 循环中,使用 read 方法读取字节。
更准确地说,这个 while 循环将在变量 howmanybytes 保持大于零时执行。我们之前定义的文件流 filestream1 应用了 Read 方法,将字节数组(bytearray1)、目前读取的字节数(bytes read)和要读取的字节总数(howmanybytes)作为该方法的参数。这个输出被输入到变量 I 中。当 I 达到零时,我们使用 break 关键字中断这个循环。
清单 7-9 的最后步骤包括首先将 bytearray1 转换成可读的 Unicode 字符,并将其存储到 string1 中,然后显示该字符串。
使用 UTF-8 字符编码,存储在字符串中的每个拉丁字母数字字符通常占用两个字节,即 16 位。
C# 中的垃圾收集
垃圾收集(GC) 的概念基本上是指自动化的内存管理。这种机制可以根据程序的需要分配和释放 RAM 支持垃圾收集的编程语言将程序员从这些任务中解放出来。包括 C# 在内的大多数现代语言都支持开箱即用的 g C。这个特性也可以以附加软件库的形式添加到没有本地支持的语言中。
随着一个编程项目的增长,它通常包括越来越多的变量和其他数据结构,除非得到适当的处理,否则它们会耗尽 RAM。对程序员来说,管理这些资源可能有点麻烦。此外,受损的内存分配会导致稳定性问题。
现在,C# 中的垃圾回收在以下三种情况下被调用:
-
您的计算机物理内存不足。系统的内存越大,这种情况发生的频率就越低。
-
分配的对象使用的内存超过了特定的阈值。该阈值由垃圾收集组件实时更新。
-
一个 GC。程序员调用了 Collect 方法。对于较小的项目,这很少是必要的。
本机堆与托管堆
本机堆是由操作系统管理的动态分配的内存。每当一个程序被执行时,本机堆内存被动态地分配和释放。被称为托管堆的内存区域是一个不同的实体。每个单独的进程都有自己的托管堆。一个进程中的所有线程也共享这个内存空间。
C# 中的多线程
如您所料,多线程在 C# 中确实得到了很好的支持。参见清单 7-10 进行演示。首先,我们定义一个自定义方法, *ChildThread(),*来产生新的线程。我们稍后将创建三个线程,它们将同时使用 Sleep 方法,简单地暂停一个线程的处理。在我们的线程中,这些方法被赋予一个 0 到 6000 毫秒之间的随机值,最大延迟为 6 秒,按照基于线程的范例,所有这些方法都被同时计数。
程序由进程组成。线程是进程内部的实体,它们可以在最需要的时候被调度。在 C# 中,我们也可以对线程的创建和其他生命周期事件施加很多控制。
接下来,在我们的自定义方法中,我们检索一个相当冗长的属性,称为 System。threading . thread . current thread . managed threadid为每个正在运行的线程提供一个唯一的标识号,将其存储到 happyID 中。
设置线程信息时,变量 delay 除以一千(即乘以 0.001)以秒为单位显示。我们还利用 C# 数学类中的 Round 方法将延迟整数舍入到两位小数。
using System;
using System.Threading;
public static void ChildThread() {
Random randomnr = new Random(); // Create a new Random-object, "randomnr"
int delay = randomnr.Next(0, 6000); // Set a random delay between zero and six seconds using our random-object
/* Assign thread ID number, i.e. System.Threading.Thread.CurrentThread.ManagedThreadId, into integer "happyID" */
int happyID = System.Threading.Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Happy thread " + happyID + " starts!");
// Round delay amount to two decimals when displaying it
Console.WriteLine("Happy thread " + happyID + " is paused for {0} seconds..", Math.Round(delay * 0.001, 2) );
Thread.Sleep(delay);
Console.WriteLine("Happy thread " + happyID + " will now resume!");
}
static void Main(string[] args) {
ThreadStart child = new ThreadStart(ChildThread);
for(int i=0; i<3; ++i) { // Create a total of three child-threads
Thread childThread = new Thread(child);
childThread.Start(); // Commence execution of thread
}
}
Listing 7-10A listing demonstrating how child threads are created in C#
列出 7-10 的主要方法相当简单。使用 for 循环,我们创建了三个 ChildThread 实例,使用 C# Thread 类中的 Start( ) 方法开始执行。
C# 线程中的锁和属性
像 Java 和 Python 一样,C# 为它的线程化应用程序提供了锁定机制。有了锁,我们可以让线程同步(参见清单 7-11 )。
using System;
using System.Threading;
public class LockingThreads
{
public void OurThread()
{
// Get a handle for the currently executing thread
// so we can retrieve its properties, such as Name
Thread wackyhandle = Thread.CurrentThread;
// Apply a lock to synchronize thread
lock(this) { // Locked code begins
Console.WriteLine("(This thread is " + wackyhandle.ThreadState+" with " + wackyhandle.Priority + " priority)");
for(int i=0; i<3; ++i) {
Console.WriteLine(wackyhandle.Name + " has been working for " +i+ " hours");
Thread.Sleep(400); // Wait for 0.4 seconds before next line
}
} // Locked code ends
}
}
public static void Main()
{
LockingThreads jollythread = new LockingThreads();
Thread thread1 = new Thread(new ThreadStart(jollythread.OurThread));
Thread thread2 = new Thread(new ThreadStart(jollythread.OurThread));
thread1.Name="Graham"; thread2.Name="Desdemona";
// Both of these threads are synchronized / locked:
// thread1 will be processed until completion before thread2 begins
thread1.Start();
thread2.Start();
}
Listing 7-11A listing in C# demonstrating thread locking and accessing some thread properties
清单 7-11 的输出如下:
(This thread is Running with Normal priority)
Graham has been working for 0 hours
Graham has been working for 1 hours
Graham has been working for 2 hours
(This thread is Running with Normal priority)
Desdemona has been working for 0 hours
Desdemona has been working for 1 hours
Desdemona has been working for 2 hours
如果没有锁定机制,我们会在这个与工作相关的状态更新中看到关于 Graham 和 Desdemona 的线条交替出现。您还将看到线程类的三个属性被充分展示出来(即,线程状态、优先级和名称)。
睡眠与产量
到目前为止,我们已经在几个清单中探索了 Sleep 方法的使用。重申一下,这告诉线程在预定的持续时间内小睡/挂起自己,通常以毫秒为单位。
该是我们带来 C# 收益的时候了。这个关键字在 C# 中根据上下文有多种含义;我们将在下一章更详细地研究它们。在多线程环境中,Yield 告诉线程进入不确定等待状态。被放弃的线程将在需要时被重新激活;这可能在几毫秒内发生,也可能需要更长的时间。Yield 基本上将 CPU 从执行特定线程中解放出来,以便处理其他更紧急的线程。这种紧急程度最终取决于一个人的操作系统。
Sleep 方法实际上可以在一定程度上模拟 C# 的产出。年纪大。NET 框架(4.0 版之前)还不支持 Yield。在这些情况下,在 Sleep(0)中键入类似于 Yield。
现在让我们看看 Yield 在实际中的表现(参见清单 7-12 )。
using System;
using System.Threading;
public class AmazingThreads
{
private int counter; // Declare a counter-variable
public void YieldThread()
{
Console.WriteLine("First thread is an infinite loop with Yield()");
while(true) // Start an infinite loop
{
// Without Yield, the counter would trail off to much longer lengths
Thread.Yield();
++counter;
}
}
public void SecondThread()
{
Console.WriteLine("Second thread informs you: First thread counter reached " + counter);
}
}
public class YieldExample
{
public static void Main()
{
AmazingThreads greatobject = new AmazingThreads();
Thread thread1 = new Thread(new ThreadStart(greatobject.YieldThread));
Thread thread2 = new Thread(new ThreadStart(greatobject.SecondThread));
thread1.Start();
thread2.Start();
}
}
Listing 7-12A listing demonstrating the Yield method in C#
在清单 7-12 中,我们定义了一个类整数,计数器,以粗略记录第一个线程在让步开始之前的执行时间长度。然后我们启动两个线程,第一个线程进入无限循环。第二个线程显示存储在计数器变量中的数量。就收益率而言,我们可以预计,在大多数情况下,这一数据将远低于 2000 点。如果没有 Yield,计数器将显示高出几个数量级的读数,最终导致程序无响应。
同样,操作系统是根据其他线程的状态和优先级调度发出让步请求的线程的最终实体。
加入
C# 中线程工作最重要的方法之一叫做 Join 。使用这种方法,您可以让线程在其他线程完成处理时等待。自然,Join 只能被已经开始执行的线程调用(参见清单 7-13 )。
using System;
using System.Threading;
static void OurFunction() { // Create a custom method
for (int i = 0; i < 10; ++i)
Console.Write(i + " ");
}
static void Main(string[] args) {
Thread thread1 = new Thread(OurFunction);
thread1.Start();
thread1.Join();
// Join() makes sure a thread completes its processing
// before the listing proceeds
Console.Write("10"); // Finish the list with a number ten
}
Listing 7-13A basic demonstration of the Join method
in C#
清单 7-13 为我们提供了一个 Join 方法的基本示例。清单 7-13 的输出应该是 0 1 2 3 4 5 6 7 8 9 10 。如果没有 Join,您可能会得到意想不到的结果,例如 100 1 2 3 4 5 6 7 8 9 。这是因为最后的控制台。Write 没有被明确告知要等到 thread1 完成其处理。
在 C# 中实现异步
基本上,异步编程是一门艺术,让一个程序包含许多任务,这些任务不会互相冲突(或与其他程序冲突)。这种方法对程序员来说也更容易理解,因为它产生了相当清晰的代码布局。
C# 中异步处理的当前实现利用了一个名为 ThreadPool 的类。这个类采用了不同于 Thread 类的方法(在本章前面已经讨论过)。首先,ThreadPool 只创建低优先级的后台线程;给线程分配优先级不是这个类的一部分。
一个设计模式是一个可重用的解决方案,用于解决软件设计中特定环境下反复出现的问题。
现在,C# 中有三种主要的异步编程范例。其中只有一个 TAP 被微软推荐在 2021 年使用。
-
异步编程模型(APM):APM 方法使用 IAsyncResult 接口及其 BeginOperationName 和 EndOperationName 方法。微软不再推荐使用这种有些费力的设计模式。
-
基于事件的异步模式(EAP) : EAP 被设计来提供异步软件范例的好处,同时从程序员的角度来看保持事情相对优雅。这种设计模式也被认为是过时的方法。
-
基于任务的异步模式(TAP) : TAP 是编写异步软件的最新最优雅的设计模式。与 APM 和 EAP 不同,TAP 使用单一方法来表示异步操作的启动和完成,并以任务和任务<t 结果> 类为中心。
有关使用 TAP 的异步模式的示例,请参见清单 7-14 。
using System;
using System.Threading;
static async Task Main(string[] args) // Our small asynchronous main thread
{
await happyMethod(); // Await for happyMethod to do its thing
Console.WriteLine("Program complete.");
}
public static async Task happyMethod() // Create asynchronous Task happyMethod()
{
int fibo = await Fibonacci(); // Await for Fibonacci-method to complete
// and assign its results into integer "fibo"
DisplayFibonacci(fibo); // Summon our most simple method
}
public static async Task<int> Fibonacci()
// Create asynchronous Task Fibonacci(), which is to return an integer
{
int x = 0, y = 1, z = 0; // Define variables needed in our for-loop
// This task is kept simple for the sake of clarity. In real life
// you could do some heavy lifting here in concert with other
// asynchronous tasks.
await Task.Run(() =>
{
for (int i = 2; i < 5; i++) // Look for Fibonacci number
{
z = x + y;
x = y;
y = z;
}
});
return z; // Return result, i.e. the fifth Fibonacci
}
public static void DisplayFibonacci(int fibo) // Method for display the result
{
Console.WriteLine("The fifth Fibonacci number is " + fibo);
}
Listing 7-14A demonstration of asynchronous processing in C# using the TAP design pattern
现在,清单 7-14 被分成四个方法,其中只有一个在本质上不是异步的(即 DisplayFibonacci )。前三种方法使用了异步/等待机制。通过在方法旁边使用定义 async ,我们指定一个方法是异步的。这允许我们使用 await 关键字,将控制权交给它的调用方法。
async/await 机制允许程序在后台处理潜在的繁重计算,这通常会减少用户界面的故障和/或加快网络文件传输。
创建方法时,TAP 有一些特定的关键字。任务是用来表示不返回值的方法。在我们的清单中,您还会发现一个任务< int > 的实例;这个表达式表示一个必须返回整数的方法。任务可以返回任何类型的变量。所有创建的异步任务都将被安排在由 ThreadPool 类管理的线程上运行。
命令等待任务。Run 使用 ThreadPool 类在自己的线程中启动一个任务。现在,基本上有三种方法来调用这个机制:
1 await Task.Run(() => SomeMethod );
2 await Task.Run(() => SomeOtherMethod(something) );
3 await Task.Run(() => {
Console.WriteLine("Let's do something here");
return 0;
});
第一种方法接受以前声明的不带参数的方法;您只需传递一个方法的名称。第二种方法是采用参数的方法。我们也可以使用基于块的语法,正如第三个机制所演示的。和往常一样,必须注意圆括号、花括号和其他特殊字符的正确使用。正如您可能注意到的,清单 7-7 对其等待任务使用了基于块的方法。运行()。
在斐波纳契数列中,每个数字代表它前面两个数字的和,例如,0,1,1,2,3,5,8。这个概念是由意大利数学家*莱昂纳多·博纳奇(约 1170 年,可能是公元 1250 年)*在其开创性的著作 Liber Abaci 中引入的。在他生命中的某个时刻,波纳奇被取了一个绰号叫斐波那契。
最后
读完这一章,你可能会对以下内容有所了解:
-
C# 系统的一些常见用法。全球化名称空间及其一些类,包括 Calendar 和 CultureInfo
-
C# 中使用 file 和 FileInfo 类的基本文件操作
-
C# 中垃圾收集(GC)的基础知识
-
C# 中基本多线程的实现,包括一些最相关的方法(连接、启动、睡眠、产出)
-
使用基于任务的异步设计模式(TAP)及其异步/等待机制的 C# 异步编程基础
第八章致力于 C#、Java 和 Python 提供的更好的技术,包括高级多线程。我们将进入制作有用的(但绝对是小规模的)应用程序的世界。
八、毕业日:稍大的编程项目
在这一章中,我们将研究几个比目前介绍的稍微大一点的 Python、C# 和 Java 编程项目。这些项目旨在演示本书中讨论的一些最相关的概念:变量、循环、文件访问和线程。如果你能够破译这些小项目的内部结构,你可以有把握地说,你已经对本书中介绍的三种编程语言的基础有了一定的理解。如果在这一点上你仍然不确定你作为一个程序员的技能,这一章可以帮助你澄清一些概念。
Python 的通用聊天模拟器
我们将从向世界介绍通用聊天模拟器开始这一章。这个程序将展示 Python 的以下特性,所有这些我们在本书前面都讨论过:
-
文件操作
-
字符串格式
-
自定义函数定义(使用 def 关键字)
-
变量(包括随机化)、列表和迭代
-
基本异常处理
-
元素枚举
-
程序流程和简单循环
-
日期和时间检索
像本章中的其他程序一样,通用聊天模拟器(UCS)是一个控制台应用程序,这意味着它只在文本模式下工作;为了保持简单,不使用图形元素。
现在,UCS 的两个关键元素以虚拟聊天主持人和他们的虚拟观众的形式出现,后者由一个共享的昵称“聊天”来描述。这两个虚拟角色每隔几秒钟就在屏幕上输出文本,文本的内容由三个外部的文本文件决定,因此很容易编辑。
开始使用 UCS 时,不需要键入整个清单;这个文件可以在 GitHub 上找到,这个免费下载的列表也有完整的注释。然而,为了彻底起见,现在让我们一个块一个块地检查程序。
第一部分:设置项目
首先,我们导入所需的代码模块。日期时间、时间、随机随 Python 而来。然而,对于我们的项目,我们还需要从 Python 包索引(PyPI) 中获取一个额外的模块。这是一个免费的在线知识库,用于扩展 Python 的功能。我们将使用 Abhijith Boppe 的资产 clrprint 。安装并导入 clrprint 模块后,我们可以用类似下面这样的代码行将彩色文本添加到 Python 项目中: clrprint("Hello!",clr='red') 。项目代码的第一部分见清单 8-1 。
只需在 macOS 终端、Windows shell 或 Linux 命令行中输入*“pip 3 install clr print”*即可安装 clrprint 。
# import three Python code-modules
import datetime
import time
import random
# also import the clrprint-module from the Python Package Index (PyPI)
from clrprint import *
name = "Apple representative" # virtual chat host name
mood = 50 # moods: 50 = neutral, 0 = bad ending, 100 = good ending
score = messages_sent = 0 # assign zero to score and messages_sent
response = negative = positive = False # assign False to three variables
negativetrigger = "orange" # negative trigger word, used to reduce mood
positivetrigger = "apple" # positive trigger word, used to grow mood
# Fetch current time and date using now() from datetime-class
initial_time = datetime.datetime.now()
# define a custom function, print_chat()
# variable "cap" decides how the chat messages are to be capitalized, if at all
# both 0 and -1 mean no changes in capitalization
# 1 means capitalize first letter of a line, 2 means capitalize all letters in a line
def print_chat(chat):
cap = random.choice([-1, 0, 1, 2])
if cap == 1:
chat = chat.capitalize() # summon capitalize() method
elif cap == 2:
chat = chat.upper() # summon upper() method for ALL CAPS
print("Chat: " + timer.strftime("<%A %H:%M> ") + chat)
Listing 8-1First part of the listing for the Universal Chatting Simulator in Python
接下来,我们定义一个名为 open_chatfile 的定制函数,它有两个参数, name 和 deletechar 。Name 只是表示我们要打开的文件名。Deletechar 用于表示我们要从这些列表中删除的字符;这是存在的,因此我们可以从聊天中删除所有换行符(例如, \n )以消除不必要的空行(参见清单 8-2 )。
我们还在函数中使用了一个临时列表结构 templist 。数据被输入到 templist 中,以便可以对其进行枚举并删除其换行符。使用 Python 的 readlines 方法读取文件内容。
def open_chatfile(name, deletechar):
try:
file = open(name, 'r') # open file in read-only mode, "r"
print("Loading file " + name)
templist = file.readlines() # read file into templist
for i, element in enumerate(templist): # enumerate the list
templist[i] = element.replace(deletechar, '') # delete newlines
return templist # return the enumerated list
file.close() # closing the file is always a good practice
except (FileNotFoundError, IOError): # handle an exception
# str() converts variables to string
print("The file " + str(name) + " could not be read!")
Listing 8-2The second part of the listing for the Universal Chatting Simulator in Python
第二部分:展示高分
在我们开始记分之前,我们要定义三个列表用于 open_chatfile 函数;他们被称为中性情绪、坏情绪和聊天情绪。来自文本文件的数据需要存储在某个地方,这就是我们三个列表的用武之地(参见清单 8-3 )。
在 Python 中,我们可以使用赋值操作符在同一行用同一个变量初始化变量,包括列表,例如, a = b = 10 或 happy = wonderful = [ ] 。
接下来我们实际使用我们的方法,召唤它三次打开文件 neutral.txt 、 bad.txt 和 chat.txt 。open_chatfile 的另外两个参数是我们之前创建的列表和换行符。现在,我们有三个列表,充满了发人深省的交流,为我们的虚拟聊天乐趣做好了准备。
在项目的这一部分结束时,我们将打开一个包含当前最高分的文本文件。是的,我们在通用聊天模拟器中有一个评分系统;稍后将详细介绍。我们通过创建一个 try 块来获取分数数据。提醒一下,try 块允许我们动态测试代码中的错误。加上 except 关键字(exception 的缩写),我们可以引入自己的错误消息。在清单 8-3 的 try 块中,试图加载文件 highscores.txt 。如果找不到这样的文件,异常块会告诉我们并创建文件。
# display program title in vibrant yellow using the clrprint-module
clrprint("Universal Chatting Simulator (UCS) ver 0.1.0 by Bob the Developer", clr='yellow')
# define three list-structures to be filled with data from text-files
neutralmoods = badmoods = chatmoods = [] # [] denotes an empty list
# insert textfile-data into the three lists using our custom function
neutralmoods = open_chatfile("neutral.txt", "\n")
badmoods = open_chatfile("bad.txt", "\n")
chatmoods = open_chatfile("chat.txt", "\n")
print("Your starting mood is " + str(mood) + "/100")
# display current highscore stored in "highscore.txt"
try:
# open "highscore.txt" in r+ mode for both reading and writing
with open("highscore.txt", "r+") as scorefile:
hiscore = scorefile.read()
print("CURRENT HIGHSCORE: %s" % hiscore)
# if highscore.txt is not found, create this file
except (FileNotFoundError, IOError):
print("Highscores not found. Creating highscore.txt")
scorefile = open("highscore.txt", "w")
scorefile.write("0") # write zero into "highscore.txt"
scorefile.close()
Listing 8-3The third part of the listing for the Universal Chatting Simulator in Python
第三部分:不屈不挠的主循环
现在,我们进入主循环的领域。这是大部分处理和/或魔术发生的地方。UCS 在 while 循环中运行,其内容为“当可变情绪小于 100 时”如果情绪超过 100,循环就不再运行。此外,如果 mood 属于 break 关键字,则使用 break 关键字退出循环。
虚拟聊天者使用简单称为 chat 的变量在程序中获得他们的表示;从 chatmoods 列表中随机选择一个元素,并输入到该变量中,以在清单 8-4 中进一步显示。
你可能想知道关于情绪多变的喧闹是怎么回事。不仅 mood 用于中断主循环,而且如果它低于 30,程序将开始为我们的虚拟主机使用一组不同的行(即文件 bad.txt 中定义的更糟糕的行)。mood 整数的值受特定关键字的影响,即另外两个变量 negativetrigger 和 positivetrigger ,在前面的清单 8-1 *中定义。*如果程序检测到一个负面的触发词,“情绪”会减少 1 到 5 之间的一个数字。类似地,正触发字导致整数被加上一个 1 到 5 之间的数。为此,我们使用行 random.randint(1,5) 生成一个在期望范围内的整数,将其赋给另一个变量 moodchange 。
现在,如果在清单 8-4 中检测到一个触发字,有两个布尔变量会产生一个标志;这些简称为负和正。这两个布尔值用来显示程序后面任何情绪变化的状态消息。
资本化和延迟
接下来,我们在程序中加入一些修饰性的变化。清单 8-4 可以用三种格式显示所有的聊天行:常规、首字母大写和全部大写。这是通过每个主循环检查一次变量 cap 的随机值来实现的。我们发布 cap 四个潜在值:-1、0、1 和 2。如果 cap 保持在 1 以下,一行颤振保持不变。值 1 使程序调用 Python 的*大写()*方法,正如它的名字所暗示的那样。最后,值 2 对应于 upper( ) 方法,基本上为我们的虚拟 chatters 打开了 caps lock(仅针对一行)。
对于一个模拟聊天的程序,必须实现一些延迟,以便观众可以充分享受适度有见地的评论流。在清单 8-4 中,我们通过应用 Python 奇妙的 sleep()方法并给它一个 1 到 3 之间的随机值来实现这一点,对应于相同的延迟秒数。
# main while-loop begins
while 1 < mood < 100: # keep looping as long as mood remains under 100
timer = datetime.datetime.now() # Fetch current time and date
chat = random.choice(chatmoods) # Select a random line from "chatmoods"
if negativetrigger in chat: # negative trigger word found
# set variable "moodchange" between one and five
moodchange = random.randint(1, 5)
mood -= moodchange
negative = True # negative trigger word was found
if positivetrigger in chat: # positive trigger word found
moodchange = random.randint(1, 5)
mood += moodchange
positive = True # positive trigger word was found
print_chat(chat) # summon function for printing chat's output
messages_sent = messages_sent + 1
if negative: # same as "if negative == True:"
if mood < 0:
mood = 0
clrprint("MOOD -" + str(moodchange) + "! Your current mood is
" + str(mood) + "/100", clr='red')
negative = False
if positive: # same as "if positive == True:"
clrprint("MOOD +" + str(moodchange) + "! Your current mood is
" + str(mood) + "/100", clr='green')
positive = False
# delay program between one to three seconds
time.sleep(random.randint(1, 3))
# set a 50% of chance for a response from our virtual chat host
response = random.choice([True, False])
if response:
if mood > 30:
clrprint(
name + ": " + timer.strftime("<%A %H:%M> ") +
random.choice(neutralmoods),
clr='white')
else:
clrprint(name + ": " + timer.strftime("<%A %H:%M> ") +
random.choice(badmoods),
clr='white')
Listing 8-4The fourth part of the listing for the Universal Chatting Simulator in Python
第四部分:游戏结束
在一个非常繁忙的主循环之后,我们终于到达了聊天模拟器的最后一部分。它主要是通过添加一些路径分支来保持分数(见清单 8-5 )。
程序中的评分是基于虚拟聊天者发送的聊天消息的数量;每条信息值三分。这个模拟器程序有两种结局。在“坏”的一个中,观看者被简单地告知虚拟聊天主持人的情绪降到了零。另一个更乐观的结局给予观众价值 1000 分的“欣快奖励”。
# game over
score = messages_sent * 3 # make each message from chat worth 3 points
if mood < 1: # bad ending
clrprint("Your mood fell to 0/100!", clr='yellow')
else: # good ending
clrprint("Your mood reached 100/100!", clr='yellow')
clrprint("Euphoria bonus: 1000", clr='yellow')
score = score + 1000 # add euphoria bonus to score
timespent = timer - initial_time # calculate elapsed time
print("Time spent in the chat: " + str(timespent))
print("Your chat sent " + str(messages_sent) + " messages")
print("SCORE: " + str(score))
# if current score is greater than saved highscore, save that to file
with open("highscore.txt", "r+") as scorefile:
hiscore = scorefile.read()
if not hiscore: # if file is empty..
hiscore = '0' # ..assign zero into it
if score > int(hiscore): # if current score is greater than the
stored highscore
print("NEW HIGHSCORE MADE!")
scorefile.seek(0) # "rewind" to the beginning of the file
scorefile.write(score) # write the new score into "highscore.txt"
else:
print("CURRENT HIGHSCORE: %s" % hiscore)
Listing 8-5The fifth and final part of the listing for the Universal Chatting Simulator in Python
在清单 8-4 中,我们有很多方法在工作。作为回顾,让我们记录通用聊天模拟器使用的主要 Python 方法(见表 8-1 )。
表 8-1
通用聊天模拟器使用的一些 Python 方法
|方法
|
描述
|
清单 8-4 中的使用示例
| | --- | --- | --- | | 打开( ) | 打开文件进行读取和/或写入 | 文件=打开(名称,' r ') | | 阅读( ) | 读取打开文件的内容 | hiscore = scorefile.read() | | seek() | 设置文件的位置 | scorefile.seek(0) | | random.randint() | 返回一个随机整数 | neutralmoods[random.randint(0,len(neutralmoods) - 1)], | | random.choice() | 随机选择一个元素 | response = random . choice([对,错]) | | str() | 将变量转换为字符串 | print("SCORE: " + str(score)) | | int() | 返回一个整数对象 | 如果 score > int(hiscore): | | len() | 返回对象/字符串长度 | |
C# 线程竞速器
为了让你回忆一下线程在 C# 中是如何工作的,请看一下清单 8-6 。在这个小清单中,发送了三个线程来运行五米短跑。该程序演示了 C# 语言的以下功能:
-
对共享数据/变量的简单线程化和基于线程的访问
-
使用锁定对象和互锁。减量( )方法
-
将随机值赋给变量,包括字符串数组
-
定义方法(例如 MakeNames)并从中检索数据
using System;
using System.Threading;
public class ThreadRacer
{
// Define an empty (i.e. null) string for holding the winner of the race
string winner = null;
// Define an integer for examining the threads' arrival order at finishing line
int place = 3;
// Create an object, happylock, for thread-locking purposes
static object happylock = new object();
// Define Racer, a thread-based method
public void Racer()
{
int distance = 5; // Set racing distance to 5
// Assign variable "handle" with the current thread so we may read
// thread properties, such as Name
Thread handle = Thread.CurrentThread;
// Loop while a thread's distance is greater than zero
while(distance > 0) {
Console.WriteLine(handle.Name+" is at " + distance + " meters from the goal.");
// Reduce racer's distance by one using an Interlocked.Decrement-method
Interlocked.Decrement(ref distance);
}
// Summon our locking object
lock(happylock) {
// If the winner-variable is still set to "null", announce current thread
// as the winner
if(winner == null) Console.WriteLine(handle.Name + " WON 1st place!");
// Use an Interlocked.Decrement-method to subtract 1 from variable "place"
Interlocked.Decrement(ref place);
winner = handle.Name;
if(place==1) Console.WriteLine(handle.Name + " came in 2ND..");
if(place==0) Console.WriteLine(handle.Name + " came in 3RD..");
}
}
public static string MakeNames() {
// create new object, random0, using the C# Random class
var random0 = new Random();
// Create two string-arrays, first_names and last_names
string[] first_names = { "Peter", "Paul", "Patrick", "Patricia", "Priscilla", "Pauline" };
string[] last_names = { "Plonker", "Pillock", "Prat", "Pecker" };
int f_index = random0.Next(first_names.Length);
int l_index = random0.Next(last_names.Length);
// return name as a string:
return (first_names[f_index] + " " + last_names[l_index]);
}
public static void Main() // Main function
{
ThreadRacer racer = new ThreadRacer();
Thread thread1 = new Thread(new ThreadStart(racer.Racer));
Thread thread2 = new Thread(new ThreadStart(racer.Racer));
Thread thread3 = new Thread(new ThreadStart(racer.Racer));
thread1.Name = "[Racer A] " + MakeNames();
thread2.Name = "[Racer B] " + MakeNames();
thread3.Name = "[Racer C] " + MakeNames();
Console.WriteLine("Welcome to THREAD RACER!\n");
thread1.Start(); // Start threads
thread2.Start();
thread3.Start();
}
}
Listing 8-6A program demonstrating threading in C#
在清单 8-6 中,我们为线程同步制作了一个锁定对象 happylock。我们还使用了一种新方法,互锁。减量,将“距离”和“位置”变量减一。为了在基于线程的项目中进行有效和安全的添加,C# 还提供了一种称为 Interlocked 的方法。增量(我们在 Thread Racer 中不需要它)。在 C# 中处理基于线程的代码时,这两种方法通常可以取代标准的减法和加法运算符(例如- variable 或++variable)。
现在,我们在清单 8-6 中精心制作了一个我们自己的方法;这将是的成名之作。这个方法是用来演示数组的,更具体地说是字符串类型的数组。这些数组用名称填充,并作为单个字符串返回,供 main 函数使用。
Thread Racer 中的 f_index 和 l_index 变量用于存储字符串数组 first_names 和 last_names 的所需索引位置。这些索引位置是通过在数组长度上应用下一个方法(一个随机数发生器)依次创建的。这些长度然后通过调用恰当命名的长度方法来推导。
下面是清单 8-6 中的一行代码:
int f_index = random0.Next(first_names.Length);
这是这样的:“让整数 f_index 等于一个随机数,其最大值为名字数组的长度。”脱口而出。
我们可以将方法的输出直接添加到变量中。以清单 8-6 中的这一行为例:*线程 1。name = "[Racer A]"+MakeNames();*其中我们将字符串“[Racer A]”与输出中发生的任何 MakeNames 结合起来,只使用了一个简单的加号运算符。
Thread Racer 的线程调度也是由操作系统执行的。这意味着过一会儿你可能会得到同样的结果。
C# 的快乐测验
接下来,清单 8-7 向您展示了一个基于控制台的小测验程序。它展示了这种优秀语言的以下特征:
-
基本文件操作和线程
-
字符串格式
-
程序流、用户交互和循环
-
变量和迭代
Jolly Quiz 使用两个独立的文件, questions.txt 和 answers.txt 。它们都是自上而下处理的,问题文件中的每一行都与答案文件中的同一行相对应。这种方法使添加新问题和修改现有问题变得容易,只需编辑这两个文本文件。
希望用户在程序执行期间键入他们的答案。一个线程对象在后台运行,独立于正在进行的测验,告诉用户“快点!”每四秒钟。每个正确答案加十分。在测验结束时,将向用户显示他们的分数以及正确答案的百分比。
using System;
using System.IO;
using System.Threading;
class JollyQuiz
{
public static void Main()
{
// Declare three integer-variables
int counter=0, score=0, percentage=0;
// Declare a boolean-variable (takes: true or false)
bool countdown=true;
Console.WriteLine("Welcome to THE JOLLY QUIZ PROGRAM!\n");
// Create a new thread-object, timerthread1
Thread timerthread1 = new Thread(delegate() {
Thread.Sleep(6000); // Sleep for 6 seconds at first
while(true) { // Create an infinite loop inside our thread
Console.WriteLine("HURRY UP!");
Thread.Sleep(4000);
// End thread processing if countdown is set to "false" in main program
if(!countdown) break; // This reads: if countdown is NOT "true" exit loop
}
});
// Open the question and answer -files. These should work from the
// same directory as your project file
var q = File.ReadAllLines("questions.txt");
var a = File.ReadAllLines("answers.txt");
timerthread1.Start(); // Start the timer-thread
foreach(string lines in q) // Iterate through the questions-file
{
Console.WriteLine("{0}\nEnter response: ", lines);
// Convert response to ALL CAPS to eliminate input capitalization issues
// i.e. make "Helsinki" equally valid to "HeLSiNKI"
string response = Console.ReadLine().ToUpper();
++counter;
// Compare user input against correct answers
if(response.Equals(a[counter-1])) {
Console.WriteLine("{0} is correct!", response); score+=10;
// use else if to determine whether the user wrote a wrong answer
// or simply pressed enter
} else if(response!="") Console.WriteLine("{0} is wrong..", response);
else Console.WriteLine("Type something next time!");
}
// No need to remind the user to hurry up after the quiz is over;
// so we next tell timerthread1 to end processing
countdown = false;
Console.Write("\nYour score: {0}. ", score);
percentage = score*100/(counter*10);
Console.WriteLine("You knew/guessed {0}% right on the {1} questions presented.", percentage, counter);
// Display shaming message for motivation after a poor score
if(percentage < 50) Console.WriteLine("Shame on you!");
// Display a different message for a more impressive score
if(percentage >= 90) Console.WriteLine("Well done!");
}
}
Listing 8-7A quiz program in C# demonstrating basic file operations and threading
你会注意到清单 8-6 和 8-7 有一个特殊的变量类型, var 。这是 C# 中的一个隐式变量定义,简单地说就是允许编译器程序决定变量的类型。对于 C# 中较小的项目,这种方法通常很好。
一本 Java 语言的食谱
接下来我们将带着一个名为美味芬兰食谱的小程序回到 Java 的世界。这个相当简单的清单基本上显示了容易定制的文本文件。
import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
public class FinnishRecipes {
// Define LoadText, a method for loading text-files
// which takes a file path and filename as its arguments
static void LoadText(String path1, String section) {
try {
File fileobject1 = new File(path1 + section);
// Create a Scanner-object, reader1, for user input
Scanner reader1 = new Scanner(fileobject1);
while (reader1.hasNextLine()) {
String output1 = reader1.nextLine();
System.out.println(output1);
}
reader1.close();
} catch (FileNotFoundException e) { // Throw exception/error if needed
System.out.println("File " + section + " not found!");
}
}
static void DisplayMenu() {
System.out.println("DELICIOUS FINNISH RECIPES\nEnter choice:");
System.out.println("[1] Salads");
System.out.println("[2] Main Courses");
System.out.println("[3] Custom recipes (make your own)");
System.out.println("[4] Quit)");
}
// Define Main method
public static void main(String[] args) {
// Display main menu
DisplayMenu();
Scanner keyscanner1 = new Scanner(System.in);
while(true) { // Create an infinite while-loop
String input1 = keyscanner1.nextLine();
// "C:\\" refers to the file-path to the most common Windows root-drive
// modify this path as per your file locations
if(input1.equals("1")) LoadText("","salads.txt");
if(input1.equals("2")) LoadText("","maincourses.txt");
if(input1.equals("3")) LoadText("","custom.txt");
if(input1.equals("4")) break;
// Display menu again if user inputs a plain enter
if(input1.equals("")) DisplayMenu();
} System.out.println("Have a great day!");
}
}
Listing 8-8A program in Java demonstrating file access
在清单 8-8 中,我们创建了一个方法 LoadText ,它接受两个参数,即文件路径和名称。然后,它开始加载指定的文本文件,并在屏幕上逐行显示其内容。我们还有另一个方法, DisplayMenu ,它只是用来向用户显示可用的选项。两种方法都被定义为 void ,这意味着它们不返回任何信息。
清单 8-8 中有一个 try-catch 块,如果找不到文件,它会发出声音,也就是抛出一个异常。无限 while 循环使程序一直运行,直到用户在键盘上输入“4”,在这种情况下,执行 break 关键字。
Java 中的秒表
下面的程序就像一个简单的秒表。你输入几秒钟让它嘎吱作响,然后它会告诉你时间到了。这个程序代表了 Java 线程的一个非常基本的实现,由一个主线程组成。
import java.util.Scanner;
public class Counter {
public static void main(String[] args) {
int counter1=0;
Scanner keyscanner1 = new Scanner(System.in);
// Loop while counter1 equals zero
while(counter1 <= 0) {
System.out.println("Enter timer count (in seconds): ");
String input1 = keyscanner1.nextLine();
// begin a try-catch block
try {
// Convert "input1" to integer
counter1 = Integer.parseInt(input1);
} catch (NumberFormatException e) {
System.out.println("Enter value in numbers only!");
}
if(counter1<0) System.out.println("(Positive numbers only!)");
} // Loop end
// Loop while counter is greater than zero
while(counter1 > 0) {
System.out.println("Time left: " + counter1 + " sec");
try
{ Thread.sleep(1000); }
catch(InterruptedException ex)
{ Thread.currentThread().interrupt(); }
--counter1;
} // Second loop end
System.out.println("All done!");
}
}
Listing 8-9A listing for a stopwatch program
in Java
清单 8-9 有两个 while 循环。第一个是从用户那里获取输入,并确保它符合特定的规则。主要地,输入仅由正数组成。如果输入了字母或其他非数字字符,程序会抛出一个异常,准确地说是一个 NumberFormatException 。此外,第一个循环拒绝接受负数,并在需要时发布一条消息。
在第二个循环中,我们使用带有 Thread.sleep(1000) 的一行来体验期望的延迟量。请注意,为了让 sleep 方法在 Java 中工作,您需要检查它是否有异常。在清单 8-9 中,我们为此使用了一个经典的 try-catch 块。
最后
读完本章后,您将会对 Python、C# 和 Java 的以下方面有所了解:
-
数据结构、程序流程和基本文件操作
-
初级线程技术
-
使用 try-catch 块的异常处理
-
功能定义和访问
第九章的代码会轻得多,都是关于一些可爱的图,也就是那些用通用建模语言(UML) 创建的图。
九、UML 类图
这一章致力于统一建模语言的奇迹,它是软件设计中一个相当普遍的工具——也当之无愧。在本书的前面,我们仅仅触及了 UML 的表面,现在我们将更深入地探索 UML 提供的更多可能性,因为它与类建模有关。
可视化面向对象的范例
我们在第四章中介绍了面向对象的范例。书中的大部分代码清单实际上都是这种范例,包括它们的类、对象和方法。将 UML 视为软件开发的预编码阶段的一个有价值的工具。你用它描绘出所有与 OOP 相关的机制,包括类和它们与其他类的关系。
通用建模语言有两个标准,即 UML 1.x(最初于 1996 年)和 UML 2.x(于 2005 年首次发布)。这两个标准都包含许多类型的图,几乎可以用于任何建模目的。
UML 图的类别
UML 图类型的整个范围超出了本书的范围。然而,知道 UML 有哪些变种是很有用的。概括地说,它通常分为两个主要类别,然后再分为许多子类。许多较大的项目可能需要以下大部分(非详尽的)图表列表;它们是互相排斥的。
-
行为图:与结构图不同的是,行为图集中于描绘一个正在运行的系统。
-
用例:UML 中的参与者是与系统交互的一方。用例图描述了(人类)参与者和特定系统之间的交互。用例关注系统提供的特定功能。例如,一个人从 ATM 机取钱是用例图的一个潜在场景。
-
序列图:这些图关注的是对象发送消息的时间顺序。因此,当对象之间的交互被精确地建模时,我们使用 UML 序列图。
-
状态图:UML 中的状态意味着一个对象持有的不同种类的信息,而不是它们的行为。当对系统状态的变化建模时,使用状态图。
-
活动图:当我们需要可视化系统中的控制流时,这就是要使用的图的类型。基本上,活动图提供了一个系统在执行时如何工作的概念。
-
-
结构图:这些类型的图是用来模拟静态系统的本质的。
-
类图:这些可能是 UML 中最常用的图。在这一章中,我们将主要关注类图。正如您可能已经猜到的,它们处理的是类方面,这是面向对象编程(OOP)范例的基础。
-
对象图:明显与类图相关,对象图描述的是类的实例。在构建系统原型时,经常会用到这些类型的图。
-
组件图(Component diagrams):这些图关注的是系统中软件组件的类型以及它们之间的联系。这些组件通常被称为物理资产,尽管从技术上讲,它们倾向于完全驻留在数字层面。
-
部署图:这些用于可视化系统的完整布局,显示系统的物理和软件部分。部署图也可以称为系统组件的拓扑。
-
回到 UML:类图
如前所述,UML 可以用来建模几乎任何东西,一个类图可以帮助我们可视化一个面向对象的软件项目。让我们从简单的事情开始(见图 9-1 )。
图 9-1
一个简单的 UML 类/对象图
图 9-1 展示了 UML 中的类,包括它们的变量和方法以及类间关系;它还具有一个单一的 UML 对象图。
图 9-1 中的主类叫做 Book。它有三个变量(即标题、出版商和出版年份)。如您所见,我们还需要在类中指定变量的类型(例如,String)。UML 中属性或方法前面的加号(+)表示公共访问修饰符。带有破折号(-)字符的类成员表示私有访问修饰符。
你可以像我们在图 9-1 中所做的那样,直接在 UML 图中添加有用的注释;这些将采取右上角折叠的矩形的形式。
现在,简单的线条标记了 UML 中类和其他实体之间的关联。图 9-1 中这些行旁边的数字和星号是 UML 中多样性的演示。这个概念用于指示一个类可以提供或被允许与之交互的实例(即对象)的数量。
转到图 9-1 的对象部分,我们有一个主类的实例,叫做 exampleBook1 。在 UML 中,对象可以被表示为尖边或圆边的盒子;为了多样化,我们选择了后者。
UML 中基于树的继承
继承可以在 UML 中以简洁的方式表示。为此,我们可以使用基于树的方法(参见图 9-2 )。
图 9-2
使用基于树的方法演示 UML 中的继承的图表
图 9-2 中的图表展示了三个专业化水平。首先,您有通用的业务类。接下来是三个专业类别,即出版商、面包店和香水店。最后,我们有两个不同类型的出版商类最专业的水平,一类是婚礼面包店和奢侈品香水店。
在 OOP 中,术语基类也被称为父类。子类通常被称为子类。
图 9-2 中的业务类定义为摘要;在 UML 中,斜体的类名表示这一点。这些类为子类提供了特定的继承方法。抽象类不能直接实例化。相反,您使用它们的非抽象(即具体)子类之一来创建对象。
Java 中的图 9-2
如果将图 9-2 翻译成 Java 会是什么样子?看看清单 9-1 中可能的解决方案。
在清单 9-1 中,业务是基类。所有其他的类都被定义在它的文件 Business.java 中,以避免拥有多个源文件(例如,,等等)。).
// Define abstract base class
abstract class Business {
// Define class attributes/variables
public double turnover = 20000;
public String address = "none";
// Define method for displaying address attribute/variable
void DisplayAddress() {
System.out.println(address);
}
// Create main method
public static void main(String[] args)
{
// Uncommenting the next line will throw an error
// Business business1 = new Business();
// Create new Bakery object, happybakery, and display its address
Bakery happybakery = new Bakery();
System.out.println("A new bakery is opened at " + happybakery.address);
// Create new FictionPublisher object, jolly_books, and display its turnover
FictionPublisher jolly_books = new FictionPublisher();
System.out.println("Fiction publisher Jolly Books had an unfortunate turnover of £" + jolly_books.turnover + " in 2020");
// Create new NonFictionPublisher object, silly_books, set and display its turnover
NonFictionPublisher silly_books = new NonFictionPublisher();
System.out.println(("Non-fiction publisher Silly Books had a great turnover of £" + (silly_books.turnover + " in 2020")));
// Create new LuxuryPerfumerie object, exquisite_odors, set and display its address
LuxuryPerfumerie exquisite_odors = new LuxuryPerfumerie();
exquisite_odors.address = "10 Wacky Avenue";
System.out.print("A wonderful luxury perfumerie is located at ");
exquisite_odors.DisplayAddress(); // Summon method inherited from Business class
}
}
// Define the rest of the classes
class Bakery extends Business { String address = "2 Happy Street"; }
class WeddingBakery extends Bakery { }
class Perfumerie extends Business { }
class LuxuryPerfumerie extends Perfumerie { }
class Publisher extends Business { }
class FictionPublisher extends Publisher { double turnover = 4.55; }
class NonFictionPublisher extends Publisher { /* turnover is inherited from Business class */ }
Listing 9-1A Java implementation
of Figure 9-2 demonstrating inheritance (filename Business.java)
C# 中的图 9-2
接下来让我们观察图 9-2 在 C# 上的实现。您可能还记得本书前面的章节,Java 和 C# 是非常相似的语言。
using System;
abstract class Business {
// Define class attributes/variables
public double turnover = 20000;
public string address = "none";
// Define method for displaying address attribute/variable
void DisplayAddress() {
Console.WriteLine(address);
}
// Create main method
public static void Main() {
// Uncommenting the next line will throw an error
// Business business1 = new Business();
// Create new Bakery, happybakery, and display its address
Bakery happybakery = new Bakery();
Console.WriteLine("A new bakery is opened at " + happybakery.address);
// Create new FictionPublisher, jolly_books, and display its turnover
FictionPublisher jolly_books = new FictionPublisher();
Console.WriteLine("Jolly Books had an unfortunate turnover of £"
+ jolly_books.turnover + " in 2020");
// Create NonFictionPublisher, silly_books, set and display its turnover
NonFictionPublisher silly_books = new NonFictionPublisher();
Console.WriteLine("Silly Books had a great turnover of £"
+ silly_books.turnover + " in 2020");
// Create new LuxuryPerfumerie, exquisite_odors, set and display its address
LuxuryPerfumerie exquisite_odors = new LuxuryPerfumerie();
exquisite_odors.address = "10 Wacky Avenue";
Console.Write("A wonderful luxury perfumerie is located at " );
exquisite_odors.DisplayAddress(); // Summon method inherited from Business class
}
}
// Create the rest of the classes
class Bakery : Business { new public string address = "2 Happy Street"; }
class WeddingBakery : Bakery { }
class Perfumerie : Business { }
class LuxuryPerfumerie : Perfumerie { }
class Publisher : Business { }
class FictionPublisher : Publisher { new public double turnover=4.55; }
class NonFictionPublisher : Publisher { /* turnover is inherited from Business class */ }
Listing 9-2A C# implementation
of Figure 9-2 demonstrating inheritance
列表 9-1 和 9-2 几乎相同。首先,在 Java 和 C# 中,类的实现方式非常相似。自然,有一些差异(见表 9-1 )。
表 9-1
清单 9-1 和 9-2 的主要区别
|元素
|
清单 9-1 (Java)
|
清单 9-2 (C#)
| | --- | --- | --- | | 类继承 | 类发布者扩展业务{ } | 类发布者:商业{ } | | 主要方法 | public static void main(String[]args) | public static void Main( ) | | 控制台输出 | System.out.println( … ) | 控制台。WriteLine( … ) | | 成员声明 | 双周转= 4.55; | 新公双成交额= 4.55; |
Python 中的图 9-2
现在来点不同的。让我们看一看 Python 中的图 9-2 可能是什么样子(参见清单 9-3 )。为了在 Python 中使用抽象类,我们需要导入一个名为 ABC 的代码模块。然后,我们让我们的基类 Business 继承这个模块。行 @abstractmethod 是所谓的 Python decorator。你可能已经猜到了,它告诉我们一个方法应该被认为是抽象的。
# import code module for working with abstract classes, ABC
from abc import ABC, abstractmethod
# define classes, starting with an abstract Business class
class Business(ABC):
def __init__(self): # set class attribute default values
self.address = "none"
self.turnover = 20000
@abstractmethod # define abstract method
def Display_Address(self):
pass
class Publisher(Business):
def Display_Address(self):
pass
class Bakery(Business):
def Display_Address(self):
pass
def __init__(self):
self.address = "2 Happy Street"
class Perfumerie(Business):
def Display_Address(self):
pass
class FictionPublisher(Publisher):
def __init__(self):
self.turnover = 4.55
class NonFictionPublisher(Publisher):
pass
class WeddingBakery(Bakery):
pass
class LuxuryPerfumerie(Perfumerie):
def __init__(self):
self.address = "10 Wacky Avenue"
def Display_Address(self): # override abstract method
print(self.address)
happybakery = Bakery() # Create new Bakery object
print("A new bakery is opened at", happybakery.address)
jolly_books = FictionPublisher() # Create new FictionPublisher object
print("Jolly Books had an unfortunate turnover of £", jolly_books.turnover, "in 2020")
silly_books = NonFictionPublisher() # Create new NonFictionPublisher object
print("Silly Books had a great turnover of £", silly_books.turnover, "in 2020")
exquisite_odors = LuxuryPerfumerie() # Create new LuxuryPerfumerie object
print("A wonderful luxury perfumerie is located at ", end = '')
exquisite_odors.Display_Address() # summon Display_Address-method
Listing 9-3A Python implementation
of Figure 9-2
UML 自行车
图 9-3
用 UML 展示聚合关系的图表
接下来,让我们探索如何用 UML 建模一个基本的踏板驱动的车辆。图 9-3 中引入了新元素。这是由空心菱形符号描绘的集合体。
聚合关联意味着一个类可以没有其他类而存在。为了拥有一辆功能齐全的自行车,我们需要所有的部件。但是,即使我们移除了其他组件,它们仍然会存在。
蟒蛇皮自行车
坚持使用 Python,让我们对图 9-3 创建一个可能的编程解释(参见清单 9-4 )。
class Frame:
# class constructor
def __init__(self):
print('Frame ready.')
weight = 10.5 # define a class variable
class Saddle:
# class constructor
def __init__(self):
print('Saddle installed.')
material = "rubber" # define a class variable
class Drivetrain:
# class constructor
def __init__(self):
print('Drivetrain installed.')
type = "one-speed" # define a class variable
# define class methods
def pedalForward(self):
print("Pedaling forward!")
def brake(self):
print("Braking!")
class Wheels:
diameter = 0
# class constructor
def __init__(self, diameter):
print('Wheels installed.')
self.diameter = diameter
class Handlebars:
# class constructor
def __init__(self):
print("Handlebars installed.")
# define class methods
def turnLeft(self):
print("Turning left..")
def turnRight(self):
print("Turning right..")
class Bicycle:
# define a class variable
make = "Helkama"
# set up class constructor & composition
def __init__(self):
self.my_Frame = Frame()
self.my_Saddle = Saddle()
self.my_Drivetrain = Drivetrain()
self.my_Wheels = Wheels(571) # pass a new diameter value
self.my_Handlebars = Handlebars()
def main(): # create main method
# create Bicycle-object, "your_bike"
your_bike = Bicycle()
print("The wheels in your " + your_bike.make + "-bike are " + str(your_bike.my_Wheels.diameter) + " mm in diameter. The frame weighs " + str(your_bike.my_Frame.weight)+" lbs.")
print("This bike has a " + your_bike.my_Drivetrain.type + " drivetrain and the saddle is made of the finest " + your_bike.my_Saddle.material+".\n")
# summon class methods
your_bike.my_Drivetrain.pedalForward()
your_bike.my_Handlebars.turnLeft()
your_bike.my_Drivetrain.pedalForward()
your_bike.my_Handlebars.turnRight()
your_bike.my_Drivetrain.brake()
if __name__ == "__main__":
main() # execute main method
Listing 9-4A Python implementation of Figure 9-3 (i.e., a UML diagram for a bicycle). The Component Inspection Unit A is not implemented here for the sake of brevity
问:你自己如何用 Java 和/或 C# 解释图 9-3?
UML 中的个人计算机
现在让我们用复合关联和聚合关联来建模。这是为了让你考虑你需要在 UML 中区分这两种类型的关联的场景。
图 9-4 代表一台典型的个人电脑。它旨在描述一个功能系统所需的所有主要组件。然而,使用复合关联(即,实心菱形符号)仅描绘了这些组件中的一些。这是因为,从理论上讲,一台个人电脑没有它们也能正常运行。这可能不是最有用的系统,但至少它可以打开并显示错误信息。
图 9-4
展示 UML 中组合关系的图表
图 9-4 中用复合关联描述的基本计算机组件:
- 电源、主板、CPU 和 RAM 芯片
用集合关联描述的不太重要的组件:
- 存储单元(例如,硬盘驱动器)、输入设备(例如,鼠标和/或键盘)和显示适配器/视频卡(集成视频芯片可以与 CPU 一起提供)
请参见表 9-2 了解聚合和复合关联之间的差异。
表 9-2
UML 中聚合关联和复合关联的主要区别
| |总计
|
复合材料
| | --- | --- | --- | | 关联强度 | 无力的 | 强烈的 | | 关系 | "有一个" | “的一部分” | | 属国 | 无论超类存在与否,子类都可以存在 | 子类需要一个超类才能存在 | | 例子 | 即使一个班级的学生毕业了,?? 大学依然存在关闭单个部门不会终结整个公司 | 没有了的房子,房间就毫无意义一个功能正常的心脏对于一个 ?? 人来说是必须的 |
UML 中的实现和依赖
实现指的是与两组元素的关系,其中一组代表一个规范,另一组代表它的*实现。*实现的实际工作取决于上下文,并没有在 UML 中严格定义(见图 9-5 )。
图 9-5
UML 中简单实现关系的三个例子
在图 9-5 的图表中,你会看到 UML 实现关系的三个简单例子。在第一种情况下,搜索算法为在线搜索引擎提供功能。同样,显卡需要显卡驱动软件才能真正显示任何图像。最后,支付事件可以通过三种可用的支付方式实现:现金、卡或支票。
接下来,我们有依赖(见图 9-6 )。这种类型的图表表示从属元素和独立元素之间的连接。用虚线和简单的箭头表示,依赖关系本质上是单向的。
图 9-6
UML 依赖的两个例子
自反联想
一个自反关联用来表示属于同一个类的实例。当一个类可以被分成许多责任时,我们可以使用这种类型的关联。
在图 9-7 中,我们有一个名为雇员的类,它与自己有一个关联,正如现在熟悉的简单线条*所描绘的。*Employee 类用来代表受人尊敬的主管和地位低下的学员;您会注意到 UML 多样性信息也被插入到这个图中。
图 9-7
UML 中自反关联的一个例子
现在,图 9-7 以两种不同的方式描绘了相同的设置。左图显示了基于角色的方法,因为我们展示了两种不同的角色(即主管和学员)。右图使用了一个命名的关联。
UML 类图:基本元素
在这一点上,回顾一下我们到目前为止遇到的 UML 元素是一个好主意(见图 9-8 )。
图 9-8
显示 UML 类图中使用的大多数基本元素的图表
UML 用例
尽管一些 UML 图类型超出了本书的范围,但是还有一种类型您应该熟悉。这是用例,它以最简单的方式展示了用户与系统的交互。这些通常是包含很少技术细节的高级图表。
我们现在将引入一些新的 UML 元素。这些是角色、系统和用例。此外,用例图用熟悉的简单线条来描述关系。请看图 9-9 中的简单用例图。
图 9-9
一个描述书籍写作过程中章节回顾的 UML 用例
你会看到图 9-9 中的两个演员,由一些可爱的简笔画代表。保持演员在本质上的明确性通常是一个好主意,就像命名一个作者作者而不是约翰或旋律羊毛索克斯。在 UML 中,发起交互的参与者被称为主要参与者。次要角色对主要角色的行为做出反应。
系统的第二个元素在 UML 中用一个简单的矩形表示;在图 9-9 的情况下,称为章审。在系统内部,我们有内部的用例,用椭圆形表示。这些代表了完成特定用例所需的不同阶段。最后,我们用熟悉的简单线条来表示参与者和用例的不同关联。
现在,图 9-9 中的图表描绘了以下事件顺序:
-
作者,主要演员,写一章。
-
完成的章节由作者和章节审阅者(次要演员)共同阅读。这由两个参与者之间的共享关联来表示。
-
章节审阅者建议对章节进行修改。
-
最后,作者将更改提交到一个章节中,以结束该场景。
更多关于用例的信息
是时候看看一个稍微复杂一点的用例来引入两个新元素了:扩展了,包含了关系(见图 9-10 )。正如 UML 类图的情况一样,我们也可以将那些有用的注释元素合并到 UML 用例中。
图 9-10
描述电子邮件服务基本功能的 UML 用例
图 9-10 描绘了一个简单的电子邮件用例场景。我们将账户所有者(即人类用户)作为主要参与者,将电子邮件服务器作为次要参与者。图 9-10 包含以下事件序列:
-
账号拥有者用密码登录。如果输入了错误的代码,则访问名为错误密码的可选用例;这个结果用箭头表示,关键字 < <延伸> > 。
-
帐户所有者也可能希望阅读一些帮助文件。这也是一个可选场景,因此分配了关键字<>
-
用例成功登录的最终结果自动导致另一个用例验证密码,如关键字 < <所示,包括> > 。二级参与者电子邮件服务器参与到密码认证过程中,因此在图 9-9 中,在所述参与者和验证密码的用例之间存在一条关联线。
-
用例验证密码导致另外三个用例:查看电子邮件、删除电子邮件和发送电子邮件。自然,次要角色电子邮件服务器也参与了所有这些,尽管只是被动的角色;帐户所有人可以随意启动所有这三个操作。
当描述用例中的关系时,注意你的箭头的方向。
UML 工具
除了纸和笔,如果你想尝试在你的计算机上绘制 UML,有很多很棒的免费选项。我们现在将回顾这些工具的一些最好的例子。
UMLet 和 UMLetino 由 UMLet 团队制作
一个免费且精彩的开源工具, UMLet 将让你用其直观的拖放式用户界面立刻绘制出时髦的类图。关联线很好地捕捉到类的边缘并保持在那里,使组织你的图变得轻而易举。
UMLet 的特点是为类图、用例、时序图和许多其他的可视化元素提供现成的选择。该计划出口到许多常见的图像文件格式,包括 PNG,GIF 和 SVG。对于那些需要一个漂亮、干净的 UML 设计工具的人来说,UMLet 绝对是必备的。本书中的所有图表都是用 UMLet 创建的。
在 UMLet 中发现的一个有趣的特性是它的半自动图表创建。通过导航到文件 ➤ 从文件或目录生成类元素,您可以设置您的 UML 图的初始阶段。不幸的是,当使用这个特性时,UMLet 在创建关联方面有一些问题。
UMLetino 是同一软件的基于浏览器的版本。它拥有离线版本的大部分功能。此外,UMLetino 还集成了 Dropbox,用于导入和导出图文件。但是,该软件仅支持 PNG 图像文件的输出。
-
从
www.umlet.com为任何支持 Java 的操作系统下载 UMLet -
在
www.umletino.com/umletino.html的任何支持 Java 的浏览器中运行 UMLetino
Diagrams.net 团队的 Diagrams.net
由专门的团队积极维护,Diagrams.net是一个通用的开源工具,有浏览器版和桌面版。后者不需要用户注册。Diagrams.net 既面向基本的 UML 工作,也面向高级用户,具有像层和“插件”这样的特性来增加功能。
以前被称为“draw.io ”,该软件在导出 UML 图(包括 Adobe PDF)时提供了强大的文件格式支持。不仅如此,通过选择文件 ➤ 导出为 ➤ 高级,你可以设置诸如 DPI(每英寸点数)和边框宽度等属性。
-
从
https://github.com/jgraph/drawio-desktop/releases下载Diagrams.net的桌面 app -
在
https://app.diagrams.net运行浏览器上的Diagrams.net
由 Paul Hensgen 和 Umbrello UML Modeler 作者编写
从 2001 年开始开发, Umbrello 对于任何类型的 UML 工作来说都是一个伟大的工具。该软件有一个直观的用户界面,以及从 UML 生成代码的选项。Umbrello 可以将你的图导出为(通常)函数式 Java、C#、Python 和许多其他编程语言;只需导航到代码 ➤ *代码生成向导,设置您的选项,然后单击下一步。*该软件还提供了出色的格式化功能,包括让您正在处理的图表可以使用您的所有操作系统字体。
不要让花哨的默认配色方案欺骗了你——对于许多类型的图表工作来说,Umbrello 是一个非常有用的应用程序。
- 从
https://umbrello.kde.org下载翁贝罗的桌面应用
最后
读完这一章,你已经获得了以下知识:
-
UML 类图和用例图的基本元素
-
如何将简单的 UML 图翻译成 Java、C# 和 Python
编后记
所以我们到了这本书的结尾。过去你可能会觉得可怕的编程术语已经变得不那么可怕了。到目前为止,变量、类和对象对您来说至少已经有点熟悉了。您知道如何用 Java、C# 和 Python 制作简单的基于文本的应用程序。或许你甚至可以在聚会上随口解释一下 Python 对缩进的严格要求。但是你的程序员之旅还远没有结束。了解了三种语言的编码基础,现在你可以开始在这个领域获得更多的经验。一个解决问题的世界在等着你。
编程可能会令人沮丧。但是,让一个讨厌的、不起作用的清单最终发挥作用有一种独特的满足感。无论你最终是否在专业环境中编码,编程在某些时候可能会成为一种建设性的嗜好。它也是治疗失眠和/或无聊的良方。
显化有意义的编码项目需要大量的意图和时间,无论它们是视频游戏还是由雇主定义的原子化编程任务。但是随着程序员的成长,没有一行是浪费的。即使是错误信息也能教会你最有价值的东西。
阿达·洛芙莱斯是勋爵和拜伦女士的私生子,被认为是世界上第一批程序员之一。她与她那个时代的几位杰出科学家一起工作,包括查尔斯·巴贝奇和迈克尔·法拉第,在某个时候,她编写了被认为是有史以来第一个计算机程序。我们让阿达来说最后一句话:
我可以把来自宇宙四分之一的光线投射到一个巨大的焦点上。我的路线是如此清晰明了,以至于想到它有多直就令人愉快。
—阿达·洛芙莱斯(1815–1852)*