阅读Effective Python(第二版)的一些笔记
原文地址:https://www.cnblogs.com/-beyond/p/16102350.html
一些简单的场景,可以使用namedtuple来快速创建简单类,使用格式如下namedtuple(className, [property_1, property_2...])
#coding:utf-8 from collections import namedtuple # 使用namedtuple创建简单的类 UserClass = namedtuple("User", ["name", "gender", "age"]) user = UserClass(name="小明", gender="男", age=99) print(user) # User(name='小明', gender='男', age=99) print(user.name) # 小明
Python类中的__call__()
方法,重写了这个方法后,就可以像调用函数一样调用对象实例,例子如下:
# coding:utf-8 class MyCallableClass(object): def __init__(self, desc): self.desc = desc # 实现了__call__方法后可以想调用函数一样调用对象 def __call__(self, *args, **kwargs): print("__call__()方法被调用,desc:{}".format(self.desc)) print("*args:{}, **kwargs:{}".format(args, kwargs)) demo = MyCallableClass("hello") # 然后像调用方法一样进行调用 demo(1, 2, 3, name="小明", age=99) # __call__()方法被调用,desc:hello # *args:(1, 2, 3), **kwargs:{'name': '小明', 'age': 99}
在Python中,函数和接口都是一等公民,也就是可以像变量一样进行赋值操作。
Python有许多内置的API,都允许我们传入某个函数来定制它的行为(当做变量传入)。这种函数可以叫作挂钩(hook),API在执行过程中,会回调(call back)这些挂钩函数。
下面是一个示例:
# coding:utf-8 # 获取logger, 并调用传入的before_hander和after_handler,这俩handler就是hook def get_logger(before_handler, after_handler): def _logger(msg): before_handler(msg) print(msg) after_handler() return _logger def before_h(m): print("这是before输出的内容,msg:{}".format(m)) def after_h(): print("这是after输出的内容") log = get_logger(before_h, after_h) log("hello world") # 这是before输出的内容,msg:hello world # hello world # 这是after输出的内容
上面传的before_hander和after_handler,这俩handler就是hook,也就是可以被调用的函数。前面说了实现了__call__()
方法的类实例也是可以被调用的,所以可以将对象传入,也ok,示例如下:
class MyBeforeHandelr(object): def __call__(self, *args, **kwargs): print("这是MyBeforeHandelr.__call__()输出的内容,msg:{}".format(args)) class MyAfterHandler(object): def __call__(self, *args, **kwargs): print("这是MyAfterHandler.__call__()输出的内容") my_before_handler = MyBeforeHandelr() my_after_handler = MyAfterHandler() log = get_logger(my_before_handler, my_after_handler) log("hello world") # 这是MyBeforeHandelr.__call__()输出的内容,msg:('hello world',) # hello world # 这是MyAfterHandler.__call__()输出的内容
传入一个函数 与 传入一个可以被call的对象,有啥区别吗?
其实没啥区别,主要就是单独传入一个函数的话,这个是无状态的,也就是说处理过程完全一样;但是传入可以调用的对象的话,hook就可以是有状态的了,因为对象里面是可以保存状态的。
多态机制使同一体系中的多个类可以按照各自独有的方式来实现同一个方法,这意味着这些类都可以满足同一套接口,或者都可以当作某个抽象类来使用,同时,它们又能在这个前提下,实现各自的功能;
比如下面的Book类(接口定义),定义了info接口,以及两个子类EBook、PaperBook,子类对info方法进行各自的实现。
# coding:utf-8 # 父类 class Book(object): # 由子类实现方法 def info(self): raise NotImplementedError # 电子书 class EBook(Book): def __init__(self, brand, size, price): super().__init__() # 调用父类初始化方法 self.brand = brand self.size = size self.price = price def info(self): print("brand:{}, size:{}, price:{}".format(self.brand, self.size, self.price)) # 纸书 class PaperBook(Book): def __init__(self, pages, press, edition): super().__init__() self.pages = pages self.press = press self.edition = edition def info(self): print("pages:{}, press:{}, edition:{}".format(self.pages, self.press, self.edition))
测试代码
ebook = EBook("xiaomi", 10.2, 1999) ebook.info() # brand:xiaomi, size:10.2, price:1999 paper_book = PaperBook(600, "人民邮电出版社", "the second") paper_book.info() # pages:600, press:人民邮电出版社, edition:the second book_list = [ebook, paper_book] for book in book_list: # 直接调用方法,不需要关心是哪个子类,因为他们都实现了info方法 book.info() # brand:xiaomi, size:10.2, price:1999 # pages:600, press:人民邮电出版社, edition:the second
上面代码就简单实现了多态;
现在需要有一个通用的功能,根据传入类的类型和参数,实现该类的实例化,传入的类可能是Ebook,也可能是PaperBook,此时可以采用类似工厂模式那种,如下:
# 仿照工厂模式 def build_book(clazz, param): if clazz == EBook: return EBook(param['brand'], param['size'], param['price']) elif clazz == PaperBook: return EBook(param['pages'], param['press'], param['edition']) else: raise TypeError book1 = build_book(EBook, {"brand": "xiaomi", "size": 10.2, "price": 1000}) book1.info() # brand:xiaomi, size:10.2, price:1000 book2 = build_book(PaperBook, {"pages": "400", "press": "asdf", "edition": "2ed"}) book2.info() # brand:400, size:asdf, price:2ed
上面的代码是能实现需求的,但是一旦Book类又多了一个子类,那么就得修改build_book方法,否则创建新的子类对象,就会报TypeError;此时可以采用下面的方式:
# 父类 class BookV2(object): # 由子类实现方法 def info(self): raise NotImplementedError # 留给子类实现,从config_data中获取数据来实例化子类对象 @classmethod def create_book_instance(cls, config_data): raise NotImplementedError # 电子书 class EBookV2(BookV2): def __init__(self, brand, size, price): super().__init__() # 调用父类初始化方法 self.brand = brand self.size = size self.price = price @classmethod def create_book_instance(cls, config_data): return EBookV2(config_data["brand"], config_data["size"], config_data["price"]) def info(self): print("brand:{}, size:{}, price:{}".format(self.brand, self.size, self.price)) # 纸书 class PaperBookV2(BookV2): def __init__(self, pages, press, edition): super().__init__() self.pages = pages self.press = press self.edition = edition @classmethod def create_book_instance(cls, config_data): return PaperBookV2(config_data["pages"], config_data["press"], config_data["edition"]) def info(self): print("pages:{}, press:{}, edition:{}".format(self.pages, self.press, self.edition)) # 仿照工厂模式 def build_book_v2(clazz, param): # 直接返回 return clazz.create_book_instance(param) # 测试 book3 = build_book_v2(EBookV2, {"brand": "xiaomi", "size": 10.2, "price": 999}) book3.info() # brand:xiaomi, size:10.2, price:999 book4 = build_book_v2(PaperBookV2, {"pages": "500", "press": "gfd", "edition": "2ed"}) book4.info() # pages:500, press:gfd, edition:2ed
在Python中,如果类是支持多继承的,也就是说一个类可以继承多个类;
当在子类中要初始化父类时,目前有两种方式,这两种方式的区别比较大:
这种方式比较简单粗暴
class subClass(parentClass1,parentClass2)
中,parentClass1,parentClass2
的顺序不重要,这个顺序不会影响父类的初始化,父类的初始化顺序取决于子类手动调用父类初始化方法的顺序,所以使用这种方式的话,不要单纯的看class的继承循序就以为是初始化数据。这种方式
# coding:utf-8 class OneClass(object): def __init__(self): print("OneClass __init__()") def say_one(self): print("OneClass say one") class TwoClass(object): def __init__(self): print("TwoClass __init__()") def say_two(self): print("TwoClass say two") # class语句,OneClass和TwoClass的顺序不同 # class ThreeClass(OneClass, TwoClass): class ThreeClass(TwoClass, OneClass): def __init__(self): # 如果不手动调用父类的初始化方法,那么父类的__init__不会自动调用 OneClass.__init__(self) TwoClass.__init__(self) print("ThreeClass __init__()") def say_three(self): super(ThreeClass, self).say_one() super(ThreeClass, self).say_two() print("ThreeClass say three") three = ThreeClass() three.say_three() # OneClass __init__() # TwoClass __init__() # ThreeClass __init__() # OneClass say one # TwoClass say two # ThreeClass say three
class subClass(parentClass1,parentClass2)
中,顺序初始化parentClass1,parentClass2
,而不是先初始化parentClass2,parentClass1
# 使用super().__init__()时,父类的初始化顺序就是class的参数顺序 class FourClass(TwoClass, OneClass): def __init__(self): # 如果不手动调用父类的初始化方法,那么父类的__init__不会自动调用 super().__init__() print("FourClass __init__()") def say_four(self): super().say_one() super().say_two() print("FourClass say four") four = FourClass() four.say_four()
执行结果:
TwoClass __init__() FourClass __init__() OneClass say one TwoClass say two FourClass say four
关于使用哪种方式,可以根据自己的喜好觉得。
关于python的mix-in,可以参考https://wiki.woodpecker.org.cn/moin/IntroMixin
假设A类想要使用B类的方法,那么A类可以直接继承B类,这样自然而然的拥有了B类的方法;这个时候使用继承,更多的考虑扩充自身功能;
比如ToJson类是一个通用的工具了,可以将对象的属性序列化为json,那么其他类有需要的话都可以继承这个类;继承该类后,如果有特殊的逻辑,那么可以重写响应的方法。
Python的属性和方法只有两种访问级别,public和private:
_类名_属性名
或者_类名_方法名
# coding:utf-8 class Person(object): def __init__(self, name, addr, age): self.name = name self._addr = addr self.__age = age def get_age(self): return self.__age def _get_addr(self): return self._addr def __get_name(self): return self.name p = Person("abc", "beijing", 10) print(p.name) # abc print(p._addr) # beijing # print(p.__age) # AttributeError: 'Person' object has no attribute '__age' print(p._Person__age) # 10 print(p.get_age()) # 10 print(p._get_addr()) # beijing # print(p.__get_name()) # AttributeError: 'Person' object has no attribute '__get_name' print(p._Person__get_name()) # abc print(dir(p)) # ['_Person__age', '_Person__get_name', '__class__', '__delattr__', '__dict__', # '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', # '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', # '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', # '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', # '_addr', '_get_addr', 'get_age', 'name']
在定义类的时候,如果想使用某个类的功能,那么可以继承这个类;比如定义一个类,支持像list一样进行len、count、index,那么就可以继承list即可。
但是不使用继承,还想使用len()
,那么就需要重写类的__len__
方法;想使用下标形式[0]
形式访问指定位置的元素,就得重写__getitem__
方法,这个需要查资料或者记住;
有时候一个普通的操作可能会涉及到多个方法的重写,少重写一个方法就会报错,而且只有当运行的时候才会知道还需要重写哪个方法,一个一个解决;为了避免这个问题,collections.abc
模块,提供了很多接口,可以直接继承这些接口,就能知道哪些方法是必须要实现的了。
#coding:utf-8 from collections.abc import Set # 继承collections.abc.Set, 自动带出哪些接口需要实现. class MySet(Set): def __iter__(self): pass def __contains__(self, x: object) -> bool: pass def __len__(self) -> int: pass