<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>润物无声 &#187; Python</title>
	<atom:link href="http://blog.zhourunsheng.com/tag/python/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.zhourunsheng.com</link>
	<description>天空一朵雨做的云</description>
	<lastBuildDate>Sat, 08 May 2021 05:17:21 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=4.1.41</generator>
	<item>
		<title>Python开发之性能优化</title>
		<link>http://blog.zhourunsheng.com/2013/08/python%e5%bc%80%e5%8f%91%e4%b9%8b%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/python%e5%bc%80%e5%8f%91%e4%b9%8b%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96/#comments</comments>
		<pubDate>Sat, 31 Aug 2013 02:45:21 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1834</guid>
		<description><![CDATA[<p>不论什么语言的开发，都会涉及到下面的几个问题，代码的运行时间，性能出现的瓶颈，内存的占用情况，是否有内存的泄露 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/python%e5%bc%80%e5%8f%91%e4%b9%8b%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96/">Python开发之性能优化</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>不论什么语言的开发，都会涉及到下面的几个问题，代码的运行时间，性能出现的瓶颈，内存的占用情况，是否有内存的泄露等等，同样Python也不例外，本文介绍了几个优秀的Python工具，可以实现如上所述问题的检测机制，通过动态运行跟踪代码的执行情况，从而发现问题点。</p>
<p>While it’s not always the case that every Python program you write will require a rigorous performance analysis, it is reassuring to know that there are a wide variety of tools in Python’s ecosystem that one can turn to when the time arises.</p>
<p>Analyzing a program’s performance boils down to answering 4 basic questions:</p>
<ol>
<li>How fast is it running?</li>
<li>Where are the speed bottlenecks?</li>
<li>How much memory is it using?</li>
<li>Where is memory leaking?</li>
</ol>
<p>Below, we’ll dive into the details of answering these questions using some awesome tools.<span id="more-1834"></span></p>
<h3 id="coarse-grain-timing-with-time">Coarse grain timing with time（代码执行时间）</h3>
<p>Let’s begin by using a quick and dirty method of timing our code: the good old unix utility<em>time</em>.</p>
<pre>$ time python yourprogram.py

real    0m1.028s
user    0m0.001s
sys     0m0.003s</pre>
<p>The meaning between the three output measurements are detailed in this <a href="http://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1">stackoverflow article</a>, but in short</p>
<ul>
<li>real - refers to the actual elasped time</li>
<li>user - refers to the amount of cpu time spent outside of kernel</li>
<li>sys - refers to the amount of cpu time spent inside kernel specific functions</li>
</ul>
<p>You can get a sense of how many cpu cycles your program used up regardless of other programs running on the system by adding together the <em>sys</em> and <em>user</em> times.</p>
<p>If the sum of <em>sys</em> and <em>user</em> times is much less than <em>real</em> time, then you can guess that most your program’s performance issues are most likely related to IO waits.</p>
<h3 id="fine-grain-timing-with-a-timing-context-manager">Fine grain timing with a timing context manager</h3>
<p>Our next technique involves direct instrumentation of the code to get access to finer grain timing information. Here’s a small snippet I’ve found invaluable for making ad-hoc timing measurements:</p>
<p><em>timer.py</em></p>
<pre>import time

class Timer(object):
    def __init__(self, verbose=False):
        self.verbose = verbose

    def __enter__(self):
        self.start = time.time()
        return self

    def __exit__(self, *args):
        self.end = time.time()
        self.secs = self.end - self.start
        self.msecs = self.secs * 1000  # millisecs
        if self.verbose:
            print 'elapsed time: %f ms' % self.msecs</pre>
<p>In order to use it, wrap blocks of code that you want to time with Python’s <em>with</em> keyword and this <em>Timer</em> context manager. It will take care of starting the timer when your code block begins execution and stopping the timer when your code block ends.</p>
<p>Here’s an example use of the snippet:</p>
<pre>from timer import Timer
from redis import Redis
rdb = Redis()

with Timer() as t:
    rdb.lpush("foo", "bar")
print "=&gt; elasped lpush: %s s" % t.secs

with Timer as t:
    rdb.lpop("foo")
print "=&gt; elasped lpop: %s s" % t.secs</pre>
<p>I’ll often log the outputs of these timers to a file in order to see how my program’s performance evolves over time.</p>
<h3 id="line-by-line-timing-and-execution-frequency-with-a-profiler">Line-by-line timing and execution frequency with a profiler（代码行运行时间分析）</h3>
<p>Robert Kern has a nice project called <a href="http://packages.python.org/line_profiler/">line_profiler</a> which I often use to see how fast and how often each line of code is running in my scripts.</p>
<p>To use it, you’ll need to install the python package via pip:</p>
<pre>$ pip install line_profiler</pre>
<p>Once installed you’ll have access to a new module called “line_profiler” as well as an executable script “kernprof.py”.</p>
<p>To use this tool, first modify your source code by decorating the function you want to measure with the <em>@profile</em> decorator. Don’t worry, you don’t have to import anyting in order to use this decorator. The <em>kernprof.py</em> script automatically injects it into your script’s runtime during execution.</p>
<p><em>primes.py</em></p>
<pre>@profile
def primes(n): 
    if n==2:
        return [2]
    elif n&lt;2:
        return []
    s=range(3,n+1,2)
    mroot = n ** 0.5
    half=(n+1)/2-1
    i=0
    m=3
    while m &lt;= mroot:
        if s[i]:
            j=(m*m-3)/2
            s[j]=0
            while j&lt;half:
                s[j]=0
                j+=m
        i=i+1
        m=2*i+3
    return [2]+[x for x in s if x]
primes(100)</pre>
<p>Once you’ve gotten your code setup with the <em>@profile</em> decorator, use <em>kernprof.py</em> to run your script.</p>
<pre>$ kernprof.py -l -v fib.py</pre>
<p>The <em>-l</em> option tells kernprof to inject the <em>@profile</em> decorator into your script’s builtins, and <em>-v</em> tells kernprof to display timing information once you’re script finishes. Here’s one the output should look like for the above script:</p>
<pre>Wrote profile results to primes.py.lprof
Timer unit: 1e-06 s

File: primes.py
Function: primes at line 2
Total time: 0.00019 s

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     2                                           @profile
     3                                           def primes(n): 
     4         1            2      2.0      1.1      if n==2:
     5                                                   return [2]
     6         1            1      1.0      0.5      elif n&lt;2:
     7                                                   return []
     8         1            4      4.0      2.1      s=range(3,n+1,2)
     9         1           10     10.0      5.3      mroot = n ** 0.5
    10         1            2      2.0      1.1      half=(n+1)/2-1
    11         1            1      1.0      0.5      i=0
    12         1            1      1.0      0.5      m=3
    13         5            7      1.4      3.7      while m &lt;= mroot:
    14         4            4      1.0      2.1          if s[i]:
    15         3            4      1.3      2.1              j=(m*m-3)/2
    16         3            4      1.3      2.1              s[j]=0
    17        31           31      1.0     16.3              while j&lt;half:
    18        28           28      1.0     14.7                  s[j]=0
    19        28           29      1.0     15.3                  j+=m
    20         4            4      1.0      2.1          i=i+1
    21         4            4      1.0      2.1          m=2*i+3
    22        50           54      1.1     28.4      return [2]+[x for x in s if x]</pre>
<p>Look for lines with a high amount of hits or a high time interval. These are the areas where optimizations can yield the greatest improvements.</p>
<h3 id="how-much-memory-does-it-use">How much memory does it use?（内存占用）</h3>
<p>Now that we have a good grasp on timing our code, let’s move on to figuring out how much memory our programs are using. Fortunately for us, Fabian Pedregosa has implemented a nice<a href="https://github.com/fabianp/memory_profiler">memory profiler</a> modeled after Robert Kern’s line_profiler.</p>
<p>First install it via pip:</p>
<pre>$ pip install -U memory_profiler
$ pip install psutil</pre>
<p>(Installing the <em>psutil</em> package here is recommended because it greatly improves the performance of the memory_profiler).</p>
<p>Like line_profiler, memory_profiler requires that you decorate your function of interest with an<em>@profile</em> decorator like so:</p>
<pre>@profile
def primes(n): 
    ...
    ...</pre>
<p>To see how much memory your function uses run the following:</p>
<pre>$ python -m memory_profiler primes.py</pre>
<p>You should see output that looks like this once your program exits:</p>
<pre>Filename: primes.py

Line #    Mem usage  Increment   Line Contents
==============================================
     2                           @profile
     3    7.9219 MB  0.0000 MB   def primes(n): 
     4    7.9219 MB  0.0000 MB       if n==2:
     5                                   return [2]
     6    7.9219 MB  0.0000 MB       elif n&lt;2:
     7                                   return []
     8    7.9219 MB  0.0000 MB       s=range(3,n+1,2)
     9    7.9258 MB  0.0039 MB       mroot = n ** 0.5
    10    7.9258 MB  0.0000 MB       half=(n+1)/2-1
    11    7.9258 MB  0.0000 MB       i=0
    12    7.9258 MB  0.0000 MB       m=3
    13    7.9297 MB  0.0039 MB       while m &lt;= mroot:
    14    7.9297 MB  0.0000 MB           if s[i]:
    15    7.9297 MB  0.0000 MB               j=(m*m-3)/2
    16    7.9258 MB -0.0039 MB               s[j]=0
    17    7.9297 MB  0.0039 MB               while j&lt;half:
    18    7.9297 MB  0.0000 MB                   s[j]=0
    19    7.9297 MB  0.0000 MB                   j+=m
    20    7.9297 MB  0.0000 MB           i=i+1
    21    7.9297 MB  0.0000 MB           m=2*i+3
    22    7.9297 MB  0.0000 MB       return [2]+[x for x in s if x]</pre>
<h3 id="wheres-the-memory-leak">Where’s the memory leak?（内存泄露）</h3>
<p>The cPython interpreter uses reference counting as it’s main method of keeping track of memory. This means that every object contains a counter, which is incremented when a reference to the object is stored somewhere, and decremented when a reference to it is deleted. When the counter reaches zero, the cPython interpreter knows that the object is no longer in use so it deletes the object and deallocates the occupied memory.</p>
<p>A memory leak can often occur in your program if references to objects are held even though the object is no longer in use.</p>
<p>The quickest way to find these “memory leaks” is to use an awesome tool called <a href="http://mg.pov.lt/objgraph/">objgraph</a>written by Marius Gedminas. This tool allows you to see the number of objects in memory and also locate all the different places in your code that hold references to these objects.</p>
<p>To get started, first install <em>objgraph</em>:</p>
<pre>pip install objgraph</pre>
<p>Once you have this tool installed, insert into your code a statement to invoke the debugger:</p>
<pre>import pdb; pdb.set_trace()</pre>
<h5 id="which-objects-are-the-most-common">Which objects are the most common?</h5>
<p>At run time, you can inspect the top 20 most prevalent objects in your program by running:</p>
<pre>(pdb) import objgraph
(pdb) objgraph.show_most_common_types()

MyBigFatObject             20000
tuple                      16938
function                   4310
dict                       2790
wrapper_descriptor         1181
builtin_function_or_method 934
weakref                    764
list                       634
method_descriptor          507
getset_descriptor          451
type                       439</pre>
<h5 id="which-objects-have-been-added-or-deleted">Which objects have been added or deleted?</h5>
<p>We can also see which objects have been added or deleted between two points in time:</p>
<pre>(pdb) import objgraph
(pdb) objgraph.show_growth()
.
.
.
(pdb) objgraph.show_growth()   # this only shows objects that has been added or deleted since last show_growth() call

traceback                4        +2
KeyboardInterrupt        1        +1
frame                   24        +1
list                   667        +1
tuple                16969        +1</pre>
<h5 id="what-is-referencing-this-leaky-object">What is referencing this leaky object?（资源引用计数）</h5>
<p>Continuing down this route, we can also see where references to any given object is being held. Let’s take as an example the simple program below:</p>
<pre>x = [1]
y = [x, [x], {"a":x}]
import pdb; pdb.set_trace()</pre>
<p>To see what is holding a reference to the variable <em>x</em>, run the <em>objgraph.show_backref()</em>function:</p>
<pre>(pdb) import objgraph
(pdb) objgraph.show_backref([x], filename="/tmp/backrefs.png")</pre>
<p>The output of that command should be a PNG image stored at <em>/tmp/backrefs.png</em> and it should look something like this:</p>
<p><img alt="back refrences" src="http://www.huyng.com/media/3011/backrefs.png" width="574" height="428" /></p>
<p>The box at the bottom with red lettering is our object of interest. We can see that it’s referenced by the symbol <em>x</em> once and by the list <em>y</em> three times. If <em>x</em> is the object causing a memory leak, we can use this method to see why it’s not automatically being deallocated by tracking down all of its references.</p>
<p>So to review, <a href="http://mg.pov.lt/objgraph/">objgraph</a> allows us to:</p>
<ul>
<li>show the top N objects occupying our python program’s memory</li>
<li>show what objects have been deleted or added over a period of time</li>
<li>show all references to a given object in our script</li>
</ul>
<h3 id="effort-vs-precision">Effort vs precision</h3>
<p>In this post, I’ve shown you how to use several tools to analyze a python program’s performance. Armed with these tools and techniques you should have all the information required to track down most memory leaks as well as identify speed bottlenecks in a Python program.</p>
<p>As with many other topics, running a performance analysis means balancing the tradeoffs between effort and precision. When in doubt, implement the simplest solution that will suit your current needs.</p>
<h5 id="refrences">Refrences</h5>
<ul>
<li><a href="http://stackoverflow.com/questions/556405/what-do-real-user-and-sys-mean-in-the-output-of-time1">stack overflow - time explained</a></li>
<li><a href="http://packages.python.org/line_profiler/">line_profiler</a></li>
<li><a href="https://github.com/fabianp/memory_profiler">memory_profiler</a></li>
<li><a href="http://mg.pov.lt/objgraph/">objgraph</a></li>
</ul>
<p>文章参考：<a href="http://www.huyng.com/posts/python-performance-analysis/" target="_blank">http://www.huyng.com/posts/python-performance-analysis/</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/python%e5%bc%80%e5%8f%91%e4%b9%8b%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96/">Python开发之性能优化</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/python%e5%bc%80%e5%8f%91%e4%b9%8b%e6%80%a7%e8%83%bd%e4%bc%98%e5%8c%96/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>搭建基于Python的视频服务器</title>
		<link>http://blog.zhourunsheng.com/2013/08/%e6%90%ad%e5%bb%ba%e5%9f%ba%e4%ba%8epython%e7%9a%84%e8%a7%86%e9%a2%91%e6%9c%8d%e5%8a%a1%e5%99%a8/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/%e6%90%ad%e5%bb%ba%e5%9f%ba%e4%ba%8epython%e7%9a%84%e8%a7%86%e9%a2%91%e6%9c%8d%e5%8a%a1%e5%99%a8/#comments</comments>
		<pubDate>Sat, 24 Aug 2013 02:57:27 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Video]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1819</guid>
		<description><![CDATA[<p>本文基于Python后台搭建了一个视频服务器，集视频的上传，视频的格式转化，视频的播放于一体，后台基于Djan [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e6%90%ad%e5%bb%ba%e5%9f%ba%e4%ba%8epython%e7%9a%84%e8%a7%86%e9%a2%91%e6%9c%8d%e5%8a%a1%e5%99%a8/">搭建基于Python的视频服务器</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>本文基于Python后台搭建了一个视频服务器，集视频的上传，视频的格式转化，视频的播放于一体，后台基于Django框架和Amazon S3的存储，视频格式的转换基于Encoding.com的在线服务，消息队列基于RabbitMQ，视频上传和转换处理完毕后，采用浏览器的Html5播放，采用了Video.js。</p>
<p>Stickyworld's consultation web app has supported video for a long time but it's been hosted via a YouTube embed. When we started building the new version of the web app we wanted to take control of the video content and also free our users from YouTube's terms of service.<span id="more-1819"></span></p>
<p>I personally had worked on projects with clients in the past which did video transcoding and it was never something easy to achieve. It takes a lot to accept every video, audio and container format under the sun and output them into various video formats that the web knows and loves.</p>
<p>With that in mind we decided the conversion process would be handled by<a href="http://www.encoding.com/r?id=22129">Encoding.com</a>. They let you encode your first Gigabyte of video with them for free and then have a tiered pricing system there after.</p>
<p>Throughout the development of the code below I would upload a two-second, 178KB video to test that everything was working. When the exceptions stopped being raised, I tested larger and more exotic files.</p>
<h2>Stage 1: The User uploads a video</h2>
<p>At the moment the new codebase just has a quick-and-dirty HTML5-based uploading mechanism. This is the CoffeeScript for uploading from the client to the server:</p>
<pre>$scope.upload_slide = (upload_slide_form) -&gt;
    file = document.getElementById("slide_file").files[0]
    reader = new FileReader()
    reader.readAsDataURL file
    reader.onload = (event) -&gt;
      result = event.target.result
      fileName = document.getElementById("slide_file").files[0].name
      $.post "/world/upload_slide",
        data: result
        name: fileName
        room_id: $scope.room.id
        (response_data) -&gt;
          if response_data.success? is not yes
            console.error "There was an error uploading the file", response_data
          else
            console.log "Upload successful", response_data
    reader.onloadstart = -&gt;
      console.log "onloadstart"
    reader.onprogress = (event) -&gt;
      console.log "onprogress", event.total, event.loaded, (event.loaded / event.total) * 100
    reader.onabort = -&gt;
      console.log "onabort"
    reader.onerror = -&gt;
      console.log "onerror"
    reader.onloadend = (event) -&gt;
      console.log "onloadend", event</pre>
<p>It would be nice to loop through <tt>("slide_file").files</tt> and upload each file individually instead of just the first file. This is somewhere we'll be addressing soon.</p>
<h2>Stage 2: Validate and upload to S3</h2>
<p>On the backend we're running Django and RabbitMQ. The key modules we're using are:</p>
<pre>$ pip install 'Django&gt;=1.5.2' 'django-celery&gt;=3.0.21' \
    'django-storages&gt;=1.1.8' 'lxml&gt;=3.2.3' 'python-magic&gt;=0.4.3'</pre>
<p>I created two models: <tt>SlideUploadQueue</tt> to store references to all the uploads and <tt>SlideVideoMedia</tt> to store all the references to the processed videos.</p>
<pre>class SlideUploadQueue(models.Model):
    created_by = models.ForeignKey(User)
    created_time = models.DateTimeField(db_index=True)
    original_file = models.FileField(
        upload_to=filename_sanitiser, blank=True, default='')
    media_type = models.ForeignKey(MediaType)
    encoding_com_tracking_code = models.CharField(
        default='', max_length=24, blank=True)

    STATUS_AWAITING_DATA = 0
    STATUS_AWAITING_PROCESSING = 1
    STATUS_PROCESSING = 2
    STATUS_AWAITING_3RD_PARTY_PROCESSING = 5
    STATUS_FINISHED = 3
    STATUS_FAILED = 4

    STATUS_LIST = (
        (STATUS_AWAITING_DATA, 'Awaiting Data'),
        (STATUS_AWAITING_PROCESSING, 'Awaiting processing'),
        (STATUS_PROCESSING, 'Processing'),
        (STATUS_AWAITING_3RD_PARTY_PROCESSING,
            'Awaiting 3rd-party processing'),
        (STATUS_FINISHED, 'Finished'),
        (STATUS_FAILED, 'Failed'),
    )

    status = models.PositiveSmallIntegerField(
        default=STATUS_AWAITING_DATA, choices=STATUS_LIST)

    class Meta:
        verbose_name = 'Slide'
        verbose_name_plural = 'Slide upload queue'

    def save(self, *args, **kwargs):
        if not self.created_time:
            self.created_time = \
                datetime.utcnow().replace(tzinfo=pytz.utc)

        return super(SlideUploadQueue, self).save(*args, **kwargs)

    def __unicode__(self):
        if self.id is None:
            return 'new &lt;SlideUploadQueue&gt;'
        return '&lt;SlideUploadQueue&gt; %d' % self.id

class SlideVideoMedia(models.Model):
    converted_file = models.FileField(
        upload_to=filename_sanitiser, blank=True, default='')

    FORMAT_MP4 = 0
    FORMAT_WEBM = 1
    FORMAT_OGG = 2
    FORMAT_FL9 = 3
    FORMAT_THUMB = 4

    supported_formats = (
        (FORMAT_MP4, 'MPEG 4'),
        (FORMAT_WEBM, 'WebM'),
        (FORMAT_OGG, 'OGG'),
        (FORMAT_FL9, 'Flash 9 Video'),
        (FORMAT_THUMB, 'Thumbnail'),
    )

    mime_types = (
        (FORMAT_MP4, 'video/mp4'),
        (FORMAT_WEBM, 'video/webm'),
        (FORMAT_OGG, 'video/ogg'),
        (FORMAT_FL9, 'video/mp4'),
        (FORMAT_THUMB, 'image/jpeg'),
    )

    format = models.PositiveSmallIntegerField(
        default=FORMAT_MP4, choices=supported_formats)

    class Meta:
        verbose_name = 'Slide video'
        verbose_name_plural = 'Slide videos'

    def __unicode__(self):
        if self.id is None:
            return 'new &lt;SlideVideoMedia&gt;'
        return '&lt;SlideVideoMedia&gt; %d' % self.id</pre>
<p>Our models use a <tt>filename_sanitiser</tt> method in each <tt>models.FileField</tt> field to automatically adjust filenames into a <tt>&lt;model&gt;/&lt;uuid4&gt;.&lt;extention&gt;</tt>format. This sanitises each filename and makes sure they're unique. To add to that, we used signed URLs that expire so we can control who is served our content and for how long.</p>
<pre>def filename_sanitiser(instance, filename):
    folder = instance.__class__.__name__.lower()
    ext = 'jpg'

    if '.' in filename:
        t_ext = filename.split('.')[-1].strip().lower()

        if t_ext != '':
            ext = t_ext

    return '%s/%s.%s' % (folder, str(uuid.uuid4()), ext)</pre>
<p>A file uploaded as <tt>testing.mov</tt> would turn into <tt>https://our-bucket.s3.amazonaws.com/slideuploadqueue/3fe27193-e87f-4244-9aa2-66409f70ebd3.mov</tt> and would be uploaded by Django Storages.</p>
<p>In our backend endpoint where video is uploaded to from the browser we validate the uploaded content using <a href="https://github.com/ahupp/python-magic">Magic</a>. This detects what kind of file it is based on it's contents:</p>
<pre>@verify_auth_token
@return_json
def upload_slide(request):
    file_data = request.POST.get('data', '')
    file_data = base64.b64decode(file_data.split(';base64,')[1])
    description = magic.from_buffer(file_data)</pre>
<p>So if <tt>description</tt> matches something like <tt>MPEG v4 system</tt> or <tt>Apple QuickTime movie</tt> then we know it's suitable for transcoding. If it isn't something like the above, we can flag it up with the user.</p>
<p>Next, we'll save the video into a our <tt>SlideUploadQueue</tt> model and send a job off to RabbitMQ. Because we're using Django Storages, it'll be uploaded to Amazon S3 automatically.</p>
<pre>slide_upload = SlideUploadQueue()
...
slide_upload.status = SlideUploadQueue.STATUS_AWAITING_PROCESSING
slide_upload.save()
slide_upload.original_file.\
    save('anything.%s' % file_ext, ContentFile(file_data))
slide_upload.save()

task = ConvertRawSlideToSlide()
task.delay(slide_upload)</pre>
<h2>Stage 3: Send the video to a 3rd party</h2>
<p>RabbitMQ will take over and handle the <tt>task.delay(slide_upload)</tt> call.</p>
<p>Here all we're doing is sending <a href="http://www.encoding.com/r?id=22129">Encoding.com</a> a URL of our video and instructions on what output formats we want. They'll give us a job number in return which we'll use to check up on the progress on the transcoding at a later point.</p>
<pre>class ConvertRawSlideToSlide(Task):
    queue = 'backend_convert_raw_slides'
    ...
    def _handle_video(self, slide_upload):
        mp4 = {
            'output': 'mp4',
            'size': '320x240',
            'bitrate': '256k',
            'audio_bitrate': '64k',
            'audio_channels_number': '2',
            'keep_aspect_ratio': 'yes',
            'video_codec': 'mpeg4',
            'profile': 'main',
            'vcodecparameters': 'no',
            'audio_codec': 'libfaac',
            'two_pass': 'no',
            'cbr': 'no',
            'deinterlacing': 'no',
            'keyframe': '300',
            'audio_volume': '100',
            'file_extension': 'mp4',
            'hint': 'no',
        }

        webm = {
            'output': 'webm',
            'size': '320x240',
            'bitrate': '256k',
            'audio_bitrate': '64k',
            'audio_sample_rate': '44100',
            'audio_channels_number': '2',
            'keep_aspect_ratio': 'yes',
            'video_codec': 'libvpx',
            'profile': 'baseline',
            'vcodecparameters': 'no',
            'audio_codec': 'libvorbis',
            'two_pass': 'no',
            'cbr': 'no',
            'deinterlacing': 'no',
            'keyframe': '300',
            'audio_volume': '100',
            'preset': '6',
            'file_extension': 'webm',
            'acbr': 'no',
        }

        ogg = {
            'output': 'ogg',
            'size': '320x240',
            'bitrate': '256k',
            'audio_bitrate': '64k',
            'audio_sample_rate': '44100',
            'audio_channels_number': '2',
            'keep_aspect_ratio': 'yes',
            'video_codec': 'libtheora',
            'profile': 'baseline',
            'vcodecparameters': 'no',
            'audio_codec': 'libvorbis',
            'two_pass': 'no',
            'cbr': 'no',
            'deinterlacing': 'no',
            'keyframe': '300',
            'audio_volume': '100',
            'file_extension': 'ogg',
            'acbr': 'no',
        }

        flv = {
            'output': 'fl9',
            'size': '320x240',
            'bitrate': '256k',
            'audio_bitrate': '64k',
            'audio_channels_number': '2',
            'keep_aspect_ratio': 'yes',
            'video_codec': 'libx264',
            'profile': 'high',
            'vcodecparameters': 'no',
            'audio_codec': 'libfaac',
            'two_pass': 'no',
            'cbr': 'no',
            'deinterlacing': 'no',
            'keyframe': '300',
            'audio_volume': '100',
            'file_extension': 'mp4',
        }

        thumbnail = {
            'output': 'thumbnail',
            'time': '5',
            'video_codec': 'mjpeg',
            'keep_aspect_ratio': 'yes',
            'file_extension': 'jpg',
        }

        encoder = Encoding(settings.ENCODING_API_USER_ID,
            settings.ENCODING_API_USER_KEY)
        resp = encoder.add_media(source=[slide_upload.original_file.url],
            formats=[mp4, webm, ogg, flv, thumbnail])

        media_id = None

        if resp is not None and resp.get('response') is not None:
            media_id = resp.get('response').get('MediaID')

        if media_id is None:
            slide_upload.status = SlideUploadQueue.STATUS_FAILED
            slide_upload.save()
            log.error('Unable to communicate with encoding.com')
            return False

        slide_upload.encoding_com_tracking_code = media_id
        slide_upload.status = \
            SlideUploadQueue.STATUS_AWAITING_3RD_PARTY_PROCESSING
        slide_upload.save()
        return True</pre>
<p><a href="http://www.encoding.com/r?id=22129">Encoding.com</a> recommended some <a href="https://github.com/djworth/python-encoding/blob/master/encoding/__init__.py">less-than-ideal python wrappers</a> for communicating with their service. I added a few fixes into the module but there is still work to be done to get it to a state I'm happy with. Below is what this wrapper currently looks like in our codebase:</p>
<pre>import httplib
from lxml import etree
import urllib
from xml.parsers.expat import ExpatError
import xmltodict

ENCODING_API_URL = 'manage.encoding.com:80'

class Encoding(object):

    def __init__(self, userid, userkey, url=ENCODING_API_URL):
        self.url = url
        self.userid = userid
        self.userkey = userkey

    def get_media_info(self, action='GetMediaInfo', ids=[],
        headers={'Content-Type': 'application/x-www-form-urlencoded'}):
        query = etree.Element('query')

        nodes = {
            'userid': self.userid,
            'userkey': self.userkey,
            'action': action,
            'mediaid': ','.join(ids),
        }

        query = self._build_tree(etree.Element('query'), nodes)
        results = self._execute_request(query, headers)

        return self._parse_results(results)

    def get_status(self, action='GetStatus', ids=[], extended='no',
        headers={'Content-Type': 'application/x-www-form-urlencoded'}):
        query = etree.Element('query')

        nodes = {
            'userid': self.userid,
            'userkey': self.userkey,
            'action': action,
            'extended': extended,
            'mediaid': ','.join(ids),
        }

        query = self._build_tree(etree.Element('query'), nodes)
        results = self._execute_request(query, headers)

        return self._parse_results(results)

    def add_media(self, action='AddMedia', source=[], notify='', formats=[],
        instant='no',
        headers={'Content-Type': 'application/x-www-form-urlencoded'}):
        query = etree.Element('query')

        nodes = {
            'userid': self.userid,
            'userkey': self.userkey,
            'action': action,
            'source': source,
            'notify': notify,
            'instant': instant,
        }

        query = self._build_tree(etree.Element('query'), nodes)

        for format in formats:
            format_node = self._build_tree(etree.Element('format'), format)
            query.append(format_node)

        results = self._execute_request(query, headers)
        return self._parse_results(results)

    def _build_tree(self, node, data):
        for k, v in data.items():
            if isinstance(v, list):
                for item in v:
                    element = etree.Element(k)
                    element.text = item
                    node.append(element)
            else:
                element = etree.Element(k)
                element.text = v
                node.append(element)

        return node

    def _execute_request(self, xml, headers, path='', method='POST'):
        params = urllib.urlencode({'xml': etree.tostring(xml)})

        conn = httplib.HTTPConnection(self.url)
        conn.request(method, path, params, headers)
        response = conn.getresponse()
        data = response.read()
        conn.close()
        return data

    def _parse_results(self, results):
        try:
            return xmltodict.parse(results)
        except ExpatError, e:
            print 'Error parsing encoding.com response'
            print e
            return None</pre>
<p>Left on the todo list include HTTPS-only transmission with strict validation of <a href="http://www.encoding.com/r?id=22129">Encoding.com</a>'s SSL certificate and to write some unit tests (tickets, tickets and more tickets).</p>
<h2>Stage 4: Download all new video formats</h2>
<p>We have a periodic job running every 15 seconds via RabbitMQ which is checking up on the progress of the video transcoding:</p>
<pre>class CheckUpOnThirdParties(PeriodicTask):
    run_every = timedelta(seconds=settings.THIRD_PARTY_CHECK_UP_INTERVAL)
    ...
    def _handle_encoding_com(self, slides):
        format_lookup = {
            'mp4': SlideVideoMedia.FORMAT_MP4,
            'webm': SlideVideoMedia.FORMAT_WEBM,
            'ogg': SlideVideoMedia.FORMAT_OGG,
            'fl9': SlideVideoMedia.FORMAT_FL9,
            'thumbnail': SlideVideoMedia.FORMAT_THUMB,
        }

        encoder = Encoding(settings.ENCODING_API_USER_ID,
            settings.ENCODING_API_USER_KEY)

        job_ids = [item.encoding_com_tracking_code for item in slides]
        resp = encoder.get_status(ids=job_ids)

        if resp is None:
            log.error('Unable to check up on encoding.com')
            return False</pre>
<p>We'll go through the response from <a href="http://www.encoding.com/r?id=22129">Encoding.com</a> validating each piece of the response as we go along:</p>
<pre>if resp.get('response') is None:
    log.error('Unable to get response node from encoding.com')
    return False

resp_id = resp.get('response').get('id')

if resp_id is None:
    log.error('Unable to get media id from encoding.com')
    return False

slide = SlideUploadQueue.objects.filter(
    status=SlideUploadQueue.STATUS_AWAITING_3RD_PARTY_PROCESSING,
    encoding_com_tracking_code=resp_id)

if len(slide) != 1:
    log.error('Unable to find a single record for %s' % resp_id)
    return False

resp_status = resp.get('response').get('status')

if resp_status is None:
    log.error('Unable to get status from encoding.com')
    return False

if resp_status != u'Finished':
    log.debug("%s isn't finished, will check back later" % resp_id)
    return True

formats = resp.get('response').get('format')

if formats is None:
    log.error("No output formats were found. Something's wrong.")
    return False

for format in formats:
    try:
        assert format.get('status') == u'Finished', \
        "%s is not finished. Something's wrong." % format.get('id')

        output = format.get('output')
        assert output in ('mp4', 'webm', 'ogg', 'fl9',
            'thumbnail'), 'Unknown output format %s' % output

        s3_dest = format.get('s3_destination')
        assert 'http://encoding.com.result.s3.amazonaws.com/'\
            in s3_dest, 'Suspicious S3 url: %s' % s3_dest

        https_link = \
            'https://s3.amazonaws.com/encoding.com.result/%s' %\
            s3_dest.split('/')[-1]
        file_ext = https_link.split('.')[-1].strip()

        assert len(file_ext) &gt; 0,\
            'Unable to get file extension from %s' % https_link

        count = SlideVideoMedia.objects.filter(slide_upload=slide,
            format=format_lookup[output]).count()

        if count != 0:
            print 'There is already a %s file for this slide' % output
            continue

        content = self.download_content(https_link)

        assert content is not None,\
            'There is no content for %s' % format.get('id')
    except AssertionError, e:
        log.error('A format did not pass all assertions: %s' % e)
        continue</pre>
<p>At this point we've asserted everything is as it should be a we can save each of the videos:</p>
<pre>media = SlideVideoMedia()
media.format = format_lookup[output]
media.converted_file.save('blah.%s' % file_ext, ContentFile(content))
media.save()</pre>
<h2>Stage 5: Video via HTML5</h2>
<p>On our frontend we've created a page with an HTML5 video element. We're using <a href="http://www.videojs.com/">video.js</a> to display the video in the best-supported format for each browser.</p>
<pre>➫ bower install video.js
bower caching git://github.com/videojs/video.js-component.git
bower cloning git://github.com/videojs/video.js-component.git
bower fetching video.js
bower checking out video.js#v4.0.3
bower copying /home/mark/.bower/cache/video.js/5ab058cd60c5615aa38e8e706cd0f307
bower installing video.js#4.0.3</pre>
<p>In our <tt>index.jade</tt> file we include it's dependencies:</p>
<pre>!!! 5
html(lang="en", class="no-js")
  head
    meta(http-equiv='Content-Type', content='text/html; charset=UTF-8')
    ...
    link(rel='stylesheet', type='text/css', href='/components/video-js-4.1.0/video-js.css')
    script(type='text/javascript', src='/components/video-js-4.1.0/video.js')</pre>
<p>In a Angular.js/JADE-based template we've included a <tt>&lt;video&gt;</tt> tag and it's <tt>&lt;source&gt;</tt> children tags. There is also a <tt>poster</tt> element that will show a static image of the video we've transcoded from the first few moments of the video.</p>
<pre>#main.span12
    video#example_video_1.video-js.vjs-default-skin(controls, preload="auto", width="640", height="264", poster="{{video_thumbnail}}", data-setup='{"example_option":true}', ng-show="videos")
        source(ng-repeat="video in videos", src="{{video.src}}", type="{{video.type}}")</pre>
<p>This will print out every video format we've converted into, each as it's own <tt>&lt;source&gt;</tt> tag. <a href="http://www.videojs.com/">Video.js</a> will decide which of them to play based on the browser the user is using.</p>
<p>We still have a lot of work to do around fallback support, building unit tests and improving the robustness of our <a href="http://www.encoding.com/r?id=22129">Encoding.com</a> service wrapper. If this sort of work interests you please do <a href="http://uk.linkedin.com/in/marklitwintschik">get in touch</a>.</p>
<p>文章节选：<a href="http://techblog.stickyworld.com/video-with-python.html" target="_blank">http://techblog.stickyworld.com/video-with-python.html</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e6%90%ad%e5%bb%ba%e5%9f%ba%e4%ba%8epython%e7%9a%84%e8%a7%86%e9%a2%91%e6%9c%8d%e5%8a%a1%e5%99%a8/">搭建基于Python的视频服务器</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/%e6%90%ad%e5%bb%ba%e5%9f%ba%e4%ba%8epython%e7%9a%84%e8%a7%86%e9%a2%91%e6%9c%8d%e5%8a%a1%e5%99%a8/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Python模块发布文件setup.py vs requirements.txt</title>
		<link>http://blog.zhourunsheng.com/2013/08/python%e6%a8%a1%e5%9d%97%e5%8f%91%e5%b8%83%e6%96%87%e4%bb%b6setup-py-vs-requirements-txt/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/python%e6%a8%a1%e5%9d%97%e5%8f%91%e5%b8%83%e6%96%87%e4%bb%b6setup-py-vs-requirements-txt/#comments</comments>
		<pubDate>Sun, 04 Aug 2013 09:06:39 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[PyPI]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1710</guid>
		<description><![CDATA[<p>我们都知道发布Python模块的时候需要准备几份文件，其中就包括了setup.py和requirements. [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/python%e6%a8%a1%e5%9d%97%e5%8f%91%e5%b8%83%e6%96%87%e4%bb%b6setup-py-vs-requirements-txt/">Python模块发布文件setup.py vs requirements.txt</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>我们都知道发布Python模块的时候需要准备几份文件，其中就包括了setup.py和requirements.txt，这两份文件中都需要填写待发布模块的依赖关系，那么是不是这样就会出现信息的重复，维护起来也不方便呢？ 如下的文章解释了这两个文件中重复信息的不同功用，前者为“虚引用”，后者为“具体引用”，那么他们利用的场合和背后的机制是什么样子的，马上就会知晓了。</p>
<p>There's a lot of misunderstanding between <code>setup.py</code> and <code>requirements.txt</code> and their roles. A lot of people have felt they are duplicated information and have even created <a href="https://pypi.python.org/pypi/pbr/#requirements">tools</a> to handle this "duplication".<span id="more-1710"></span></p>
<h2>Python Libraries</h2>
<p>A Python library in this context is something that has been developed and released for others to use. You can find a number of them on <a href="https://pypi.python.org/pypi">PyPI</a> that others have made available. A library has a number of pieces of metadata that need to be provided in order to successfully distribute it. These are things such as the Name, Version, Dependencies, etc. The <code>setup.py</code> file gives you the ability to specify this metadata like:</p>
<div>
<pre>from setuptools import setup

setup(
    name="MyLibrary",
    version="1.0",
    install_requires=[
        "requests",
        "bcrypt",
    ],
    # ...
)</pre>
</div>
<p>This is simple enough, you have the required pieces of metadata declared. However something you don't see is a specification as to where you'll be getting those dependencies from. There's no url or filesystem where you can fetch these dependencies from, it's just "requests" and "bcrypt". This is important and for lack of a better term I call these "abstract dependencies". They are dependencies which exist only as a name and an optional version specifier. Think of it like duck typing your dependencies, you don't care what specific "requests" you get as long as it looks like "requests".</p>
<h2>Python Applications</h2>
<p>Here when I speak of a Python application I'm going to typically be speaking about something that you specifically deploy. It may or may not exist on PyPI but it's something that likely does not have much in the way of reusability. An application that does exist on PyPI typically requires a deploy specific configuration file and this section deals with the "deploy specific" side of a Python application.</p>
<p>An application typically has a set of dependencies, often times even a very complex set of dependencies, that it has been tested against. Being a specific instance that has been deployed, it typically does not have a name, nor any of the other packaging related metadata. This is reflected in the abilities of a <a href="http://pip-installer.org/">pip</a>requirements file. A typical requirements file might look something like:</p>
<div>
<pre># This is an implicit value, here for clarity
--index https://pypi.python.org/simple/

MyPackage==1.0
requests==1.2.0
bcrypt==1.0.2</pre>
</div>
<p>Here you have each dependency shown along with an exact version specifier. While a library tends to want to have wide open ended version specifiers an application wants very specific dependencies. It may not have mattered up front what version of requests was installed but you want the same version to install in production as you developed and tested with locally.</p>
<p>At the top of this file you'll also notice a <code>--index https://pypi.python.org/simple/</code>. Your typical requirements.txt won't have this listed explicitly like this unless they are not using PyPI, it is however an important part of a <code>requirements.txt</code>. This single line is what turns the abstract dependency of <code>requests==1.2.0</code> into a "concrete" dependency of "requests 1.2.0 from https://pypi.python.org/simple/". This is not like duck typing, this is the packaging equivalent of an <code>isinstance()</code> check.</p>
<h2>So Why Does Abstract and Concrete Matter?</h2>
<p>You've read this far and maybe you've said, ok I know that <code>setup.py</code> is designed for redistributable things and that <code>requirements.txt</code> is designed for non-redistributable things but I already have something that reads a <code>requirements.txt</code> and fills out my <code>install_requires=[...]</code> so why should I care?</p>
<p>This split between abstract and concrete is an important one. It was what allows the PyPI mirroring infrastructure to work. It is what allows a company to host their own private package index. It is even what enables you to fork a library to fix a bug or add a feature and use your own fork. Because an abstract dependency is a name and an optional version specifier you can install it from PyPI or from Crate.io, or from your own filesystem. You can fork a library, change the code, and as long as it has the right name and version specifier that library will happily go on using it.</p>
<p>A more extreme version of what can happen when you use a concrete requirement where an abstract requirement should be used can be found in the <a href="http://golang.org/">Go language</a>. In the go language the default package manager (<code>go get</code>) allows you to specify your imports via an url inside the code which the package manager collects and downloads. This would look something like:</p>
<div>
<pre>import (
        "github.com/foo/bar"
)</pre>
</div>
<p>Here you can see that an exact url to a dependency has been specified. Now if I used a library that specified its dependencies this way and I wanted to change the "bar" library because of a bug that was affecting me or a feature I needed, I would not only need to fork the bar library, but I would also need to fork the library that depended on the bar library to update it. Even worse, if the bar library was say, 5 levels deep, then that's a potential of 5 different packages that I would need to fork and modify only to point it at a slightly different "bar".</p>
<h3>A Setuptools Misfeature</h3>
<p>Setuptools has a feature similar to the Go example. It's called<a href="http://pythonhosted.org/setuptools/setuptools.html#dependencies-that-aren-t-in-pypi">dependency links</a> and it looks like this:</p>
<div>
<pre>from setuptools import setup

setup(
    # ...
    dependency_links = [
        "http://packages.example.com/snapshots/",
        "http://example2.com/p/bar-1.0.tar.gz",
    ],
)</pre>
</div>
<p>This "feature" of setuptools removes the abstractness of its dependencies and hardcodes an exact url from which you can fetch the dependency from. Now very similarly to Go if we want to modify packages, or simply fetch them from a different server we'll need to go in and edit each package in the dependency chain in order to update the <code>dependency_links</code>.</p>
<h2>Developing Reusable Things or How Not to Repeat Yourself</h2>
<p>The "Library" and "Application" distinction is all well and good, but whenever you're developing a Library, in a way <em>it</em> becomes your application. You want a specific set of dependencies that you want to fetch from a specific location and you know that you should have abstract dependencies in your <code>setup.py</code> and concrete dependencies in your <code>requirements.txt</code> but you don't want to need to maintain two separate lists which will inevitably go out of sync. As it turns out pip requirements file have a construct to handle just such a case. Given a directory with a <code>setup.py</code> inside of it you can write a requirements file that looks like:</p>
<div>
<pre>--index https://pypi.python.org/simple/

-e .</pre>
</div>
<p>Now your <code>pip install -r requirements.txt</code> will work just as before. It will first install the library located at the file path <code>.</code> and then move on to its abstract dependencies, combining them with its <code>--index</code> option and turning them into concrete dependencies and installing them.</p>
<p>This method grants another powerful ability. Let's say you have two or more libraries that you develop as a unit but release separately, or maybe you've just split out part of a library into its own piece and haven't officially released it yet. If your top level library still depends on just the name then you can install the development version when using the <code>requirements.txt</code> and the release version when not, using a file like:</p>
<div>
<pre>--index https://pypi.python.org/simple/

-e https://github.com/foo/bar.git#egg=bar
-e .</pre>
</div>
<p>This will first install the bar library from https://github.com/foo/bar.git, making it equal to the name "bar", and then will install the local package, again combining its dependencies with the <code>--index</code> option and installing but this time since the "bar" dependency has already been satisfied it will skip it and continue to use the in development version.</p>
<p><em><strong>Recognition:</strong> This post was inspired by <a href="http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/">Yehuda Katz's blog post</a> on a similar issue in Ruby with <code>Gemfile</code> and <code>gemspec</code>.</em></p>
<p>文章选自：<a href="https://caremad.io/blog/setup-vs-requirements/?utm_source=pycoders&amp;utm_campaign=47251bf186-Pycoder_s_Weekly_Issue_76_Caremad7_26_2013&amp;utm_medium=email&amp;utm_term=0_64134e0a27-47251bf186-49523505">setup-vs-requirements</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/python%e6%a8%a1%e5%9d%97%e5%8f%91%e5%b8%83%e6%96%87%e4%bb%b6setup-py-vs-requirements-txt/">Python模块发布文件setup.py vs requirements.txt</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/python%e6%a8%a1%e5%9d%97%e5%8f%91%e5%b8%83%e6%96%87%e4%bb%b6setup-py-vs-requirements-txt/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Python函数调用的自动化内联机制</title>
		<link>http://blog.zhourunsheng.com/2013/08/python%e5%87%bd%e6%95%b0%e8%b0%83%e7%94%a8%e7%9a%84%e8%87%aa%e5%8a%a8%e5%8c%96%e5%86%85%e8%81%94%e6%9c%ba%e5%88%b6/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/python%e5%87%bd%e6%95%b0%e8%b0%83%e7%94%a8%e7%9a%84%e8%87%aa%e5%8a%a8%e5%8c%96%e5%86%85%e8%81%94%e6%9c%ba%e5%88%b6/#comments</comments>
		<pubDate>Sat, 03 Aug 2013 02:14:02 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1701</guid>
		<description><![CDATA[<p>相信开发过C++语言的同学都知道，inline内联函数，get和set函数通常都写成内联机制，为的是提高程序的 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/python%e5%87%bd%e6%95%b0%e8%b0%83%e7%94%a8%e7%9a%84%e8%87%aa%e5%8a%a8%e5%8c%96%e5%86%85%e8%81%94%e6%9c%ba%e5%88%b6/">Python函数调用的自动化内联机制</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>相信开发过C++语言的同学都知道，inline内联函数，get和set函数通常都写成内联机制，为的是提高程序的运行效率，缩短函数调用的时间。那么开发过Android的同学，也应该知道，android程序优化中有一条机制就是尽量不用get和set函数，因为Java中不支持C++的内联机制，所以只能舍弃get和set方法，尽管有失面向对象的访问机制，但是对手持设备来说，合理利用有限资源，提高运行效率才是首选。</p>
<p>本文中讲的是怎样在Python语言中利用内联机制，Python是解释性的语言，没有编译过程，那么怎么在Python中实现内联机制呢？相信读完全文，就会有个清晰的认识，目前的实现还仍有很多不足的地方，限制很多，期待以后Python的升级能够自带就好了。<span id="more-1701"></span></p>
<p>Calling functions in Python can be expensive. Consider this example: there are two statements that are being timed, the first one calls a function that returns an integer while the second one calls a function that returns the result of a second function call which returns an integer.</p>
<div>
<pre>tom@toms ~$ python -m timeit -n 10000000  -s "def get_n(): return 1" "get_n()"
10000000 loops, best of 3: 0.145 usec per loop
tom@toms ~$ time python -m timeit -n 10000000 -s "get_n = lambda: 1; get_r_n = lambda: get_n()" "get_r_n()"
10000000 loops, best of 3: 0.335 usec per loop</pre>
</div>
<p>The additional function call doubled the program execution time, despite not effecting the output of the function in any way. This got me thinking, how hard would it be to create a Python module that would inline functions, removing the calling overhead from certain functions you specify?</p>
<p>As it turns out, not that hard. <strong>Note</strong>: This is simply an experiment to see what's possible, don't even think about using this in real Python code (there are some serious limitations explained at the end). Check this out:</p>
<div>
<pre>from inliner import inline

@inline
def add_stuff(x, y):
    return x + y

def call_func_args(num):
    return add_stuff(1, num)

import dis
dis.dis(call_func_args)
# Prints:
# 0 LOAD_CONST               1 (1)
# 3 LOAD_FAST                0 (num)
# 6 BINARY_ADD          
# 7 RETURN_VALUE</pre>
</div>
<p>The dis function prints out the bytecode operations for a Python function, which shows that the call_func_args function has been modified so that the add_stuff() call never takes place and instead the body of the add_stuff function has been inlined inside the call_func_args function. I've put the <a href="https://github.com/orf/inliner">code on GitHub</a>, have a look if you like. Below I will explain how it works, for those interested.</p>
<h4 id="diving-in-import-hooks-and-the-ast-module">Diving in: Import hooks and the AST module</h4>
<p>Python is an interpreted language, when you run a Python program the source code is parsed into an Abstract Syntax Tree which is then 'compiled' into bytecode. We need a way of modifying the AST of an imported module before it gets compiled, and as luck would have it Python provides <a href="http://www.python.org/dev/peps/pep-0302/">powerful hooks</a> into the import mechanism that allow you to write importers that <a href="http://blog.dowski.com/2008/07/31/customizing-the-python-import-system/">grab code from the internet</a> or <a href="http://journal.thobe.org/2008/07/simple-stuff-with-import-hooks-in.html">restrict packages from being imported</a>. Getting our claws into the import mechanism is as simple as this:</p>
<div>
<pre>import sys, imp

class Loader(object):
    def __init__(self, module):
        self.module = module

    def load_module(self, fullname):
        return self.module

class Importer(object):
    def find_module(self, fullname, path):
        file, pathname, description = imp.find_module(
            fullname.split(".")[-1], path)
        module_contents = file.read()
        # We can now mess around with the module_contents.
        # and produce a module object
        return Loader(make_module(module_contents))

sys.meta_path.append(Importer())</pre>
</div>
<p>Now whenever anything is imported our find_module() method will be called. This should return an object with a load_module() function, which returns the final module.</p>
<h5 id="modifying-the-ast">Modifying the AST</h5>
<p>Python provides an <a href="http://docs.python.org/3.4/library/ast.html">AST module</a> to modify Python AST trees. So inside our find_module function we can get the source code of the module we are importing, parse it into an AST representation and then modify it before compiling it. You can see this in <a href="https://github.com/orf/inliner/blob/master/inliner/import_hook.py#L24">action here</a>.</p>
<p>First we need to find all functions that are wrapped by our inline decorator, which is pretty simple to do. The AST module provides a NodeVisitor and a NodeTransformer class you can subclass. For <a href="http://greentreesnakes.readthedocs.org/en/latest/nodes.html">each different type of AST node</a> a visit_NAME method will be called, which you can then choose to modify or pass along untouched. The InlineMethodLocator runs through all the function definition's in a tree and stores any that are wrapped by our inline decorator:</p>
<div>
<pre>class InlineMethodLocator(ast.NodeVisitor):
    def __init__(self):
        self.functions = {}

    def visit_FunctionDef(self, node):
        if any(filter(lambda d: d.id == "inline", node.decorator_list)):
            func_name = utils.getFunctionName(node)
            self.functions[func_name] = node</pre>
</div>
<p>The next step after we have identified the functions we want to inline is to find where they are called, and then inline them. To do this we need to look for all Call nodes in our modules AST tree:</p>
<div>
<pre>class FunctionInliner(ast.NodeTransformer):
    def __init__(self, functions_to_inline):
        self.inline_funcs = functions_to_inline

    def visit_Call(self, node):
        func = node.func
        func_name = utils.getFunctionName(func)
        if func_name in self.inline_funcs:
            func_to_inline = self.inline_funcs[func_name]
            transformer = transformers.getFunctionHandler(func_to_inline)
            if transformer is not None:
                node = transformer.inline(node, func_to_inline)

        return node</pre>
</div>
<p>This visits all call objects and if we are calling a function we want to inline then we go grab a transformer object which will be responsible for the actual inlining. I've only <a href="https://github.com/orf/inliner/blob/master/inliner/transformers/SimpleFunctionHandler.py">written one transformer so far</a> that works on simple functions (functions with 1 statement), but more can be added fairly easily. The simple function transformer simply returns the contents of the function and maps the functions values to the values of the calling function:</p>
<div>
<pre>class SimpleFunctionHandler(BaseFunctionHandler):
    def inline(self, node, func_to_inline):
        # Its a simple function we have here. That means it is one statement and we can simply replace the
        # call with the inlined functions body
        body = func_to_inline.body[0]
        if isinstance(body, ast.Return):
            body = body.value

        return self.replace_params_with_objects(body, func_to_inline, node)</pre>
</div>
<h4 id="limitations">Limitations</h4>
<p>There are some serious limitations with this code:</p>
<ol>
<li><strong>Inlined functions must have a unique name</strong>: The AST provides us with no type information (as Python is dynamically typed), only the name of the function we are calling. That means without writing code that attempts to deduce the type of a class instance (no mean feat) then each function call must have a unique name.</li>
<li><strong>Only inlines functions in the same module</strong>: To keep things simple only calls in the same module are inlined.</li>
<li><strong>Inlined class functions can't reference any double underscore attributes</strong>: Accessing self.__attr is about as 'private' as you can get in Python. The attribute lookup is prefixed with the class name, which we can't easily detect while inlining.</li>
<li><strong>Everything will break</strong>: Python is very dynamic, you may wish to replace functions at runtime. Obviously if the functions have been inlined then this won't have any effect.</li>
</ol>
<p>文章选自：<a href="http://tomforb.es/automatically-inline-python-function-calls">automatically-inline-python-function-calls</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/python%e5%87%bd%e6%95%b0%e8%b0%83%e7%94%a8%e7%9a%84%e8%87%aa%e5%8a%a8%e5%8c%96%e5%86%85%e8%81%94%e6%9c%ba%e5%88%b6/">Python函数调用的自动化内联机制</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/python%e5%87%bd%e6%95%b0%e8%b0%83%e7%94%a8%e7%9a%84%e8%87%aa%e5%8a%a8%e5%8c%96%e5%86%85%e8%81%94%e6%9c%ba%e5%88%b6/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>分享Python模块到PyPI</title>
		<link>http://blog.zhourunsheng.com/2013/08/%e5%88%86%e4%ba%abpython%e6%a8%a1%e5%9d%97%e5%88%b0pypi/</link>
		<comments>http://blog.zhourunsheng.com/2013/08/%e5%88%86%e4%ba%abpython%e6%a8%a1%e5%9d%97%e5%88%b0pypi/#comments</comments>
		<pubDate>Sat, 03 Aug 2013 01:40:54 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[PyPI]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1687</guid>
		<description><![CDATA[<p>怎么样打包自己的Python模块，并且分享到PyPI，相信下面的文章会给你一个满意的答案！ A complet [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e5%88%86%e4%ba%abpython%e6%a8%a1%e5%9d%97%e5%88%b0pypi/">分享Python模块到PyPI</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>怎么样打包自己的Python模块，并且分享到PyPI，相信下面的文章会给你一个满意的答案！</p>
<p>A completely incomplete guide to packaging a Python module and sharing it with the world on PyPI.</p>
<h2>Abstract</h2>
<p>Few things give me <a href="https://twitter.com/hynek/status/344358831163310081">caremads</a> like Python modules I want to use that aren’t on <a href="https://pypi.python.org/">PyPI</a> (pronounced “pie pee eye”, or “cheese shop”, <em>not</em> “<a href="http://pypy.org/">pie pie</a>”!). On the other hand – as<a href="http://pydanny.com/made-up-statistics.html#debate-statistics">pydanny points out</a> – the current situation on packaging is rather confusing.</p>
<p>Therefore I want to help everyone who has some great code but feels lost with getting it on PyPI. I will be using my latest opus “<a href="https://github.com/hynek/pem">pem</a>” as a realistic yet simple example of how to get a pure-Python 2 <em>and</em> 3 module packaged up, tested and uploaded to PyPI. Including the new and shiny binary <a href="http://wheel.readthedocs.org/">wheel</a> format that’s faster and allows for binary extensions (read the <a href="http://wheel.readthedocs.org/en/latest/story.html">wheel story</a> if you want to know more)!</p>
<p>I’ll keep it <em>super simple</em> to get everyone started. At the end, I’ll link more complete documentation to show you the way ahead. Sorry, no Windows.<span id="more-1687"></span></p>
<h2>Tools Used</h2>
<p>This is not a history lesson, therefore we will use:</p>
<ul>
<li><a href="http://www.pip-installer.org/">pip</a> 1.4+ (if you have no pip yet, <a href="http://www.pip-installer.org/en/latest/installing.html#using-get-pip">bootstrapping</a> is easy),</li>
<li><a href="http://pythonhosted.org/setuptools/">setuptools</a> 0.9+,</li>
<li>and <a href="http://wheel.readthedocs.org/">wheel</a> 0.21+.</li>
</ul>
<div>
<pre>$ pip install -U "pip&gt;=1.4" "setuptools&gt;=0.9" "wheel&gt;=0.21"</pre>
</div>
<p>Please make sure all installs succeed, ancient installations may need some extra manual labor.</p>
<h3>A Minimal Glimpse Into The Past</h3>
<p><em>Forget</em> that there ever was <del>distribute</del> (<a href="http://pythonhosted.org/setuptools/merge.html">cordially merged into setuptools</a>), <del>easy_install</del> (part of setuptools, supplanted by pip), or <del>distutils2</del> aka <del>packaging</del> (was supposed to be the official thing from Python 3.3 on, didn’t get done in time due to lack of helping hands, <a href="http://mail.python.org/pipermail/python-dev/2012-June/120430.html">got ripped out by a heart-broken Éric</a> and abandoned now).</p>
<p>Be just <em>vaguely aware</em> that there are <a href="http://docs.python.org/2/library/distutils.html">distutils</a> and <a href="https://distlib.readthedocs.org/">distlib</a>somewhere underneath but ideally it shouldn't matter to you at all <em>for now</em>.</p>
<h2>setup.py</h2>
<p>Nowadays, every project that you want to package needs a<code>setup.py</code> file. Let’s have a look at what pem’s could look like:</p>
<div>
<pre>from setuptools import setup

setup(
    name='pem',
    version='0.1.0',
    description='Parse and split PEM files painlessly.',
    long_description=(open('README.rst').read() + '\n\n' +
                      open('HISTORY.rst').read() + '\n\n' +
                      open('AUTHORS.rst').read()),
    url='http://github.com/hynek/pem/',
    license='MIT',
    author='Hynek Schlawack',
    author_email='hs@ox.cx',
    py_modules=['pem'],
    include_package_data=True,
    classifiers=[
        'Development Status :: 5 - Production/Stable',
        'Intended Audience :: Developers',
        'Natural Language :: English',
        'License :: OSI Approved :: MIT License',
        'Operating System :: OS Independent',
        'Programming Language :: Python',
        'Programming Language :: Python :: 2',
        'Programming Language :: Python :: 2.6',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.3',
        'Topic :: Software Development :: Libraries :: Python Modules',
    ],
)</pre>
</div>
<p>In the simplest case, you just import <code>setup</code> from <code>setuptools</code>and run it with some keyword arguments.</p>
<p>Before I go into these arguments, I want to point out one of the most neglected and yet most important one: <code>license</code>.<em>Always</em> <a href="http://giovanni.bajo.it/post/56510184181/is-gpl-still-relevant">set a license</a>! Otherwise nobody can use your module, which would be a pity, right?</p>
<p>I take a few shortcuts here which you may want to copy too:</p>
<ul>
<li>I love <a href="http://en.wikipedia.org/wiki/Don%27t_repeat_yourself">DRY</a>, therefore my long description is just a concatenation of my README, change log and author credits.</li>
<li>I use <a href="https://pypi.python.org/pypi/bumpversion/">bumpversion</a> for manipulating my version strings.</li>
</ul>
<p>An essential field is <code>py_modules</code> which you'll use to denote the relevant code for packaging, if your package is actually just a single module like in <a href="https://github.com/hynek/pem/blob/master/pem.py">pem’s case</a>. If you have a full-blown package (i.e. a directory), you’ll go for:</p>
<div>
<pre>from setuptools import setup, find_packages

setup(
...
    packages=find_packages(exclude=['tests*']),
...
)</pre>
</div>
<p>The <a href="http://pythonhosted.org/distribute/setuptools.html#using-find-packages"><code>exclude</code></a> makes sure that a top-level <code>tests</code> package doesn’t get <em>installed</em> (it’s still part of the source distribution) since that would wreak havoc.</p>
<p>You can also specify them by hand like I used to for<a href="https://github.com/hynek/doc2dash/">doc2dash</a>’s <a href="https://github.com/hynek/doc2dash/blob/2357c81adc87a885952e08671fcd8db9a177b6d7/setup.py#L15"><code>setup.py</code></a>. However, unless you have good reasons, just use <code>find_packages()</code> and be done.</p>
<p>The classifiers field’s usefulness is openly disputed, nevertheless pick them from <a href="https://pypi.python.org/pypi?%3Aaction=list_classifiers">here</a>. PyPI will refuse to accept packages with unknown classifiers, hence I like to use <code>"Private :: Do Not Upload"</code> for private packages to protect myself from my own stupidity.</p>
<p>One icky thing are dependencies. Unless you <em>really</em> know what you’re doing, <strong><a href="https://caremad.io/blog/setup-vs-requirements/">don’t pin</a></strong> them (specifying minimal version your package <em>requires</em> to work is fine of course) or your users won’t be able to install security updates of your dependencies:</p>
<div>
<pre>setup(
...
    install_requires=['Django', 'django-annoying'],
...
)</pre>
</div>
<p>Rule of thumb: <a href="http://www.pip-installer.org/en/latest/logic.html#requirements-file-format"><code>requirements.txt</code></a> should contain <em>only</em> <code>==</code>,<code>setup.py</code> the rest (<code>&gt;=</code>, <code>!=</code>, <code>&lt;=</code>, …).</p>
<h2>Non-Code Files</h2>
<p>Every Python project has a “Fix MANIFEST.in” commit. Look it up, it’s true.</p>
<p>But it’s not really that hard: you add all files and directories that are <em>not</em> packaged due to <code>py_modules</code> or <code>packages</code> in your <code>setup.py</code>.</p>
<p>For “pem”, it’s just</p>
<pre>include *.rst LICENSE</pre>
<p>For more commands, have a look at the <a href="http://docs.python.org/2/distutils/sourcedist.html#commands"><code>MANIFEST.in</code> docs</a> – especially <code>recursive-include</code> is used widely. There’s also a handy tool to validate your <code>MANIFEST.in</code>: <a href="https://pypi.python.org/pypi/check-manifest">check-manifest</a>.</p>
<p><strong>Important</strong>: If you want the files and directories from<code>MANIFEST.in</code> to also be <em>installed</em> (e.g. if it’s runtime-relevant data), you will have to set <code>include_package_data=True</code> in your<code>setup()</code> call as in the example above!</p>
<h2>Configuration</h2>
<p>For our minimal Python-only project, we’ll only need two lines in setup.cfg:</p>
<div>
<pre>[wheel]
universal = 1</pre>
</div>
<p>These will make wheel build a universal wheel file (e.g.<code>pem-0.1.0-<strong>py2.py3</strong>-none-any.whl</code>) and you won’t have to circle through virtual environments of all supported Python versions to build them separately.</p>
<h2>Documentation</h2>
<p>As I’ve hopefully established, every open source project needs a <a href="http://choosealicense.com/">license</a>. No excuses.</p>
<p>Additionally, even the simplest package needs a README that tells potential users what they’re looking at. Make it<a href="http://sphinx-doc.org/rest.html#rst-primer">reStructuredText</a> (reST) so PyPI can properly render it on your project page. As a courtesy to your users, also keep a change log so they know what to expect from your releases. And finally it’s good style to credit your contributors in a third file. I like to call them <code>README.rst</code>,<code>HISTORY.rst</code>, and <code>AUTHORS.rst</code> respectively, but there’s no hard rule.</p>
<p>My long project description and thus PyPI text is the concatenation of those three (and I’ve shamelessly stolen it from <a href="https://github.com/kennethreitz/requests/blob/786fe94ac43eacc580d93442097cd3fc64090ffd/setup.py#L33">Kenneth Reitz</a>).</p>
<p>If you host on GitHub, you may want to add a<a href="https://github.com/blog/1184-contributing-guidelines"><code>CONTRIBUTING.md</code></a> that gets displayed when someone wants to open a pull request. Unfortunately it has to be a Markdown file because GitHub doesn’t support reST for this. Have a look at <a href="https://github.com/hynek/pem/blob/master/CONTRIBUTING.md">pem’s</a> if you need inspiration.</p>
<h2>Let’s Build Finally!</h2>
<p>Building a source distribution (which is what is usually used for pure-Python project) is just a matter of</p>
<div>
<pre>$ python setup.py sdist</pre>
</div>
<p>Because it’s 2013 and wheels are awesome, we’ll build one too! Fortunately, it’s just as easy (please note that you’ll need to install <code>wheel</code> as per above for this to work!):</p>
<div>
<pre>$ python setup.py bdist_wheel</pre>
</div>
<p>Of course, the wheel package is <em>optional</em>, but your users will thank you for much faster installation times.</p>
<p>Now you should have a new directory called <code>dist</code> containing a source distribution file and a wheel file. For pem, it currently looks like this:</p>
<pre>dist
├── pem-0.1.0-py2.py3-none-any.whl
└── pem-0.1.0.tar.gz</pre>
<p>You can test whether both install properly before we move on to uploading:</p>
<pre>$ rm -rf 27-sdist  # ensure clean state if ran repeatedly
$ virtualenv 27-sdist
...
$ 27-sdist/bin/pip install --no-index dist/pem-0.1.0.tar.gz
Ignoring indexes: https://pypi.python.org/simple/
Unpacking ./dist/pem-0.1.0.tar.gz
  Running setup.py egg_info for package from file:///Users/hynek/Projects/pem/dist/pem-0.1.0.tar.gz

Installing collected packages: pem
  Running setup.py install for pem

Successfully installed pem
Cleaning up...
$ 27-sdist/bin/python
Python 2.7.4 (default, Apr 21 2013, 09:35:41)
[GCC 4.2.1 Compatible Apple LLVM 4.2 (clang-425.0.27)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
&gt;&gt;&gt; import pem
&gt;&gt;&gt; pem.__version__
'0.1.0'</pre>
<p>and</p>
<pre>$ rm -rf 27-wheel  # ensure clean state if ran repeatedly
$ virtualenv 27-wheel
...
$ 27-wheel/bin/pip install --use-wheel --no-index --find-links dist pem
Ignoring indexes: https://pypi.python.org/simple/
Downloading/unpacking pem
Installing collected packages: pem
Successfully installed pem
Cleaning up...
$ 27-wheel/bin/python
Python 2.7.4 (default, Apr 21 2013, 09:35:41)
[GCC 4.2.1 Compatible Apple LLVM 4.2 (clang-425.0.27)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
&gt;&gt;&gt; import pem
&gt;&gt;&gt; pem.__version__
'0.1.0'</pre>
<p>Please note the lack of “Running setup.py install for pem” in the second test. Yes, you can finally install packages without executing arbitrary code!</p>
<p>So you’re confident that your package is perfect? Let’s use the <a href="http://wiki.python.org/moin/TestPyPI">Test PyPI server</a> to find out!</p>
<h2>The PyPI Staging Server</h2>
<p>Again, I’ll be using “pem” as the example project name to avoid <code>&lt;your project name&gt;</code> everywhere.</p>
<p>First, sign up on the <a href="https://testpypi.python.org/">test server</a>, you will receive a user name and a password. Please note that this is independent from the live servers. Thus you’ll have to re-register both yourself and your packages. It also gets cleaned from time to time so don’t be surprised if it suddenly doesn’t know about you or your projects anymore. Just re-register.</p>
<p>Next, create a <code>~/.pypirc</code> consisting of:</p>
<div>
<pre>[distutils]
index-servers=
    test

[test]
repository = https://testpypi.python.org/pypi
username = &lt;your user name goes here&gt;
password = &lt;your password goes here&gt;</pre>
</div>
<p>Then use</p>
<div>
<pre>$ python setup.py register -r test</pre>
</div>
<p>to register your project with the PyPI test server.</p>
<p>Finally, upload your distributions:</p>
<div>
<pre>$ python setup.py sdist upload -r test
$ python setup.py bdist_wheel upload -r test</pre>
</div>
<p>Please note that calling <code>upload</code> without building something in the <em>same command line</em> will give you a:</p>
<pre>error: No dist file created in earlier command</pre>
<p>Now test your packages again:</p>
<div>
<pre>$ pip install -i https://testpypi.python.org/pypi pem</pre>
</div>
<p>Everything dandy? Then lets tackle the last step: putting it on the <em>real</em> PyPI!</p>
<h2>The Final Step</h2>
<p>First, register at <a href="https://pypi.python.org/">PyPI</a>, then complete your <code>~/.pypirc</code>:</p>
<div>
<pre>[distutils]
index-servers=
    pypi
    test

[test]
repository = https://testpypi.python.org/pypi
username = &lt;your test user name goes here&gt;
password = &lt;your test password goes here&gt;

[pypi]
repository = http://pypi.python.org/pypi
username = &lt;your production user name goes here&gt;
password = &lt;your production password goes here&gt;</pre>
</div>
<p>One last deep breath and let’s rock:</p>
<div>
<pre>$ python setup.py sdist upload -r pypi
$ python setup.py bdist_wheel upload -r pypi</pre>
</div>
<p>And thus, your package is only a <code>pip install</code> away for everyone! Congratulations, do more of that!</p>
<h2>Next Steps</h2>
<p>The information herein will probably get you pretty far but if you get stuck, the current <em>canonical</em> truths for Python packaging are:</p>
<ul>
<li><a href="https://python-packaging-user-guide.readthedocs.org/">Python Packaging User Guide</a></li>
<li><a href="http://pythonhosted.org/setuptools/">setuptools</a></li>
<li><a href="http://wheel.readthedocs.org/">wheel</a></li>
<li><a href="http://docs.python.org/2/library/distutils.html">distutils</a></li>
<li>and specifically the <a href="http://docs.python.org/2/distutils/packageindex.html">PyPI chapter</a> in the latter one.</li>
</ul>
<p>Please don’t let your software rot on GitHub or launchpad! Share!</p>
<h2>Thanks</h2>
<p>This article has been kindly proof-read by <a href="https://twitter.com/roguelynn">Lynn Root</a>,<a href="https://twitter.com/dstufft">Donald Stufft</a>, <a href="https://twitter.com/alex_gaynor/">Alex Gaynor</a>, <a href="http://sec.fu.cx/">Thomas Heinrichsdobler</a>, and<a href="https://twitter.com/jezdez">Jannis Leidel</a>. All mistakes are still mine though.</p>
<p>I’d be happy if you have any feedback to make this article even more straight-forward for people new to Python packaging!</p>
<p>文章出自：<a href="http://hynek.me/articles/sharing-your-labor-of-love-pypi-quick-and-dirty/">sharing-your-labor-of-love-pypi-quick-and-dirty</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/08/%e5%88%86%e4%ba%abpython%e6%a8%a1%e5%9d%97%e5%88%b0pypi/">分享Python模块到PyPI</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/08/%e5%88%86%e4%ba%abpython%e6%a8%a1%e5%9d%97%e5%88%b0pypi/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>python 脚本登录百度空间</title>
		<link>http://blog.zhourunsheng.com/2013/05/python-%e8%84%9a%e6%9c%ac%e7%99%bb%e5%bd%95%e7%99%be%e5%ba%a6%e7%a9%ba%e9%97%b4/</link>
		<comments>http://blog.zhourunsheng.com/2013/05/python-%e8%84%9a%e6%9c%ac%e7%99%bb%e5%bd%95%e7%99%be%e5%ba%a6%e7%a9%ba%e9%97%b4/#comments</comments>
		<pubDate>Wed, 15 May 2013 13:48:18 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[百度空间]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=1657</guid>
		<description><![CDATA[<p>最近半年的时间，百度空间进行了多次变更，自从去年的wordpress百度空间博文同步插件不可用之后，就一直没有 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/05/python-%e8%84%9a%e6%9c%ac%e7%99%bb%e5%bd%95%e7%99%be%e5%ba%a6%e7%a9%ba%e9%97%b4/">python 脚本登录百度空间</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>最近半年的时间，百度空间进行了多次变更，自从去年的wordpress百度空间博文同步插件不可用之后，就一直没有维护更新，最近百度空间基本稳定了，通过抓包对比分析，发现和以前的登录过程很不一样，先利用python脚本进行登录过程的模拟，然后再利用php实现，来维护更新同步插件。<span id="more-1657"></span></p>
<h3>大体的登录原理如下：</h3>
<p>1. 首先获取登录的cookie文件，没有cookie的话，百度空间不能正常登录，访问如下网址获取cookie</p>
<p>https://passport.baidu.com/v2/api/?getapi&#038;class=login&#038;tpl=mn&#038;tangram=false</p>
<p>2. 获取登录过程的token，同样再次请求上面的网址，但是这一次需要携带第1步中server返回的cookie信息</p>
<p>https://passport.baidu.com/v2/api/?getapi&#038;class=login&#038;tpl=mn&#038;tangram=false</p>
<p>3. 发送登录账号信息（用户名，密码等）到如下网址，同样需要携带步骤2中server返回的cookie信息</p>
<p>http://passport.baidu.com/v2/api/?login</p>
<p>4. 至此，登录过程完毕</p>
<h3>代码示例：</h3>
<p>下面的示例代码会登录百度空间，然后把所有的博文自动备份到本地，其中登录的过程就如上面的原理所讲。</p>
<pre>#!/usr/bin/python

#coding:utf8

import cookielib, urllib2, urllib

import os,sys,socket,re

#解析有多少页博客

pageStr = """var PagerInfo = {\s*allCount\s*:\s*'(\d+)',\s*pageSize\s*:\s*'(\d+)',\s*curPage\s*:\s*'\d+'\s*};"""

pageObj = re.compile(pageStr, re.DOTALL)

#获取登陆token

login_tokenStr = '''bdPass.api.params.login_token='(.*?)';'''

login_tokenObj = re.compile(login_tokenStr,re.DOTALL)

#获取博客标题和url

blogStr = r'''&lt;div&gt;&lt;a href=".*?" target=_blank&gt;.*?&lt;/a&gt;&lt;/div&gt;&lt;a href="(.*?)" target=_blank&gt;(.*?)&lt;/a&gt;&lt;/div&gt;'''

blogObj = re.compile(blogStr,re.DOTALL)

class Baidu(object):

    def __init__(self,user = '', psw = '', blog = ''):

        self.user = user

        self.psw  = psw

        self.blog = blog

        if not user or not psw or not blog:

            print "Plz enter enter 3 params:user,psw,blog"

            sys.exit(0)

        if not os.path.exists(self.user):

            os.mkdir(self.user)

        self.cookiename = 'baidu%s.coockie' % (self.user)

        self.token = ''

        self.allCount  = 0

        self.pageSize  = 10

        self.totalpage = 0

        self.logined = False

        self.cj = cookielib.LWPCookieJar()

        try:

            self.cj.revert(self.cookiename)

            self.logined = True

            print "OK"

        except Exception, e:

            print e

        self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj))

        self.opener.addheaders = [('User-agent','Opera/9.23')]

        urllib2.install_opener(self.opener)

        socket.setdefaulttimeout(30)

    #登陆百度

    def login(self):

        #如果没有获取到cookie，就模拟登陆

        if not self.logined:

            print "logon to baidu ..."

            #第一次先访问一下，目的是为了先保存一个cookie下来

            qurl = '''https://passport.baidu.com/v2/api/?getapi&amp;class=login&amp;tpl=mn&amp;tangram=false'''

            r = self.opener.open(qurl)

            self.cj.save(self.cookiename)

            #第二次访问，目的是为了获取token

            qurl = '''https://passport.baidu.com/v2/api/?getapi&amp;class=login&amp;tpl=mn&amp;tangram=false'''

            r = self.opener.open(qurl)

            rsp = r.read()

            #print rsp

            self.cj.save(self.cookiename)

            #通过正则表达式获取token

            matched_objs = login_tokenObj.findall(rsp)

            if matched_objs:

                self.token = matched_objs[0]

                print 'token =', self.token

                #然后用token模拟登陆

                post_data = urllib.urlencode({'username':self.user,

                                              'password':self.psw,

                                              'token':self.token,

                                              'charset':'UTF-8',

                                              'callback':'parent.bd12Pass.api.login._postCallback',

                                              'index':'0',

                                              'isPhone':'false',

                                              'mem_pass':'on',

                                              'loginType':'1',

                                              'safeflg':'0',

                                              'staticpage':'https://passport.baidu.com/v2Jump.html',

                                              'tpl':'mn',

                                              'u':'http://www.baidu.com/',

                                              'verifycode':'',

                                            })

                #path = 'http://passport.baidu.com/?login'

                path = 'http://passport.baidu.com/v2/api/?login'

                self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(self.cj))

                self.opener.addheaders = [('User-agent','Opera/9.23')]

                urllib2.install_opener(self.opener)

                headers = {

                  "Accept": "image/gif, */*",

                  "Referer": "https://passport.baidu.com/v2/?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2F",

                  "Accept-Language": "zh-cn",

                  "Content-Type": "application/x-www-form-urlencoded",

                  "Accept-Encoding": "gzip, deflate",

                  "User-Agent": "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 2.0.50727)",

                  "Host": "passport.baidu.com",

                  "Connection": "Keep-Alive",

                  "Cache-Control": "no-cache"

                }

                req = urllib2.Request(path,

                                post_data,

                                headers=headers,

                                )

                rsp = self.opener.open(req).read()

                #print rsp

                self.cj.save(self.cookiename)

                #for login test

                #qurl = '''http://hi.baidu.com/pub/show/createtext'''

                #rsp = self.opener.open(qurl).read()

                #file_object = open('login.txt', 'w')

                #file_object.write(rsp)

                #file_object.close()

            else:

                print "Login Fail"

                sys.exit(0)

    #获取博客一共有多少页，如果有私有博文的话，登陆和不登陆获取的是不一样的

    def getTotalPage(self):

        #获取博客的总页数

        req2 = urllib2.Request(self.blog)

        rsp = urllib2.urlopen(req2).read()

        if rsp:

            rsp = rsp.replace('\r','').replace('\n','').replace('\t','')

            matched_objs = pageObj.findall(rsp)

            if matched_objs:

                obj0,obj1 = matched_objs[0]

                self.allCount = int(obj0)

                self.pageSize = int(obj1)

                self.totalpage = (self.allCount / self.pageSize) + 1

                print 'allCount:%d, pageSize:%d, totalpage:%d' % (self.allCount,self.pageSize,self.totalpage)

    #获取每一页里的博客链接

    def fetchPage(self,url):

        req = urllib2.Request(url)

        rsp = urllib2.urlopen(req).read()

        if rsp:

            rsp = rsp.replace('\r','').replace('\n','').replace('\t','')

            matched_objs = blogObj.findall(rsp)

            if matched_objs:

                for obj in matched_objs:

                    #这里可以用多线程改写一下,单线程太慢

                    self.download(obj[0],obj[1])

    def downloadBywinget(self,url,title):

        #比如使用wget之类的第三方工具，自己填参数写

        pass

    #下载博客

    def download(self,url,title):

        path = '%s/%s.html' % (self.user,title.decode('utf-8'))

        url = 'http://hi.baidu.com%s' % (url)

        print "Download url %s" % (url)

        nFail = 0

        while nFail &lt; 5:

            try:

                sock = urllib.urlopen(url)

                htmlSource = sock.read()

                myfile = file(path,'w')

                myfile.write(htmlSource)

                myfile.close()

                sock.close()

                return

            except:

                nFail += 1

        print ('download blog fail:%s' % (url))

    def dlownloadall(self):

        for page in range(1,self.totalpage+1):

            url = "%s?page=%d" % (self.blog,page)

            #这里可以用多线程改写一下,单线程太慢

            self.fetchPage(url)

def main():

    user = 'runsheng2005'       #你的百度登录名

    psw  = 'password'  #你的百度登陆密码,不输入用户名和密码，得不到私有的文章

    blog = "http://hi.baidu.com/zhourunsheng" #你自己的百度博客链接

    baidu = Baidu(user,psw,blog)

    baidu.login()

    baidu.getTotalPage()

    baidu.dlownloadall()

if __name__ == '__main__':

    main()</pre>
<p>例如，我的用户名是<strong><em>runsheng2005</em></strong>，则会在工作目录建立一个名为<strong><em>baidurunsheng2005.coockie</em></strong>的文件用来保存cookie信息，</p>
<p>其中的内容如下：</p>
<pre>#LWP-Cookies-2.0
Set-Cookie3: BAIDUID="25B820FB17B13E5F4F7C9836FB465C96:FG=1"; path="/"; domain=".baidu.com"; path_spec; domain_dot; expires="2043-05-07 14:28:07Z"; version=0
Set-Cookie3: BDUSS=mJjbjFrZmp3WXNNbUhIQUxkWDJIMjFaR2dSZjdLaHdwcnhhRDBRLVNxcjQxcmxSQVFBQUFBJCQAAAAAAAAAAAEAAABv9HAAcnVuc2hlbmcyMDA1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPhJklH4SZJRW; path="/"; domain=".baidu.com"; path_spec; expires="2021-07-31 14:28:08Z"; version=0
Set-Cookie3: HOSUPPORT=1; path="/"; domain=".passport.baidu.com"; path_spec; expires="2021-07-31 14:28:07Z"; httponly=None; version=0
Set-Cookie3: PTOKEN=2bb1ab99373dbeeeec6b69af75e6a4c6; path="/"; domain=".passport.baidu.com"; path_spec; expires="2021-07-31 14:28:08Z"; version=0
Set-Cookie3: SAVEUSERID=a00277ba04dba8956259a5c4dfec4d40; path="/"; domain=".passport.baidu.com"; path_spec; expires="2021-07-31 14:28:08Z"; version=0
Set-Cookie3: STOKEN=1f4790267126b2e7dddb1f735f29074f; path="/"; domain=".passport.baidu.com"; path_spec; expires="2021-07-31 14:28:08Z"; version=0</pre>
<p>在名为<strong><em>runsheng2005</em></strong>的子目录下面，会下载所有的博文<br />
例如 <em>打造个人的云端笔记本（CareyDiary）.html</em>等等</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2013/05/python-%e8%84%9a%e6%9c%ac%e7%99%bb%e5%bd%95%e7%99%be%e5%ba%a6%e7%a9%ba%e9%97%b4/">python 脚本登录百度空间</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2013/05/python-%e8%84%9a%e6%9c%ac%e7%99%bb%e5%bd%95%e7%99%be%e5%ba%a6%e7%a9%ba%e9%97%b4/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>混乱代码分享之 Python 程序设计</title>
		<link>http://blog.zhourunsheng.com/2011/09/%e6%b7%b7%e4%b9%b1%e4%bb%a3%e7%a0%81%e5%88%86%e4%ba%ab%e4%b9%8b-python-%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1/</link>
		<comments>http://blog.zhourunsheng.com/2011/09/%e6%b7%b7%e4%b9%b1%e4%bb%a3%e7%a0%81%e5%88%86%e4%ba%ab%e4%b9%8b-python-%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1/#comments</comments>
		<pubDate>Mon, 12 Sep 2011 01:19:07 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=874</guid>
		<description><![CDATA[<p>Python代码历来是讲究整洁的，但是也不乏高手能写出如此混乱而又有节奏的代码，下面的混乱代码就是用来生成彭罗 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2011/09/%e6%b7%b7%e4%b9%b1%e4%bb%a3%e7%a0%81%e5%88%86%e4%ba%ab%e4%b9%8b-python-%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1/">混乱代码分享之 Python 程序设计</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>Python代码历来是讲究整洁的，但是也不乏高手能写出如此混乱而又有节奏的代码，下面的混乱代码就是用来生成<a href="http://en.wikipedia.org/wiki/Penrose_tiling">彭罗斯铺砖</a>图案的。</p>
<h4>程序源码</h4>
<pre>_                                 =
                                """if!
                              1:"e,V=100
                            0,(0j-1)**-.2;
                           v,S=.5/  V.real,
                         [(0,0,4      *e,4*e*
                       V)];w=1          -v"def!
                      E(T,A,              B,C):P
                  ,Q,R=B*w+                A*v,B*w+C
            *v,A*w+B*v;retur              n[(1,Q,C,A),(1,P
     ,Q,B),(0,Q,P,A)]*T+[(0,C            ,R,B),(1,R,C,A)]*(1-T)"f
or!i!in!_[:11]:S       =sum([E          (*x)for       !x!in!S],[])"imp
  ort!cair               o!as!O;      s=O.Ima               geSurfac
   e(1,e,e)               ;c=O.Con  text(s);               M,L,G=c.
     move_to                ,c.line_to,c.s                et_sour
       ce_rgb                a"def!z(f,a)                :f(-a.
        imag,a.       real-e-e)"for!T,A,B,C!in[i       !for!i!
          in!S!if!i[""";exec(reduce(lambda x,i:x.replace(chr
           (i),"n "[34-i:]),   range(   35),_+"""0]]:z(M,A
             );z(L,B);z         (L,C);         c.close_pa
             th()"G             (.4,.3             ,1);c.
             paint(             );G(.7             ,.7,1)
             ;c.fil             l()"fo             r!i!in
             !range             (9):"!             g=1-i/
             8;d=i/          4*g;G(d,d,d,          1-g*.8
             )"!def     !y(f,a):z(f,a+(1+2j)*(     1j**(i
             /2.))*g)"!for!T,A,B,C!in!S:y(M,C);y(L,A);y(M
             ,A);y(L,B)"!c.st            roke()"s.write_t
             o_png('pen                        rose.png')
             """                                       ))</pre>
<p><span id="more-874"></span></p>
<h4>执行效果</h4>
<p><a href="http://blog.zhourunsheng.com/wp-content/uploads/2011/09/penrose.png"><img class="alignnone size-full wp-image-876" title="penrose" src="http://blog.zhourunsheng.com/wp-content/uploads/2011/09/penrose.png" alt="" width="1000" height="1000" /></a></p>
<h4>环境配置</h4>
<p>Python运行时环境需要 Python &lt;= 2.7 版本，我用的是Python2.6.4</p>
<pre>C:UsersCarey.RS.Zhou&gt;python
Python 2.6.4 (r264:75708, Oct 26 2009, 08:23:19) [MSC v.1500 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
&gt;&gt;&gt;</pre>
<p>还需要图形库 <a href="http://cairographics.org/pycairo/">Pycairo</a> 的支持，win的便捷安装请用 <a href="http://ftp.gnome.org/pub/GNOME/binaries/win32/pygtk/2.24/pygtk-all-in-one-2.24.0.win32-py2.6.msi">pygtk-all-in-one-2.24.0.win32-py2.6.msi</a></p>
<h4>源码下载</h4>
<p><a href="http://blog.zhourunsheng.com/wp-content/uploads/2011/09/penrose-tiling.zip">penrose-tiling</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2011/09/%e6%b7%b7%e4%b9%b1%e4%bb%a3%e7%a0%81%e5%88%86%e4%ba%ab%e4%b9%8b-python-%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1/">混乱代码分享之 Python 程序设计</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2011/09/%e6%b7%b7%e4%b9%b1%e4%bb%a3%e7%a0%81%e5%88%86%e4%ba%ab%e4%b9%8b-python-%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>为什么每个程序设计者都应该学习 Python 或者 Ruby</title>
		<link>http://blog.zhourunsheng.com/2011/07/%e4%b8%ba%e4%bb%80%e4%b9%88%e6%af%8f%e4%b8%aa%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1%e8%80%85%e9%83%bd%e5%ba%94%e8%af%a5%e5%ad%a6%e4%b9%a0-python-%e6%88%96%e8%80%85-ruby/</link>
		<comments>http://blog.zhourunsheng.com/2011/07/%e4%b8%ba%e4%bb%80%e4%b9%88%e6%af%8f%e4%b8%aa%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1%e8%80%85%e9%83%bd%e5%ba%94%e8%af%a5%e5%ad%a6%e4%b9%a0-python-%e6%88%96%e8%80%85-ruby/#comments</comments>
		<pubDate>Mon, 25 Jul 2011 02:47:13 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Ruby]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=481</guid>
		<description><![CDATA[<p>这是一篇选自外文的评论文章，该文告诉我们为什么要选择python或者ruby，他们相比较其他的编程语言有哪些优 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2011/07/%e4%b8%ba%e4%bb%80%e4%b9%88%e6%af%8f%e4%b8%aa%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1%e8%80%85%e9%83%bd%e5%ba%94%e8%af%a5%e5%ad%a6%e4%b9%a0-python-%e6%88%96%e8%80%85-ruby/">为什么每个程序设计者都应该学习 Python 或者 Ruby</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>这是一篇选自外文的评论文章，该文告诉我们为什么要选择python或者ruby，他们相比较其他的编程语言有哪些优势，对我们以后的开发会有怎样的帮助。</p>
<p>=========== 英文原文============</p>
<p>If you are a student, you probably know C, C++ and Java. A few know VB, or C# / .NET. At some point you’ve probably built some web pages, so you know HTML, CSS and maybe JavaScript. By and large, it is difficult to find students who have any exposure to languages beyond this. And this is a shame because there are a number of programming languages out there which will make you a better programmer.</p>
<p>In this article, we give some reasons why you must learn Python or Ruby<sup>2</sup>.</p>
<p><span id="more-481"></span></p>
<ul>
<li>Compared to <strong>C/C++/Java</strong> – Python/Ruby allow you to write the same program with much, much fewer lines of code. It is estimated that a typical Python or Ruby program will require 5 times fewer lines of code than a corresponding Java code. Why spend that much more time on writing programs unless it is absolutely necessary? Also, <a href="http://www.artima.com/intv/speed.html">someone said that a good programmer can reasonably maintain 20000 lines of code</a>. It does not matter whether those are in assembly, C, or Python/Ruby/PHP/Lisp. So, if you write in Python/Ruby, whatever you do alone would probably need a 5-person team in Java/C/C++.</li>
<li>Compared to <strong>VB/PHP</strong> – Python/Ruby are much, much better designed languages than PHP/VB. PHP and VB are very popular for writing websites, and desktop applications respectively. The reason they’re popular is that they are very easy to learn and even non-programmers can pick them up quickly. But write any large program in these languages and you’ll start seeing the huge problems with these languages because they’re so badly designed. Friends don’t let friends program in PHP/VB.</li>
<li>Compared to <strong>Lisp/Scala/Haskell/Closure/Erlang</strong> – Python/Ruby are still quite “mainstream”. Sure these languages have some really cool features, and for advanced programmers, exposure to these languages can really improve the way they think about programming. But there will be time later in your career to decide whether you want to pick up one or more of these. But for now, Python/Ruby do a much better job of balancing the power of the language against commercial applicability.</li>
<li>Compared to <strong>Perl</strong><sup>1</sup> – Both Python &amp; Ruby owe a lot to Perl, and Perl was the biggest and best dynamic language before they started gaining prominence. But now, Perl’s popularity is reducing and more and more people are adopting Ruby/Python. I find Perl’s object-orientedness a bit contrived and ugly. In general, I think Perl is a harder language to learn since it has so many different ways of doing things, and the syntax tends to be cryptic and non-intuitive until you get the hang of it. Overall, I feel that Perl is not the best language for a student to pick up, unless there is a very good reason to do so (<em>i.e.</em> if you have lots of regular expression processing, then Perl shines)</li>
<li>Compared to <strong>sh/sed/awk/bash</strong> – If you have exposure to Linux/Unix, you have probably done some shell programming, and might even have written non-trivial programs. But anything more than a few lines in these languages starts to become a bit painful and it’s much better to do this in Python. Of course, Perl is the best language for this, but Python is a close second. (Ruby is not so great for system shell scripting).</li>
</ul>
<p>Just do a Google search on ‘Why is X better than Y’ – where you put Python or Ruby for X and put one of the other languages for Y – and you will find a whole bunch of material on why these languages are so good.</p>
<p>If you have the flexibility to choose the programming language for you final year project, then pick Python or Ruby and get done in half the time that it would have required you to do the project (except if it is a mobile app development project, in which case you’ll be forced to use Java or Objective-C).</p>
<p>Here is a <a href="http://xkcd.com/353/">cartoon from xkcd which gives an idea of how powerful you feel after having mastered Python</a>:</p>
<p><a href="http://blog.zhourunsheng.com/wp-content/uploads/2011/07/python-cartoon.png"><img class="alignnone size-full wp-image-482" title="python-cartoon" src="http://blog.zhourunsheng.com/wp-content/uploads/2011/07/python-cartoon.png" alt="" width="518" height="588" /></a></p>
<p>How to get started? There are many, many website that give tutorials and classes on Python and Ruby. Here are just a couple of them that we’ve chosen:</p>
<ul>
<li><a href="http://code.google.com/edu/languages/google-python-class/">Google’s Python Class</a> is a good resource for learning Python</li>
<li><a href="http://rubylearning.org/">RubyLearning</a> is a great website where you can learn Ruby.</li>
</ul>
<p>Questions? Ask in the comments below, and we’ll try to answer them.</p>
<p><em>Footnote:</em></p>
<p><em> </em></p>
<p><em><sup>1</sup>: My post seems to have pissed of a lot of Perl fans, and in retrospect I realized that I was harsher on the language than I should have been. Hence I’ve changed the Perl section. Earlier it read:</em></p>
<p><em> </em></p>
<p><em> </em></p>
<p><em></p>
<blockquote><p>Both Python &amp; Ruby owe a lot to Perl, and Perl was the biggest and best dynamic language before they showed up. But Perl is now old. It’s object-orientedness is broken. It hasn’t really been updated in a while, and it is losing market share. For new, hot things (like web programming frameworks, web APIs) it is not as up-to-date as Python &amp; Ruby. Basically, Python/Ruby are rising, Perl is setting.</p></blockquote>
<p>Please keep this in mind when reading the comments of Lars, Torsten and Olaf.</p>
<p><sup>2</sup> All the language comparisons made in this article are for the context of students in Indian CS degree programs picking up a new programming language. A statement like “X is better than Y” will never make sense as an absolute statement because all languages that have survived the test of time are obviously better than other languages in some areas, and that is the reason they exist. In other words, there are always scenarios where PHP/Java/C/C++/Perl and others are better languages than Ruby/Python.</p>
<p></em></p>
<p><em> </em></p>
<p><em> </em></p>
<p>&nbsp;</p>
<p>=========== 中文翻译============</p>
<p>如果你是个学生,你应该会C，C++和Java。还会一些VB，或C#/.NET。多少你还可能开发过一些Web网页，你知道一些HTML，CSS和JavaScript知识。总体上说，我们很难发现会有学生显露出掌握超出这几种语言范围外的语言的才能。这真让人遗憾，因为还有很多种编程语言，它们能让你成为一个更好的程序员。</p>
<p>在这篇文章里，我将会告诉你，为什么你一定要学习Python或Ruby语言。</p>
<ul>
<li>跟<strong>C/C++/Java</strong>相比 — Python/Ruby能让你用少的多的多的代码写出相同的程序。有人计算过，Python或Ruby写出的程序的代码行数只相当于相对应的Java代码的行数的五分之一。如果没有绝对的必要，为什么要花这么多时间写出这么多的代码呢？而且，<a href="http://www.artima.com/intv/speed.html">有人说</a>，一个优秀的程序员能维护的代码量最多是2万行。这不区分用的语言究竟是汇编，C还是Python/Ruby/PHP/Lisp。所以，如果你用Python/Ruby写，你一个人干的，不管是干什么，如果换用Java/C/C++，那都需要一个5人的小团队来干。</li>
<li>跟<strong>VB/PHP</strong>比较 — 跟PHP/VB相比，Python/Ruby的是一种从设计上讲比它们好的不知多少倍的语言。PHP和VB分别是在开发网站和桌面应用程序上非常流行的语言。它们流行的原因是非常的易学。不懂计算机的人也很容易的上手。如果你用这些语言开发过大型的项目，你就会发现这些语言的设计是如此的糟糕。是朋友，他就不会劝你使用PHP/VB。</li>
<li>跟<strong>Lisp/Scala/Haskell/Closure/Erlang</strong>相比 — Python/Ruby跟它们比起来显得相当的“主流”。确实，这些语言每种都有其很酷的特征，对于高级编程人员，了解这些语言能给他们对编程的思考带来实际的提升。但这些应该在你以后的职业生涯中才去决定学哪一两种。对于现在，Python/Ruby是在语言功能和实际运用之间平衡后的更好的选择。</li>
<li>跟<strong>Perl</strong>相比 — Python和Ruby都受恩于Perl，在这两种语言异军突起前，Perl是最好、最大的一种动态语言。但现在，Perl已是昨日黄花，越来越多的人转向Ruby/Python。我感觉Perl的面向对象机制有点做作，很不好用。通常认为，Perl一种比较难学的语言，因为它提供你了太多不同的方法去完成同一个任务，它的语法有点像密码，非常不直观 — 除非你对它掌握的非常好。总之，我感觉Perl是一种对于学生来说不是很合适的语言—除非你有特殊的理由去学它(例如，你有很多正则表达式要处理，这是Perl的闪光点)。</li>
<li>跟<strong>sh/sed/awk/bash</strong>相比 — 如果你使用Linux/Unix，你可能需要做一些shell编程，甚至会编写一些不小的程序。但是，对于这些语言，一旦程序达到一定的行数，事情就会开始变得让你痛苦不堪，你最好是用Python去做这些事情。当然，做这种事情，Perl是最好的选择，Python排第二。(Ruby对于系统shell脚本不是很合适)。</li>
</ul>
<p>你可以在Google上搜一下“为什么X比Y好” — 其中把X换成Python或Ruby，把Y换成另外一种语言 — 你就会发现，有无数的文章来说明它们为什么这么好。</p>
<p>如果你有选择你的毕业设计使用的编程语言的自由，你应该选择Python或Ruby，它们能让你在开发项目的过程中节省一半的时间(除非你要开发的是移动应用，这样你必须要使用Java或Objective-C)。</p>
<p>下面是<a href="http://xkcd.com/353/">xkcd</a>上的一幅漫画，告诉你掌握Python后你会变得多么的强大：</p>
<p><a href="http://blog.zhourunsheng.com/wp-content/uploads/2011/07/python-cartoon.png"><img class="alignnone size-full wp-image-482" title="python-cartoon" src="http://blog.zhourunsheng.com/wp-content/uploads/2011/07/python-cartoon.png" alt="" width="518" height="588" /></a></p>
<p>如何去学它们呢？很多很多的网站上都提供了学习Python和Ruby的教材和课程。下面的是我从中选出的一些：</p>
<ul>
<li><a href="http://code.google.com/edu/languages/google-python-class/">谷歌的Python课程</a>，学习Python的好资源。</li>
<li><a href="http://rubylearning.org/">RubyLearning</a>，学习Ruby的一个好网站。</li>
</ul>
<p>有疑问吗？请在评论了写出来，我会尽量回答你们。</p>
<p><em>尾注：</em></p>
<p><sup>1</sup>：我的这篇文章可能会让很多Perl爱好者很郁闷，现在回味一下，我认识到对这种语言的要求过于苛刻了。因此，我把关于Perl的一节改写了一下。</p>
<blockquote><p>Python和Ruby都受恩于Perl，在这两种语言出现之前，Perl是最大、最好的动态语言。但Perl现在太老了。它的面向对象性不完整。它很久没有升级更新了，它的市场份额正在丢失。对于一些新的、很火的事物(例如Web编程框架，Web API)，它不如Python &amp; Ruby 那样能跟上时代的步伐。基本上，Python/Ruby在兴起，Perl在衰退。</p></blockquote>
<p><sup>2</sup>：本文中的所有语言的比较都是用来给印度计算机科学专业的学生选编程语言时做参考的。像“X比Y好”这样的句子准确的讲是毫无意义的，因为所有的语言都是经过时间的考验而存活下来的，有些语言会在某些领域比另外一种要强，这也是它们存活下来的原因。换句话说，总有一些情况下，PHP/Java/C/C++/Perl 看起来会比 Ruby/Python 等其它语言显的更适合。</p>
<hr />
<p>英文链接：<a href="http://reliscore.com/why-every-programmer-should-learn-python-or-ruby">http://reliscore.com/why-every-programmer-should-learn-python-or-ruby</a></p>
<p>中文链接：<a href="http://www.aqee.net/2011/07/25/why-every-programmer-should-learn-python-or-ruby/">http://www.aqee.net/2011/07/25/why-every-programmer-should-learn-python-or-ruby/</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2011/07/%e4%b8%ba%e4%bb%80%e4%b9%88%e6%af%8f%e4%b8%aa%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1%e8%80%85%e9%83%bd%e5%ba%94%e8%af%a5%e5%ad%a6%e4%b9%a0-python-%e6%88%96%e8%80%85-ruby/">为什么每个程序设计者都应该学习 Python 或者 Ruby</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2011/07/%e4%b8%ba%e4%bb%80%e4%b9%88%e6%af%8f%e4%b8%aa%e7%a8%8b%e5%ba%8f%e8%ae%be%e8%ae%a1%e8%80%85%e9%83%bd%e5%ba%94%e8%af%a5%e5%ad%a6%e4%b9%a0-python-%e6%88%96%e8%80%85-ruby/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>如何成为Python高手</title>
		<link>http://blog.zhourunsheng.com/2011/06/%e5%a6%82%e4%bd%95%e6%88%90%e4%b8%bapython%e9%ab%98%e6%89%8b/</link>
		<comments>http://blog.zhourunsheng.com/2011/06/%e5%a6%82%e4%bd%95%e6%88%90%e4%b8%bapython%e9%ab%98%e6%89%8b/#comments</comments>
		<pubDate>Thu, 23 Jun 2011 01:14:23 +0000</pubDate>
		<dc:creator><![CDATA[润物无声]]></dc:creator>
				<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Python]]></category>

		<guid isPermaLink="false">http://blog.zhourunsheng.com/?p=9</guid>
		<description><![CDATA[<p>本文是从 How to become a proficient Python programmer 这篇文章翻 [&#8230;]</p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2011/06/%e5%a6%82%e4%bd%95%e6%88%90%e4%b8%bapython%e9%ab%98%e6%89%8b/">如何成为Python高手</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></description>
				<content:encoded><![CDATA[<p>本文是从 <a href="http://blog.dispatched.ch/2011/06/12/how-to-become-a-proficient-python-programmer/">How to become a proficient Python programmer</a> 这篇文章翻译而来。</p>
<p>这篇文章主要是对我收集的一些文章的摘要。因为已经有很多比我有才华的人写出了大量关于如何成为优秀Python程序员的好文章。</p>
<p>我的总结主要集中在四个基本题目上：函数式编程，性能，测试，编码规范。</p>
<p>如果一个程序员能将这四个方面的内容知识都吸收消化，那他/她不管怎样都会有巨大的收获。</p>
<h4>函数式编程</h4>
<p>命令式的编程风格已经成为事实上的标准。命令式编程的程序是由一些描述状态转变的语句组成。虽然有时候这种编程方式十分的有效，但有时也不尽如此(比如复杂性) —— 而且，相对于声明式编程方式，它可能会显得不是很直观。</p>
<p>如果你不明白我究竟是在说什么，这很正常。这里有一些文章能让你脑袋开窍。但你要注意，这些文章有点像《骇客帝国》里的红色药丸 —— 一旦你尝试过了函数式编程，你就永远不会回头了。</p>
<ul>
<li><a href="http://www.amk.ca/python/writing/functional">http://www.amk.ca/python/writing/functional</a></li>
<li><a href="http://www.secnetix.de/olli/Python/lambda_functions.hawk">http://www.secnetix.de/olli/Python/lambda_functions.hawk</a></li>
<li><a href="http://docs.python.org/howto/functional.html">http://docs.python.org/howto/functional.html</a></li>
</ul>
<h4>性能</h4>
<p>你会看到有如此多的讨论都在批评这些“脚本语言”(Python，Ruby)是如何的性能低下，可是你却经常的容易忽略这样的事实：是程序员使用的算法导致了程序这样拙劣的表现。</p>
<p>这里有一些非常好的文章，能让你知道Python的运行时性能表现的细节详情，你会发现，通过这些精炼而且有趣的语言，你也能写出高性能的应用程 序。而且，当你的老板质疑Python的性能时，你别忘了告诉他，这世界上第二大的搜索引擎就是用Python写成的 —— 它叫做Youtube(参考<a href="http://www.python.org/about/quotes/">Python摘录</a>)</p>
<ul>
<li><a href="http://jaynes.colorado.edu/PythonIdioms.html">http://jaynes.colorado.edu/PythonIdioms.html</a></li>
<li><a href="http://wiki.python.org/moin/PythonSpeed/PerformanceTips">http://wiki.python.org/moin/PythonSpeed/PerformanceTips</a></li>
</ul>
<h4>测试</h4>
<p>如今在计算机科学界，测试可能是一个最让人不知所措的主题了。有些程序员能真正的理解它，十分重视TDD(测试驱动开发)和它的后继者BDD(行为 驱动开发)。而另外一些根本不接受，认为这是浪费时间。那么，我现在将告诉你：如果你不曾开始使用TDD/BDD，那你错过了很多最好的东西！</p>
<p>这并不只是说引入了一种技术，可以替换你的公司里那种通过愚蠢的手工点击测试应用程序的原始发布管理制度，更重要的是，它是一种能够让你深入理解你 自己的业务领域的工具 —— 真正的你需要的、你想要的攻克问题、处理问题的方式。如果你还没有这样做，请试一下。下面的这些文章将会给你一些提示：</p>
<ul>
<li><a href="http://www.oreillynet.com/lpt/a/5463">http://www.oreillynet.com/lpt/a/5463</a></li>
<li><a href="http://www.oreillynet.com/lpt/a/5584">http://www.oreillynet.com/lpt/a/5584</a></li>
<li><a href="http://wiki.cacr.caltech.edu/danse/index.php/Unit_testing_and_Integration_testing">http://wiki.cacr.caltech.edu/danse/index.php/Unit_testing_and_Integration_testing</a></li>
<li><a href="http://docs.python.org/library/unittest.html">http://docs.python.org/library/unittest.html</a></li>
</ul>
<h4>编码规范</h4>
<p>并非所有的代码生来平等。有些代码可以被另外的任何一个好的程序员读懂和修改。但有些却只能被读，而且只能被代码的原始作者修改 —— 而且这也只是在他或她写出了这代码的几小时内可以。为什么会这样？因为没有经过代码测试(上面说的)和缺乏正确的编程规范。</p>
<p>下面的文章给你描述了一个最小的应该遵守的规范合集。如果按照这些指导原则，你将能编写出更简洁和漂亮的代码。作为附加效应，你的程序会变得可读性更好，更容易的被你和任何其他人修改。</p>
<ul>
<li><a href="http://www.python.org/dev/peps/pep-0008/">http://www.python.org/dev/peps/pep-0008/</a></li>
<li><a href="http://www.fantascienza.net/leonardo/ar/python_best_practices.html">http://www.fantascienza.net/leonardo/ar/python_best_practices.html</a></li>
</ul>
<p>那就去传阅这这些资料吧。从坐在你身边的人开始。也许在下一次程序员沙龙或编程大会的时候，也已经成为一名Python编程高手了！</p>
<p>祝你学习旅途顺利。</p>
<p>如果你喜欢这些文章，请在微博上顶一下，让其他人也知道。</p>
<p>&nbsp;</p>
<p>本文转自：<a href="http://www.aqee.net/2011/06/23/how-to-become-a-proficient-python-programmer/">http://www.aqee.net/2011/06/23/how-to-become-a-proficient-python-programmer/</a></p>
<p><a rel="nofollow" href="http://blog.zhourunsheng.com/2011/06/%e5%a6%82%e4%bd%95%e6%88%90%e4%b8%bapython%e9%ab%98%e6%89%8b/">如何成为Python高手</a>，首发于<a rel="nofollow" href="http://blog.zhourunsheng.com">润物无声</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.zhourunsheng.com/2011/06/%e5%a6%82%e4%bd%95%e6%88%90%e4%b8%bapython%e9%ab%98%e6%89%8b/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
