<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Python on kamuszhou&#39;s blog</title>
    <link>https://ykyi.net/tags/python/</link>
    <description>Recent content in Python on kamuszhou&#39;s blog</description>
    <generator>Hugo</generator>
    <language>zh-cn</language>
    <lastBuildDate>Fri, 11 Oct 2019 11:55:07 +0000</lastBuildDate>
    <atom:link href="https://ykyi.net/tags/python/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>python用multiprocessing起子进程，父进程被杀掉时，如何让子进程也自动退出</title>
      <link>https://ykyi.net/?p=1612/</link>
      <pubDate>Fri, 11 Oct 2019 11:55:07 +0000</pubDate>
      <guid>https://ykyi.net/?p=1612/</guid>
      <description>&lt;p&gt;最近用python3写了一段代码，代码会起一个子进程做一些辅助的事情。但发现父进程被杀死后，这个子进程变成了孤儿进程，仍然在运行。&lt;br&gt;
起进程的代码大概如下：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python &#34;&gt;from multiprocessing import Process, Queue
__q = Queue()
__p = Process(target=__run, args=(__q,))
__p.start()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很不方便，那怎样让父进程退出后，子进程也跟着退出呢。&lt;br&gt;
一番google之后，原来可以用一行代码解决：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python &#34;&gt;from multiprocessing import Process, Queue
__q = Queue()
__p = Process(target=__run, args=(__q,))
__p.daemon = True    # 加上这一行代码
__p.start()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;设置子进程的daemon属性为True。这样，当在控制台上用ctrl-c，退发送SIGTERM杀掉父进程后，子进程也会跟着退出，不会成为孤儿进程。&lt;br&gt;
参考python官方的解释：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;daemon&lt;br&gt;
The process’s daemon flag, a Boolean value. This must be set before start() is called.&lt;br&gt;
The initial value is inherited from the creating process.&lt;br&gt;
When a process exits, it attempts to terminate all of its daemonic child processes.&lt;/p&gt;
&lt;/blockquote&gt;</description>
    </item>
    <item>
      <title>python自制包并用pip免提交到pypi仅安装到本机</title>
      <link>https://ykyi.net/?p=1588/</link>
      <pubDate>Sun, 02 Jun 2019 14:31:10 +0000</pubDate>
      <guid>https://ykyi.net/?p=1588/</guid>
      <description>&lt;p&gt;不得不说python的自制包的相关工具真是多且混乱，什么setuptools,什么distutils，什么wheel，什么egg！！怎么有这么多啊？？&lt;/p&gt;
&lt;p&gt;而且我的需求且且是创建一个自制包管理自己常用的代码，也必不想提交到PyPI，仅仅只需要安装到本机就行。&lt;/p&gt;
&lt;p&gt;下面就是几个关键步骤。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;文件目录布局&lt;br&gt;
├── package1&lt;br&gt;
│ └──-├── &lt;strong&gt;init&lt;/strong&gt;.py&lt;br&gt;
| |── mod1.py&lt;br&gt;
│ └── mod2.py&lt;br&gt;
├── setup.py&lt;br&gt;
|── README.md&lt;/li&gt;
&lt;li&gt;编写setup.py文件，类似如下：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&#34;language-python &#34;&gt;from setuptools import setup, find_packages

setup(
    name=&#34;kamustools&#34;,
    version=&#34;1.0.1&#34;,
    author=&#34;Kamuszhou&#34;,
    author_email=&#34;zausiu@gmail.com&#34;,
    description=&#34;tools used by kamuszhou exclusively.&#34;,
    license=&#34;BSD&#34;,
    keywords=&#34;kamus&#34;,
    url=&#34;https://ykyi.net&#34;,
    packages=find_packages(),
    long_description=&#34;Long descrition is actually short...&#34;,
    classifiers=[
        &#34;Development Status :: 3 - Alpha&#34;,
        &#34;Topic :: Utilities&#34;,
        &#34;License :: OSI Approved :: BSD License&#34;,
    ],
)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol start=&#34;3&#34;&gt;
  &lt;li&gt;
    python3 setup.py sdist bdist_wheel
  &lt;/li&gt;
  &lt;li&gt;
    pip install ./dist/kamustools-1.0.1.tar.gz
  &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;就这四步，其他各种功能，以后要用了再慢慢看吧，实在太庞杂了。满足我自己需求的这四个简单步骤就记录在这里。&lt;/p&gt;</description>
    </item>
    <item>
      <title>如何定制一个python的logging Handler</title>
      <link>https://ykyi.net/?p=1545/</link>
      <pubDate>Tue, 20 Nov 2018 11:16:45 +0000</pubDate>
      <guid>https://ykyi.net/?p=1545/</guid>
      <description>&lt;p&gt;上个月用python的gevent协程库写了一个tcp服务。日志库使用python标准日志库logging。一个月后，发现一个偶发的bug。这个bug发生时，用python的标准日志库自带的FileHandler写的日志会发往socket占用的文件描述符fd。结果就是，客户端收到了本要打印到磁盘上的日志。花了不少时间定位排查这个bug，仍然没有结果。我开始怀疑是gevent协程库和python的标准日志库logging有冲突。协程库会错误的把logging打开的文件描述符fd关闭并分配给新创建的socket，于是日志就打印到socket占用的fd了。&lt;/p&gt;
