Python中少有人回答的问题

  Python中少有人回答的问题
  
  The Python IAQ: Infrequently Answered Questions
  
  1 Q: 什么是”少有人回答的问题(Infrequently Answered Question)” ?#
  
  一个问题之所以很少有人回答,要么是因为很少有人知道问题的答案,要么是因为它涉及到一个晦涩而隐蔽的知识点(但可能是你关心的)。我过去认为是我在Java IAQ中发明了这个词组,但是它也出现在了以数据丰富而著称的About.com Urban Legends网站上. 关于Python的FAQ有很多,但是Python的IAQ只有这一个。(“少见问题列表”倒是有一些,其中一个是有讽刺意味的C。)
  
  2 Q: finally子句中的代码每次都会被执行,对吗?#
  
  每次?应该说,几乎每次。在try子句被执行后,无论是否出现异常,finally子句中的代码都会被执行,即使调用了sys.exit. 不过如果程序没有执行到finally子句的话,它就没有办法运行了。下面的代码中,无论choice取何值,都会发生这样的情况:
  
  Copy
  
  try: if choice: while 1: pass else: print "Please pull the plug on your computer sometime soon..." time.sleep(60 * 60 * 24 * 365 * 10000) finally: print "Finally ..."
  
  3 Q: 多态真是太棒了!无论一个列表(list)中的元素是什么类型,我都可以用sort对它排序,对吗?#
  
  不对。考虑这种情况:
  
  Copy
  
  >>> x = [1, 1j] >>> x.sort() Traceback (most recent call last): File "<pyshell#13>", line 1, in ? x.sort() TypeError: cannot compare complex numbers using <, <=, >, >=
  
  (1j是一个数,表示-1的平方根)问题在于:sort方法(在目前的实现中)使用lt方法来 比较元素的大小。而lt方法拒绝比较复数的大小(因为它们是不能排序的)。奇怪的是, complex.lt会毫不犹豫的比较复数与字符串,列表(list)和其他所有类型,除了复数。所以答案是,你可以对支持lt方法的对象序列(sequence)进行排序(当然如果将来实现变了,可能就是其它方法了)。
  
  对于问题的地一部份,“多态真棒”,我同意。但是Python有时会让使用多态变得困难,因为许多Python的类型(比如序列和数)的定义不太符合规则。
  
  4 Q: 在Python中我能写++x和x++吗?#
  
  从语法上说,++x能, x++不能。但是从实际使用来说,别这样做。这么说什么意思?
  
  可以, ++x是合法的Python语法。不过如果你是一个C++或者Java程序员的话,它表示不是你想的那个意思。加号+是一个单目前缀操作符,所以++x被解析为+(+x),它表示的(至少对于数字来说)就是x。
  
  不可以, x++本身就不是一个合法的表达式, 虽然在某些上下文时合法。比如, x++ -y被解析为x++(-(y)), 对于数字来说,等于x - y。当然,你可以创建一个类,让++x有(很有限的)意义。比如可以让这个类保存一个数字,然后使单目操作符+使它增加0.5(或者有0.5的概率增加1,如果你喜欢随机化算法),但是…
  
  不可以,那样真傻。最好还是用Python 2.0已经中加入的x += 1。 进一步的问题:为什么Python不允许 x++? 我相信原因与Python不允许在表达式中赋值一样: Python想要清晰的区分语句和表达式。如果我觉得这两者应该有所区别,那么不允许++就是最好的决定。另一方面,函数语言的鼓吹者认为语句就应该是表达式。我跟我的丹麦老乡,Bjarne Stroustrup,都这样认为。他在The Design and Evolution of C++中说:“如果是从头来设计一种语言的话,我会按照Algol68的方式,让每条语句和声明都是一个有返回值的表达式”。
  
  5 Q: 我能使用C++中对ostreams那样的语法吗,像这样么: count << x << y …?#
  
  当然可以。如果你不喜欢写”print x,y”,你可以试试这个:
  
  Copy
  
  import sys class ostream: def __init__(self, file): self.file = file def __lshift__(self, obj): self.file.write(str(obj)); return self cout = ostream(sys.stdout) cerr = ostream(sys.stderr) nl = '\n' ----------------------------------- cout << x << " " << y << nl
  
  (本文中所有的文件中的代码都在横线以上,使用这些代码的例子在横线以下。)这样你就可以使用一种不同的语法了,但是它不能给你带来一种新的输出格式,它只是把Python中以有str的格式封装了一层而已。这个做法很像Java里面的toString()格式。C++使用的是一种迥异的格式:它没有定义一组把对象转换为字符串的规则,而定义了一种把对象打印到流的规则(也许是不完整的规则,因为很多C++程序仍然使用printf)。用流来实现会更加复杂,但是它的优势在于如果你需要打印一个相当巨大的对象,就不用创建一个巨大的临时对象来做这件事。
  
  6 Q: 如果我喜欢C++的printf呢?#
  
  在Python中定义一个printf不是一个坏主意. 你可能认为printf(“%d = %s”, num, result)比print “%d = %s” % (num, result)更加自然, 因为那一对括号在更熟悉的位置(而且你不想要那个%)。更和况, 满足这个需求轻而易举:
  
  Copy
  
  def printf(format, *args): print format % args,
  
  即使是像这样的一行代码,也有几个不同实现。首先,我必需要决定是否在结尾添加逗号。为了更像C++, 我决定加上(这就意味着如果你想在结尾换行,你需要自己在格式字符串的末尾添加)。其次,结尾处会打印一个空格。如果你不想要它,使用sys.stdout.write来代替print. 最后, 把一切都变得更像C好是一件好事吗? 是,因为你需要一个打印函数(而不是一个打印语句)在只接受函数不接受语句的地方使用。比如,在lambda表达式中和map的第一个参数。事实上,这样一个函数使用起来是很趁手的,你可能想要一个没有格式化功能的:
  
  Copy
  
  def prin(x): print x,
  
  现在map(prin, seq)将打印seq中的每一个元素. 但是map(print, seq)是一个语法错误. 我曾经见过有些粗心大意的程序员(好吧, 没错, 我自己就是. 但是我知道我自己很粗心 )认为把这两个函数合二为一是个好主意, 像这样:
  
  Copy
  
  def printf(format, *args): print str(format) % args,
  
  这样 printf(42), printf(‘A multi-line\n message’)和 printf(‘%4.2f’, 42)都能工作。但是当你用了pring(‘100% guaranteed’)或者是其他任何含有%字符却并不是一个格式化指令时,”好主意”就会变成”我想啥呢?”。如果你真的实现了这么一个printf,它需要这样的注释:
  
  Copy
  
  def printf(format, *args):   """使用第一个参数作为格式字符串来格式化args, 然后打印.   如果format不是字符串, 将被str转换成字符串. 如果x可能含   有%和反斜杠字符, 你必须使用printf('%s', x)来代替   printf(x).   """ print str(format) % args,
  
  7 Q: 关于字典(Dictionary),有没有更好的语法? 我使用的键(key)都是标识符.#
  
  有!用一对引号来包括键的确是一件麻烦的事情,尤其当键是一个很长的字符串时. 起初我认为Python中加入特别的语法是有帮助的,用{a=1, b=2}来代替现在必需的{‘a’:1, ‘b’:2}。在Python 2.3中,你可以用的语法是dict(a=1, b=2, c=3, dee=4),这和我的想法一样好。在Python 2.3以前,我使用一个只有一行的函数:
  
  Copy
  
  def Dict(**dict): return dict
  
  一个读者指出,对于散列Perl也有类似的特殊符号: 在Perl中对于散列文本,你可以写(“a”, 1, “b”, 2)或者(a=>1, b=>2)。这是事实,但不是事实的全部。”man perlop”说”=>符号最多只是逗号操作符的同意词…”而且事实上当a和b是barewords时,你可以写(a, 1, b, 2)。但是,就像Dag Asheim指出的,如果你打开strict,你将会从这个写法中得到一个错误。你必须要么使用字符串,要么使用=>操作符。最后,Larry Wall已经申明,”Perl 6中将不会有bareword”。(关于perl的这以部分,我的翻译可能有很大问题,因为我根本不会Perl! –译注)
  
  8 Q: 那么,对象有没有类似的简便办法呢?#
  
  的确是有的。如果你想要创建一个对象来把数据保存在不同的域中,下面的代码就可以做到:
  
  Copy
  
  class Struct: def __init__(self, **entries): self.__dict__.update(entries) >>> globals = Struct(answer=42, linelen = 80, font='courier') >>> globals.answer 42 >>> globals.answer = 'plastics' >>> vars(globals) {'answer': 'plastics', 'font': 'courier', 'linelen': 80}
  
  从本质上说,我们在这里做的是创建一个匿名类。好吧,我知道globals的类是 Struct,但是因为我们在它里面添加了slots,就像是创建了一个新的,未命名的类(这和lambda创建匿名函数是很像的)。我讨厌再给Struct添加什么了,因为它现在很简洁,不过如果你添加下面的方法,就可以漂亮打印出它的每个结构。
  
  Copy
  
  def __repr__(self): args = ['%s=%s' % (k, repr(v)) for (k,v) in vars(self).items()] return 'Struct(%s)' % ', '.join(args) >>> globals ------------------------------------------------ Struct(answer='plastics', font='courier', linelen=80)
  
  9 Q: 这样创建新对象是很方便,但是要更新时怎么办呢?#
  
  是这样的,字典是有一个update方法的,所以当d是一个字典时,你可以用d.update(dict(a=100, b=200))。但是对象没有对应的方法,所以你只能用obj.a = 100; obj.b = 200。或者你可以定义一个函数update(x, a=100, b=200)来更新x,无论它是字典还是对象都可以:
  
  Copy
  
  import types def update(x, **entries): if type(x) == types.DictType: x.update(entries) else: x.__dict__.update(entries) return x
  
  把它用于构造函数特别漂亮:
  
  Copy
  
  def __init__(self, a, b, c, d=42, e=None, f=()): update(self, a=a, b=b, c=c, d=d, e=e, f=f)
  
  10 Q: 我能创建一个默认值为0或者[]的或者别的什么的字典么?#
  
  如果你常常要对某个东西计数,咱们会有同感: count[x] += 1比被迫用的count[x] = count.get(x, 0) + 1要优美许多。在Python 2.2以后,继承内建的dict类可以轻松的搞定这个。我把它叫做我的DefaultDict。注意copy.deepcopy的使用: 有了它,就不会让dict里面的每个key都使用同一个[]作为默认值(虽然拷贝0浪费了一点时间,不过如果你使用更新和访问比初始化更频繁的话,还算可以接受):
  
  Copy
  
  class DefaultDict(dict): """Dictionary with a default value for unknown keys.""" def __init__(self, default): self.default = default def __getitem__(self, key): if key in self: return self.get(key) return self.setdefault(key, copy.deepcopy(self.default)) -------------------------------------- >>> d = DefaultDict(0) >>> d['hello'] += 1 >>> d {'hello': 1} >>> d2 = DefaultDict([]) >>> d2[1].append('hello') >>> d2[2].append('world') >>> d2[1].append('there') >>> d2 {1: ['hello', 'there'], 2: ['world']} def bigrams(words): "Counts of word pairs, in a dict of dicts." d = DefaultDict(DefaultDict(0)) for (w1, w2) in zip([None] + words, words + [None]): d[w1][w2] += 1 return d >>> bigrams('i am what i am'.split()) {None: {'i': 1}, 'i': {'am': 2}, 'what': {'i': 1}, 'am': {None: 1, 'what': 1}}
  
  值得注意的是,如果没有DefaultDict,bigram例子程序中的dw1 += 1就大概应该象这样:
  
  Copy
  
  d.setdefault(w1,{}).setdefault(w2, 0); d[w1][w2] += 1
  
  11 Q: 嘿,你能用0.0007KB或者更少的代码做一个矩阵变换么?#
  
  我还以为你永远不会问呢. 如果你用序列组成的序列来表示矩阵的话,用zip就可以搞定了:
  
  Copy
  
  >>> m = [(1,2,3), (4,5,6)] >>> zip(*m) [(1, 4), (2, 5), (3, 6)]
  
  要想理解它,你需要知道f(m)就像于apply(f,m)。你问的是一个古老的Lisp问题,在Python中它的等价答案是map(None, m),但是用Chih-Chung Chang建议的zip版代码会更短小。你可能认为这些代码唯一的用处就是在Letterman的Stupid Programmer’sTricks(David Michael Letterman, 美国晚间脱口秀主持人,他主持的一个著名节目是Stupid Pet Tricks——译注)中露脸,但是有一天我遇到了这个问题:有一个数据库行的列表,每一行中都是排序过的值的列表。找出每一列中不重复的值,组成一个列表。我的答案是:
  
  Copy
  
  possible_values = map(unique, zip(*db))
  
  12 Q: 用f(m)的技巧很酷. 有没有同样的语法可以用在方法调用上, 比如x.f(y)?#
  
  这个问题暴露一个错误的概念。根本就没有方法调用的语法!Python语法中,有函数调用的,也有从对象中取得域的,也有绑定方法的。把这三者结合起来,就让x.f(y)看起来像一块单独的语法,而事实上,它等价于(x.f)(y),后者又等价于(getattr(x, ‘f’))(y)。我猜你可能不相信我,来看:
  
  Copy
  
  class X: def f(self, y): return 2 * y -------------------------------------- >>> x = X() >>> x.f <bound method X.f of <__main__.X instance at 0x009C7DB0>> >>> y = 21 >>> x.f(y) 42 >>> (x.f)(y) 42 >>> (getattr(x, 'f'))(y) 42 >>> xf = x.f >>> xf(y) 42 >>> map(x.f, range(5)) [0, 2, 4, 6, 8]
  
  所以这个问题的答案是:你可以在方法调用中使用y或*y(或者其他任何你可以放在函数调用中的),因为方法调用就是函数调用。
  
  13 Q: 你能用用0行代码实现Python的抽象类吗? 4行呢?#
  
  Java中有一个abstract关键词。你可以用它来定义一个只能继承不能被实例化的抽象类,该类中所有的抽象方法都需要你来实现。很少有人知道在Python中,你可以用几乎一样的方式使用abstract。不同的是,当你想要调用一个没有实现的方式时,你得到的是一个运行时错误而不是编译错误。比较下面的代码:
  
  Copy
  
  ## Python class MyAbstractClass: def method1(self): abstract class MyClass(MyAbstractClass): pass -------------------------------------- >>> MyClass().method1() Traceback (most recent call last): ... NameError: name 'abstract' is not defined
  
  ==============================================
  
  Copy
  
  /* Java */ public abstract class MyAbstractClass { public abstract void method1(); } class MyClass extends MyAbstractClass {} ---------------------------------------------- % javac MyAbstractClass MyAbstractClass.java:5: class MyClass must be declared abstract. It does not define void method1() from class MyAbstractClass.
  
  别花太多时间在Python语言参考手册里面寻找abstract关键词,它根本就不在那里。我把它加入了Python语言中,并且最美妙的是,它的实现用了0行代码! 当你调用methord1,你会得到一个NameError错误,因为不存在abstract变量。(你也许会说这是欺骗,如果有人定义一个变量叫做abstract它就没有效果了) 但是如果代码中依赖的一个变量被人重定义的话,任何程序都难逃错误的命运。这里唯一的区别就是我们依赖的是没有定义的变量。
  
  如果你愿意写abstract()替代abstract,那么你可以定义一个函数抛出一个更有意义的NotImplementedError以取代NameError。(同样,如果有人重定义abstract为零参数函数以外的任何东西,你还是会得到一个错误信息。)为了让abstract的错误信息看起来舒服一点,只需去函数调用栈(stack frame)中看看谁是这个讨厌的调用者:
  
  Copy
  
  def abstract(): import inspect caller = inspect.getouterframes(inspect.currentframe())[1][3] raise NotImplementedError(caller + ' must be implemented in subclass') ---------------------------------------------- >>> MyDerivedClass().method1() Traceback (most recent call last): ... NotImplementedError: method1 must be implemented in subclass
  
  14 Q: 在Python中我怎么实现枚举类型呢?#
  
  这个问题没有一个答案,因为在Python中有好几个答案,取决于你对枚举的期望。如果你只是想有几个变量,每个都有不同的整数值,你可以这样:
  
  Copy
  
  red, green, blue = range(3)
  
  缺点是当你想在左边添加一个新的变量,需要同时增加右边的整数。不过这不算太坏,因为当你忘记的时候Python会抛出一个错误。如果你把枚举隔离在类中可能更干凈一点:
  
  Copy
  
  class Colors: red, green, blue = range(3)
  
  现在Colors.red会得到0, 并且dir(Colors)可能也能派上用场(虽然你还需要忽略doc和module两项). 如果你想完全控制每个枚举变量的值, 可以使用好几个问题以前的Struct函数, 就像下面:
  
  Copy
  
  Enum = Struct Colors = Enum(red=0, green=100, blue=200)
  
  尽管这些简单的办法通常已经够了,可有人还想要更多。 在python.org,ASPN和faqts上都有枚举类型的实现。下面是我的版本,它(几乎)涵盖所有人的需要,并且仍然保持合理的简洁(一共44行,其中有22行代码):
  
    public class LoggingService : ServiceControl
  
  private const string _logFileLocation = @"C:\temp\servicelog.txt";
  
  private void Log(www.qjljdgt.cn  string logMessage)
  
  Directory.CreateDirectory(Path.GetDirectoryName(www.baiyytwg.com/_logFileLocation));
  
  File.AppendAllText(www.bsylept.com_logFileLocation,
  
  DateTime.UtcNow.ToString() + " : " + logMessage + Environment.NewLine)
  
  public bool Start(HostControl hostControl)
  
  Log("Starting"www.yuanyangyuL.com);
  
  return true;

  public bool Stop(HostControl hostControl)
 
  Log("Stopping"www.xcdeyiju.com);
  
  return true
  
  代码看起来是不是很简单?
  
  这里我们的服务类继承了ServiceControl类(实际上并不需要,但是这可以为我们的工作打下良好的基础)。我们必须实现服务开始和服务结束两个方法,并且像以前一样记录日志。
  
  在Program.cs文件的Main方法中,我们要写的代码也非常的简单。我们可以直接使用HostFactory.Run方法来启动服务。
  
  Copy
  
  static void Main(string[] args)
  
  HostFactory.Run(www.xgjrfwsc.cn => www.jintianxuesha.com.Service<LoggingService>());
  
  这看起来真是太简单了。但这并不是HostFactory类的唯一功能。这里我们还可以设置
  
  服务的名称
  
  服务是否自动启动
  
  服务崩溃之后的重启时间
  
  Copy
  
  static void Main(string[] args)
  
  {
  
  HostFactory.Run(x =>
  
  {
  
  x.Service<LoggingService>();
  
  x.EnableServiceRecovery(r => r.RestartService(TimeSpan.FromSeconds(10)));
  
  x.SetServiceName("TestService");
  
  x.StartAutomatically();
  
  }
  
  );
  
  }
  
  这里其实能说的东西很多,但是我建议你还是自己去看看Topshelf的文档,学习一下其他的配置选项。基本上你能使用Windows命令行完成的所有操作,都可以使用代码来设置: https://topshelf.readthedocs.io/en/latest/configuration/config_api.html
  
  部署服务#
  
  和之前一样,我们需要针对不同的Windows环境发布我们的服务。在Windows命令提示符下,我们可以在项目目录中执行以下命令:
  
  Copy
  
  dotnet publish -r win-x64 -c Release
  
  现在我们就可以查看一下bin\Release\netcoreappX.X\win-x64\publish目录,我们会发现一个编译好的exe,下面我们就会使用这个文件来安装服务。
  
  在上一篇文章中,我们是使用SC命令来安装Windows服务的。使用Topshelf我们就不需要这么做了,Topshelf提供了自己的命令行参数来安装服务。基本上使用代码能完成的配置,都可以使用命令行来完成。
  
  你可以查看相关的文档:
  
  <http://docs.topshelf-project.com/en/latest/overview/commandline.html>
  
  Copy
  
  WindowsServiceExample.exe install
  
  这里WindowsServiceExample.exe是我发布之后的exe文件。运行以上命令之后,服务应该就正常安装了!这里有一个小问题,我经常发现,即使配置了服务自动启动,但是服务安装之后,并不会触发启动操作。所有在服务安装之后,我们还需要通过以下命令来启动服务。
  
  Copy
  
  WindowsServiceExample.exe start
  
  在生产环境部署的时候,我的经验是在安装服务之后,等待10秒钟,再启动服务。
  
  调试服务#
  
  当我们是使用微软推荐方式的时候,我们会遇到了调试困难的问题。大多数情况下,无论是否在服务内部运行,我们都不得不使用命令行标志、#IF DEBUG指令或者配置值来实现调试。然后使用Hack的方式在控制台程序中模拟服务。
  
  因此,这就是为什么我们要使用Topshelf。
  
  如果我们的服务代码已经在Visual Studio中打开了,我们就可以直接启动调试。Topshelf会模拟在控制台中启动服务。我们应该能在控制台中看到以下的消息。
  
  Copy
  
  The TestService service is now running, press Control+C to exit.
  
  这确实符合了我们的需求。它启动了我们的服务,并像真正的Windows服务一样在后台运行。我们可以像往常一样设置断点,基本上它遵循的流程和正常安装的服务一样。
  
  我们可以通过ctrl+c, 来关闭我们的应用,但是在运行服务执行Stop方法之前,它是不能被关闭的,这使我们可以调试服务的关闭流程。与调试指令和配置标志相比,这要容易的多。
  
  这里需要注意一个问题。如果你收到的以下内容的消息:
  
  Copy
  
  The TestService service is running and must be stopped before running via the console
  
  这意味着你尝试调试的服务实际上已经作为Windows服务被安装在系统中了,你需要停止(不需要卸载)这个正在运行的服务,才可以正常调试。
  
  后续#
  
  在上一篇中,有读者指出.NET Core中实际上已经提供了一种完全不同的方式运行Windows服务。它的实质是利用了ASP.NET Core中引入的“托管服务”模型,并允许它们作为Windows服务来运行,这真的是非常的棒。
  
  class Enum: """创建一个可的枚举类型, 然后给他添加变量/值对. 构造函数   和.ints(names)方法接受变量名的列表并且将连续的整数赋予他们. 方法.strs(names)将每个变量名赋给它自己(就是说变量'v'有值'v'). 方法.vals(a=99, b=200) 让你可以给任何变量赋任何值. "变量名列表"也可以是一个字符串, 它将被.split()分开. 方法.end()返回最大整数值加1,比如: opcode = Enum("add sub load store").vals(illegal=255).""" def __init__(self, names=[]): self.ints(names) def set(self, var, val): """Set var to the value val in the enum.""" if var in vars(self).keys(): raise AttributeError("duplicate var in enum") if val in vars(self).values(): raise ValueError("duplicate value in enum") vars(self)[var] = val return self def strs(self, names): """Set each of the names to itself (as a string) in the enum.""" for var in self._parse(names): self.set(var, var) return self def ints(self, names): """Set each of the names to the next highest int in the enum.""" for var in self._parse(names): self.set(var, self.end()) return self def vals(self, **entries): """Set each of var=val pairs in the enum.""" for (var, val) in entries.items(): self.set(var, val) return self def end(self): """One more than the largest int value in the enum, or 0 if none.""" try: return max([x for x in vars(self).values() if type(x)==type(0)]) + 1 except ValueError: return 0 def _parse(self, names): ### If names is a string, parse it as a list of names. if type(names) == type(""): return names.split() else: return names
  
  下面是使用它的例子:
  
  Copy
  
  >>> opcodes = Enum("add sub load store").vals(illegal=255) >>> opcodes.add 0 >>> opcodes.illegal 255 >>> opcodes.end() 256 >>> dir(opcodes) ['add', 'illegal', 'load', 'store', 'sub'] >>> vars(opcodes) {'store': 3, 'sub': 1, 'add': 0, 'illegal': 255, 'load': 2} >>> vars(opcodes).values() [3, 1, 0, 255, 2]
  
  注意这些方法都是级联(cascaded)的,在构造函数后你可以把.strs, .ints和.vals组合在一行代码中。还要注意的dir和vals辅助使用,它们不会被任何东西干扰, 除了你定义的变量。为了遍历所有的枚举值,你可以使用for x in vars(opcodes).values()。还有就是,如果你愿意,可以使用非整数值来赋给枚举变量。使用.strs和.vals方法就行了。最后,注意重复变量名和值都是一种错误。有时你可能想有一个重复的值(比如为了创建别名)。你可以删掉抛出ValueError的那行,或者像这样用:vars(opcodes)[‘first_op’] = 0。这里我最不喜欢的是很有可能把vals和value搞混。也许我可以给vals想一个更好的名字。
  
  15 Q: 为什么Python中没有”集合(Set)”类型?#
  
  当这个问题第一个发布在这里的时候还没有, 程序员们通常用字典来代替它. 但是在Python 2.4中有一个很好的set类型。
  
  16 Q: 我能用布尔类型吗?#
  
  当这个问题第一次发布在这里时,Python中还没有布尔类型。现在嘛,Python 2.3以后都内建有一个bool类型。
  
  17 Q: Python中有能与(test?result:alternative)等价的操作吗?#
  
  Java和C++都有三目运算符(test?result:alternative)。Python一直拒绝它,但在将来的Python 2.5中,将允许(result if test else alternative)形式的表达式。这样的结果是破坏了Python中表达式和语句清楚的区别,不过它是对许多人要求的妥协。
  
  在Python 2.5到来前,你怎么办?这里有几个选择:
  
  你可以试试[alternaticve, result][test]. 注意如果alternative和result中有递归调用或者昂贵的操作的话, 这个方法不太好, 因为它们两个都会被求值. 如果test可以返回一个非布尔值, 那就下面这个
  
  [result, alternative][not test]. 这两个的可读性都很好.
  
  test and result or alternative 有人很习惯这样,有人却觉得它令人胡涂. 它只在能确认result非假后使用.
  
  (test and [result] or [alternative])[0] 避免了上面那个限制.
  
  [lambda: result, lambda: alternative][not not test]()摆脱了上面所有的限制(除了可读性), 但别跟人家说是我告诉你这样做的. 你甚至可以把它封装在一个函数里面. 公认的命名规范是, 对于模仿关键词的变量, 在后面跟一个下划线. 所以我们有:
  
  if_(test, result, lambda: alternative)
  
  这里我们定义:
  
  Copy
  
  def if_(test, result, alternative=None): "If test is true, 'do' result, else alternative. 'Do' means call if callable." if test: if callable(result): result = result() return result else: if callable(alternative): alternative = alternative() return alternative -------------------------------------------------- >>> fact = lambda n: if_(n <= 1, 1, lambda: n * fact(n-1)) >>> fact(6) 720
  
  现在假定你因为某种原因, 与”if(test, …”的语法相比, 就是更喜欢”if(test) …”(并且, 你从来不想摆脱alternative那个部分). 你可以试试这个:
  
  Copy
  
  def _if(test): return lambda alternative: \ lambda result: \ [delay(result), delay(alternative)][not not test]() def delay(f): if callable(f): return f else: return lambda: f >>> fact = lambda n: _if (n <= 1) (1) (lambda: n * fact(n-1)) >>> fact(100) 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000L
  
  If u cn rd ths, u cn gt a jb in fncnl prg (if thr wr any)。(这个就不翻了吧:) )
  
  18 Q: 还有其他主要类型是Python缺少的吗?#
  
  关于Python,有一件很爽的事情就是你可以使用数字,字符串,列表,和字典(现在还有集合和布尔)就能走很远。但是还有几个主要类型是缺少的. 对我来说,最重要的是一个可变的字符串。一次又一次的使用str += x 是很慢的,而维护字符组成的列表(或者子字符串的列表)意味着你放弃了一些很棒的字符串函数。一个可能的解决是array.array(‘c’)。另一个是UserString.MutableString,尽管它本来的目的是用于教学而不是实践。第三个是mmap模块, 第四是cStringIO. 这些方法都不完美,不过加在一起也提供了足够的选择。最后,我发现我经常需要一个某种顺序的队列。标准库中有一个 Queue module,但它是专用于线程的队列。因为这里有太多选项了,所以我就不为了实现一个标准队列的去游说了。不过呢,我将提供我实现的几种队列,FIFO,LIFO和优先队列:
  
  Copy
  
  """ This module provides three types of queues, with these constructors: Stack([items]) -- Create a Last In First Out queue, implemented as a list Queue([items]) -- Create a First In First Out queue PriorityQueue([items]) -- Create a queue where minimum item (by <) is first Here [items] is an optional list of initial items; if omitted, queue is empty. Each type supports the following methods and functions: len(q) -- number of items in q (also q.__len__()) q.append(item) -- add an item to the queue q.extend(items) -- add each of the items to the queue q.pop() -- remove and return the "first" item from the queue """ def Stack(items=None): "A stack, or last-in-first-out queue, is implemented as a list." return items or [] class Queue: "A first-in-first-out queue." def __init__(self, items=None): self.start = 0; self.A = items or [] def __len__(self): return len(self.A) - self.start def append(self, item): self.A.append(item) def extend(self, items): self.A.extend(items) def pop(self): A = self.A item = A[self.start] self.start += 1 if self.start > 100 and self.start > len(A)/2: del A[:self.start] self.start = 0 return item class PriorityQueue: "A queue in which the minimum element (as determined by cmp) is first." def __init__(self, items=None, cmp=operator.lt): self.A = []; self.cmp = cmp; if items: self.extend(items) def __len__(self): return len(self.A) def append(self, item): A, cmp = self.A, self.cmp A.append(item) i = len(A) - 1 while i > 0 and cmp(item, A[i//2]): A[i], i = A[i//2], i//2 A[i] = item def extend(self, items): for item in items: self.append(item) def pop(self): A = self.A if len(A) == 1: return A.pop() e = A[0] A[0] = A.pop() self.heapify(0) return e def heapify(self, i): "Assumes A is an array whose left and right children are heaps," "move A[i] into the correct position. See CLR&S p. 130" A, cmp = self.A, self.cmp left, right, N = 2*i + 1, 2*i + 2, len(A)-1 if left <= N and cmp(A[left], A[i]): smallest = left else: smallest = i if right <= N and cmp(A[right], A[smallest]): smallest = right if smallest != i: A[i], A[smallest] = A[smallest], A[i] self.heapify(smallest)
  
  注意一个技巧”items or []”,下面这样做是非常错误的
  
  Copy
  
  def Stack(items=[]): return items
  
  这是想说明默认值是一个空的列表。如果我们这样作了,那么不同的堆栈将会共享一个列表。通过使默认值为None(一个有效输入之外的false值),我们可以安排每个实例得到它自己的新列表。可能拒绝使用这个技巧的理由,在下面例子中,一个用户这样用
  
  Copy
  
  s = Stack(items)
  
  他可能觉得之后的s和items应该是相同的。但这是只会在发生在当items非空的时候。我认为这样的反对理由是不太严重的,因为这里并没有什么明确的承诺。(事实上,一个用户也可能期望items保持不变,这只在item为空时候成立)。
  
  19 Q: 在Python里面怎么实现Singleton模式?#
  
  我假定你的意思是:你希望一个类只可以被实例化一次,然后当你再次实例化时抛出一个异常。我知道的最简单的办法是定义一个函数施行这个想法,然后在你的类构造函数里面调用这个函数:
  
  Copy
  
  def singleton(object, instantiated=[]): "Raise an exception if an object of this class has been instantiated before." assert object.__class__ not in instantiated, \ "%s is a Singleton class but is already instantiated" % object.__class__ instantiated.append(object.__class__) class YourClass: "A singleton class to do something ..." def __init__(self, args): singleton(self) ...
  
  你也可以跟metaclass打交道,这样你可以写出class YourClass(Singletion),但是为什么自找麻烦呢?在”四人帮”把理论带给我们以前,”singleton”(没有那个公式化的名字)只是一个简单的想法,刚好与一行简单代码相配,而不是一套信仰.
  
  20 Q: 没有”news”是好消息吗?#
  
  我假设你的意思是Python没有new关键词。的确是的。在C++中,new用来标记堆的分配而不是栈的。这时,这个关键词是有用的。在Java中,所有的对象都是在堆上分配的,所以new没有真正的意义。它只是作为一个区别构造函数和其他静态方法的提醒。但是这个区别可能对Java弊大于利,因为它是低层次的,它强迫实现代码过早决定那些真正应该延后的东西。我想Python作出了正确的选择,保持构造函数和一个普通函数调用使用相同的语法。
  
  比如说,在有bool类出现之前,我们曾经想实现一个。为了跟内建的有所区别的,我们就叫它Bool。假设我们想实现这样的想法:Bool类型只有一个true和一个false对象。一个办法是把类名从Bool改为_Bool(这样它不会被导出),然后定义一个函数Bool:
  
  Copy
  
  def Bool(val): if val: return true else: return false true, false = _Bool(1), _Bool(0)
  
  这就让函数Bool变成_Bool对象的一个工厂(诚然是一个小得少见的工厂)。要点在于调用Bool(1)的程序员不应该知道或者关心返回的对象是一个新的还是回收的(至少对于不可变对象是这样)。Python语法允许隐藏这个区别,但是Java语法不行。
  
  在一些著作中这里有点混淆。有些人使用术语”Singleton Pattern”称呼这样的工厂,因为这里对构造函数的每个不同的参数有一个单独的对象。和大多数人一样,我赞同前一个问题中我下的定义。这个模式也可以封装一个类型。我们可以叫它”CachedFactory”。这个想法来源于当你写下:
  
  Copy
  
  class Bool: ... ## see here for Bool's definition Bool = CachedFactory(Bool)
  
  然后当你第一次调用Bool(1),参数列表(1,),得到原来的Bool类的代理。但是任何后续的对Bool(1)调用将返回第一个对象,它是被保存在缓存中:
  
  Copy
  
  class CachedFactory: def __init__(self, klass): self.cache = {} self.klass = klass def __call__(self, *args): if self.cache.has_key(args): return self.cache[args] else: object = self.cache[args] = self.klass(*args) return object
  
  需要注意的一件事情是,类和构造函数没有任何其余的东西。这个模式将适用于所有可调用的对象。当扩展到普通的函数,它被称作”Memoization Pattern”。实现代码是一样的,只是名字变了:
  
  Copy
  
  class Memoize: def __init__(self, fn): self.cache = {} self.fn = fn def __call__(self, *args): if self.cache.has_key(args): return self.cache[args] else: object = self.cache[args] = self.fn(*args) return object
  
  现在你可以写下fact = Memoize(fact),现在阶乘运算的时间复杂度是分摊到每次调用的O(1),而不是O(n)。
  
  21 Q: 我能有一个像shell里面一样的历史记录吗?#
  
  能。如果你要是这个么?
  
  Copy
  
  >>> from shellhistory import h h[2] >>> 7*8 56 h[3] >>> 9*9 81 h[4] >>> h[2] 56 h[5] >>> 'hello' + ' world' 'hello world' h[6] >>> h [None, 9, 56, 81, 56, 'hello world'] h[7] >>> h[5] * 2 'hello worldhello world' h[8] >>> h[7] is _ is h[-1] 1
  
  这是怎么办到的?变量sys.ps1是系统提示符,默认值是字符串’>>>’,但是你可以设置成其它任何东西。如果你设置了一个非字符串对象,这个对象的str方法将被调用。所以我们将创建这么一个对象,它的字符串方法把最近的结果(变量)添加到一个叫h(代表history)的列表中, 然后返回一个包含列表长度,接着是’>>>’的提示字符串。至少原来计划是这样。结果是(在IDLE 2.2的Windows实现中),sys.ps1.str被调用了三次,而不是提示符被打印前的一次。别问我为什么。为了解决这个问题,只有当不是历史列表中最后一个元素时,我才加入它。而且我也不自讨麻烦的把None加入历史列表中了,因为它不会被Python的交互循环显示。我还排除了向h自己中添加h,因为这样的环形结构可以能会带来打印和比较时的麻烦。另一个复杂因素是Python解释器实际上是尝试打印’\n’ + sys.ps1,(它本来应该单独的打印’\n’,或者打印’\n’ + str(sys.ps1))这就意味着sys.ps1也需要一个radd方法. 最后,如果Python session中(或者是在.python启动文件中)一开始的输入是导入我的第一版模块,它将会失败。在检查了一番之后,我发现这是因为直到第一个表达式被求值以后,变量才被绑定。所以我捕获了未绑定的异常。然后就有:
  
  Copy
  
  import sys h = [None] class Prompt: "Create a prompt that stores results (i.e. _) in the array h." def __init__(self, str='h[%d] >>> '): self.str = str; def __str__(self): try: if _ not in [h[-1], None, h]: h.append(_); except NameError: pass return self.str % len(h); def __radd__(self, other): return str(other) + str(self) sys.ps1 = Prompt()
  
  22 Q: 怎么得到我的函数的运行时间?#
  
  下面是一个简单的答案:
  
  Copy
  
  def timer(fn, *args): "Time the application of fn to args. Return (result, seconds)." import time start = time.clock() return fn(*args), time.clock() - start >>>timer(max, range(1e6)) (999999, 0.4921875)
  
  在我的utils module里还有一个更复杂的答案。
  
  23 Q: 我的.python启动文件是什么样子的?#
  
  现在它是看起来像这样,但是它已经改变了很多了:
  
  Copy
  
  from __future__ import nested_scopes import sys, os, string, time from utils import * ################ Interactive Prompt and Debugging ################ try: import readline except ImportError: print "Module readline not available." else: import rlcompleter readline.parse_and_bind("tab: complete") h = [None] class Prompt: def __init__(self, str='h[%d] >>> '): self.str = str; def __str__(self): try: if _ not in [h[-1], None, h]: h.append(_); except NameError: pass return self.str % len(h); def __radd__(self, other): return str(other) + str(self) if os.environ.get('TERM') in [ 'xterm', 'vt100' ]: sys.ps1 = Prompt('\001\033[0:1;31m\002h[%d] >>> \001\033[0m\002') else: sys.ps1 = Prompt() sys.ps2 = ''

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值