美文网首页
如何写出Pythonic风格的代码

如何写出Pythonic风格的代码

作者: defphot | 来源:发表于2019-10-14 08:43 被阅读0次

    如何写出Pythonic风格的代码

    索引

    >>> my_numbers = (4, 5, 3, 9)
    >>> my_numbers[0]
    4
    >>> my_numbers[1]
    5
    >>> my_numbers[-1]
    9
    >>> my_numbers[-2]
    3
    

    切片

    >>> my_numbers = (4, 5, 3, 9)
    >>> my_numbers[0:2]
    (4, 5)
    >>> my_numbers[0:3:2]  # 步长为可选参数
    (4, 3)
    >>> my_numbers[0:-2]  # 只能从左往右
    (4, 5)
    >>> my_numbers[0:-1:2]
    (4, 3)
    >>> my_numbers[2:]  # 可以缺省起始和终止索引
    (3, 9)
    >>> my_numbers[:2]
    (4, 5)
    >>> my_numbers[:]  # 相当于浅拷贝
    (4, 5, 3, 9)
    >>> my_numbers[::]
    (4, 5, 3, 9)
    >>> interval = slice(0, 3, 2)
    >>> my_numbers[interval]  # => my_numbers[0:3:2]
    (4, 3)
    >>> interval = slice(None, 3)
    >>> my_numbers[interval] == my_numbers[:3]
    True
    

    创建自定义序列类型

    class Items:
        def __init__(self, *values):
            self._values = list(values)
            
        def __len__(self):
            return len(self._values)
            
        def __getitem__(self, item):
            return self._values.__getitem__(item)
    

    上面的例子封装了内置的list结构。另外一种是使用继承,通过拓展基类collections.UserList。

    当你需要完完全全实现自己的序列结构时,请牢记两点:

    1. 按照范围切片时,结果应该是相同类型的实例序列。
    2. 在切片提供的范围内,请尊重Python使用的语义,排除最后的元素。

    另外range也可以进行切片

    >>> range(1, 100)[25:50]
    range(26, 51)
    

    上下文管理

    上下文管理器是Python提供的独特功能,它十分有用,非常适合这样的模式:运行一些代码,需要前置和后置条件。例如处理文件:open->处理->close。

    fd = open(filename)
    try:
        process_file(fd)
    finally:
        fd.close()
    

    使用with

    with open(filename) as fd:
        process_file(fd)
    

    上下文管理器依赖两个魔法方法__enter__和__exit__,分别对应前置和后置处理逻辑。

    通过类实现上下文管理器

    def stop_database():
        print("systemctl stop postgresql.service")
        
    def start_database():
        print("systemctl start postgresql.service")
        
    def db_backup():
        print("pg_dump database")
    
    class DBHandler:
        def __enter__(self):
            stop_database()
            return self  # "with xxx as obj" obj is self here.
            
        def __exit__(self, exc_type, ex_value, ex_traceback):
            start_database()
            
    def main():
        with DBHandler():
            db_backup()
    

    通过方法实现上下文管理器,需要使用contextlib.contextmanager装饰器

    import contextlib
    
    @contextlib.contextmanager
    def db_handler():
        stop_database()
        yield  # "with xxx as obj" obj is "yield {obj}".
        start_database()
    
    with db_handler():
        db_backup()
    

    甚至连with也不用,需要使用contextlib.ContextDecorator基类

    import contextlib
    
    class dbhandler_decorator(contextlib.ContextDecorator):
        def __enter__(self):
            stop_database()
    
        def __exit__(self, ext_type, ex_value, ex_traceback):
            start_database()
    
    @dbhandler_decorator()
    def offline_backup():
        print("pg_dump database")
    

    但是这种用法你没法使用__enter__函数的返回值

    使用上下文管理器处理异常

    import contextlib
    
    with contextlib.suppress(DataConversionException):
        parse_data(input_json_or_dict)
    

    如果引发DataConversionException,直接跳过。

    属性

    单下划线表示只会从内部访问(保护)的属性和方法。对象只应该公开那些外部调用者关心的属性和方法,也就是接口,一切不是严格属于接口的都应该使用单下划线。

    双下划线并非定义私有变量的方式,访问双下划线会导致AttributeError,这是因为双下划线会导致重命名:

    class Connector:
        def __init__(self):
            self.__timeout = 60  # _<class-name>__<attribute-or-function-name>
        
        def __run(self):
            print('Run')
    
    conn = Connector()
    print(conn.__timeout)  # raise AttributeError
    print(conn._Connector__timeout)  # 60
    print(conn.__run)  # raise AttributeError
    print(conn._Connector__run)  # function
    

    双下划线是非Pythonic方法。如果您需要将属性定义为私有的,使用单个下划线,并遵守Pythonic约定,即它是私有的属性。

    使用@properties和@xxxx.setter装饰器

    class User:
        def __init__(self, username):
            self.username = username
            self._email = None
    
        @property
        def email(self):
            return self._email
    
        @email.setter
        def email(self, new_email):
            self._email = new_email
    

    迭代对象

    python确定对象是否可迭代的,主要依据两点:

    1. 是否实现了__next__或__iter__迭代方法
    2. 是否是一个序列,实现了__len__和__getitem__

    python会依照顺序尝试调用这些方法来迭代对象

    class A:
        def __init__(self, count=0):
            self._count = count
    
        def __iter__(self):
            return self
    
        def __next__(self):
            if self._count >= 5:
                raise StopIteration
            self._count += 1
            return self._count
    
    class B:
        def __iter__(self):
            return A(1)
    
    a = list(A())
    b = list(B())
    print(a)  # [1, 2, 3, 4, 5]
    print(b)  # [2, 3, 4, 5]
    

    调用__iter__返回的值再调用__next__

    或者__iter__直接返回生成器

    from datetime import date, timedelta
    
    class DateRangeContainerIterable:
        def __init__(self, start_date, end_date):
            self.start_date = start_date
            self.end_date = end_date
    
        def __iter__(self):
            current_day = self.start_date
            while current_day < self.end_date:
                yield current_day
                current_day += timedelta(days=1)
    
    d = DateRangeContainerIterable(date(2018, 1, 1), date(2018, 1, 5))
    print(", ".join(map(str, d)))  # '2018-01-01, 2018-01-02, 2018-01-03, 2018-01-04'
    

    包含对象

    flag = element in container
    # => flag = container.__contains__(element)
    

    动态获取属性

    python调用存在的属性会调用__getattribute__,当属性不存在时,会调用__getattr__。

    可调用对象

    当我们调用一个对象object(*args, *kwargs)时,实际上转换为了object.__call__(args, **kwargs)

    from collections import defaultdict
    
    class CallCount:
        def __init__(self):
            self._counts = defaultdict(int)
    
        def __call__(self, argument):
            self._counts[argument] += 1
            return self._counts[argument]
    
    cc = CallCount()
    print(cc(1))  # 1
    print(cc(2))  # 1
    print(cc(1))  # 2
    print(cc(1))  # 3
    print(cc(3))  # 1
    

    魔法方法总结

    [图片上传失败...(image-bd2aa1-1571013774916)]

    Python注意事项

    不要将引用类型的对象作为函数的默认参数,否则结果不可预期

    def wrong_user_display(user_metadata: dict = {"name": "John", "age": 30}):
        name = user_metadata.pop("name")
        age = user_metadata.pop("age")
        return f"{name} ({age})"
    
    print(wrong_user_display())  # 'John (30)
    print(wrong_user_display({"name": "Jane", "age": 25}))  # John (25)
    print(wrong_user_display())  # raise KeyError: 'name'
    

    拓展内置数据类型

    拓展内置数据类型的正确方式是使用collections模块

    相关文章

      网友评论

          本文标题:如何写出Pythonic风格的代码

          本文链接:https://www.haomeiwen.com/subject/mlylmctx.html