&lt;p&gt;后来，写了一个检测脚本用来监控这个情况发生的概率。该脚本每分钟会检查进程占用的所有fd，一但发现用来打印本地日志的文件fd不见了，就重启服务进程。核心代码如下:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-bash &#34;&gt;lsof -p $pid | grep -q ${log_file_name}
if [ $? != 0 ]
then
    # 报警代码 ...
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;发现bug发生的频率大概是一个星期一次。但这毕竟根本上解决不了问题啊，又找不到bug的原因，怎么办？&lt;/p&gt;
&lt;h3 id=&#34;那就自定义一个file-handler吧&#34;&gt;那就自定义一个File Handler吧！&lt;/h3&gt;
&lt;p&gt;决定自定义一个File Handler，这个File Handler工作在另外一个单独的进程，这样无论如何日志用的fd都不会跟主进程的各种socket用的fd冲突了吧。&lt;br&gt;
代码如下，主要用到的技术进程通讯和用python的&lt;code&gt;__getattribute__&lt;/code&gt;魔法把截获类实例的方法调用。这样，只需要把旧的代码中的File Handler(我用了TimedRotatingFileHandler)换成自定义的Handler Class，所有其他旧代码都无需改动。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&#34;language-python &#34;&gt;#!/usr/bin/env python3

&#39;&#39;&#39;
created by kamuszhou*AT*tencent.com, zausiu*AT*gmail.com http://ykyi.net
Nov 20, 2018
&#39;&#39;&#39;

import logging
import logging.handlers
from functools import partial
from multiprocessing import Process, Queue


class MySpecialHandler(logging.StreamHandler):
    def __init__(self, *args, **kargs):
        self._q = Queue()
        self._p = Process(target=MySpecialHandler.__run, args=(self._q,))
        self._p.start()
        self._q.put((&#39;__init__&#39;, args, kargs))

    def join(self):
        self._p.join()

    def __run(q):
        handler = None
        while True:
            # op, params = q.get()
            method, args, kwargs = q.get()
            if method == &#39;__init__&#39;:
                handler = logging.handlers.TimedRotatingFileHandler(*args, **kwargs)
            else:  # 主进程的日志调用实际上被转到这里
                getattr(handler, method)(*args, **kwargs)

    def __proxy(self, name, *args, **kwargs):
        # 把调用的方法名和方法参数通过Queue传到专门的日志进程。
        self._q.put((name, args, kwargs))
        fun = logging.StreamHandler.__getattribute__(self, name)
        # print(&#39;call method: &#39;, name, args, kwargs)
        # 如果是setLevel函数，再调用一次父类的方法
        if name in {&#39;setLevel&#39;}:
            return fun(*args, **kwargs)

    def __getattribute__(self, name):
        &#39;&#39;&#39;
        Hook大法！截获所有方法
        &#39;&#39;&#39;
        attr = logging.StreamHandler.__getattribute__(self, name)
        if hasattr(attr, &#39;__call__&#39;) and name not in {&#39;join&#39;, &#39;emit&#39;}:
            return partial(MySpecialHandler.__proxy, self, name)
        else:
            return attr


if __name__ == &#39;__main__&#39;:
    handler = MySpecialHandler(&#39;/data/tmp/ttt.log&#39;, when=&#39;D&#39;, interval=1, backupCount=90)
    handler.setLevel(logging.DEBUG)
    formatter = logging.Formatter(&#39;%(asctime)s: %(levelname)s %(message)s&#39;)
    handler.setFormatter(formatter)
    logger = logging.getLogger(__name__)
    logger.propagate = False  # Don&#39;t propagate the logging to ROOT
    logger.setLevel(logging.DEBUG)
    logger.addHandler(handler)
    logger.debug(&#39;debug testtttttttt&#39;)
    logger.info(&#39;info testtttttttttt&#39;)
    logger.warn(&#39;warn testtttttttttt&#39;)
    logger.error(&#39;error testtttttttt&#39;)
    logger.critical(&#39;critical testttttt&#39;)
    handler.join()

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这里自定义的日志进程类只是一个很粗糙的实现，一但跑起来，只能手动杀进程。反正我的使用场景是一个服务。所以，我也懒得加‘优雅的退出代码’。&lt;/p&gt;</description>
    </item>
  </channel>
</rss>
