属性作为类的重要组成部分,除了平时常用的读取和设置操作之外,还有很多隐藏的、高级的操作。比如属性的查找顺序、属性的类型检查、限制属性的动态添加等等。这一小节,就让我们深入理解属性的各种高级操作。

19.1 通过字符串操作属性和方法

反射是一个很重要的概念,它可以把字符串映射到实例的属性或者方法,然后可以去执行调用、修改等操作。
Python提供了四个函数,可以通过字符串操作属性和方法:hasattr、getattr、setattr和delattr。但是要注意属性和方法不能是私有的,如果是以“_”开头的属性和方法,那将无法对其进行操作。

看下官方文档说明:

def hasattr(*args, **kwargs): # real signature unknown # hasattr(obj, 'x')  ① 通过"字符串"判断对象的属性或方法是否存在。"""Return whether the object has an attribute with the given name.This is done by calling getattr(obj, name) and catching AttributeError."""passdef getattr(object, name, default=None): # known special case of getattr ② 获取object对象的name属性"""getattr(object, name[, default]) -> value  Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.  相当于点操作符When a default argument is given, it is returned when the attribute doesn'texist; without it, an exception is raised in that case.  当name属性不存在时返回default,否则会引发AttributeError"""passdef setattr(x, y, v): # real signature unknown; restored from __doc__  ③ 通过"字符串"动态设置对象的属性或方法。"""Sets the named attribute on the given object to the specified value. setattr(x, 'y', v) is equivalent to ``x.y = v''  # 相当于点操作符"""passdef delattr(x, y): # real signature unknown; restored from __doc__  ④ 通过"字符串"删除对象的属性或方法。"""Deletes the named attribute from the given object.delattr(x, 'y') is equivalent to ``del x.y''  # 删除属性"""pass

了解了这个功能,发现其实效果都是与点操作符一样的。那么这四个函数有什么实际用处呢?答案是:反射。

反射就是用户通过输入字符串,在代码里通过上面四个函数处理这个字符串,把字符串反射成内存对象。对其进行调用,这个应用非常常见。举个例子:

# 反射应用:
class FileControl:def run(self):while True:# 让用户输入上传或下载功能的命令:user_input = input('请输入 上传(upload) 或 下载(download) 功能:').strip()# 通过用户输入的【字符串】判断方法是否存在,然后调用相应的方法if hasattr(self, user_input):func = getattr(self, user_input)  # 【获取字符串,转成属性】func()  # 【可以调用】else:print('输入有误!')def upload(self):print('文件正在上传...')def download(self):print('文件正在下载...')file_control_obj = FileControl()
file_control_obj.run()

可以看出来,反射就是把字符串反射成内存对象。

再来看一个反射的函数带参数的例子。

import reclass Role(object):def __init__(self, name, weapon, clothes, life=100):self.name = nameself.weapon = weaponself.clothes = clothesself.life = lifedef got_headshoot(self):print("%s got headshot!" % self.name)self.life -= 50def buy_weapon(self, weapon_name):print("%s buy a %s" % (self.name, weapon_name))def fight(self, weapon, place):print("%s use %s to shoot %s" % (self.name, weapon, place))role_default = Role("zzz", "AWM", "3-level")if __name__ == '__main__':# complete_command = "buy_weapon('gun')"complete_command = "fight('gun',123)"command = complete_command.split('(')[0]if hasattr(role_default, command):command_act = getattr(role_default, command)  # 将字符串能转成函数对象parameter_count = command_act.__code__.co_argcountif parameter_count == 1:  # 表示没有参数command_act()else:pattern = r"\((.*)\)"  # 贪婪模式,尽量多吃parameter_list = re.findall(pattern, complete_command)[0].split(",")command_act(*parameter_list)  # 参数传递给函数对象else:print('Command not exists!')

语句command_act.__code__.co_argcount用于统计函数所带参数的个数。我们在知道所需参数个数不为0后,通过正则表达式从输入的字符串complete_command 中提前出所需要的参数,并传递给函数。

反射在工作中,到底有啥用?

  • 通过字符串导入模块
temp = "re"
model = __import__(temp)
content = "Life is short, I love Python, Python is the best programming language!"
pattern = model.compile('Python')  # 编译模式串
substitution = pattern.sub('PHP', content)  # 替换
print(substitution)
  • 以字符串的形式使用模块的方法
import re
func = "compile"
content = "Life is short, I love Python, Python is the best programming language!"
pattern = getattr(re, func)('Python')  # 编译模式串
substitution = pattern.sub('PHP', content)  # 替换
print(substitution)

19.2 __slots__魔法

通过使用__slot__限制属性的动态添加功能,在创建大量对象的场合可以减少内存占用

Python用一个字典来保存一个对象的实例属性,通过__dict__可以查看所有的实例属性。Python是动态语言,因此可以在代码运行时,设置任意的新属性。比如,在实例化后,可以给实例动态添加publisher属性:

doc = Document("title", "author", "babababba")
doc.publisher = "出版社"
print(doc.publisher)

如果避免在运行时,设置任意的新属性,可以在类定义时,添加__slots__属性,规定这个类的对象能够包含的属性列表。这样,当给对象添加不在这个列表中的属性时,则会报错。

例如,在上面的类定义中增加添加__slots__属性,只能包含titleauthor__context__这三个属性。这样,在动态添加publisher属性时将会报AttributeError错误。不过, __slot__限制的是实例属性的添加,不限制类属性的添加。

class Document(object):__slots__ = ['title', 'author', '__context']......

对象的__dict__字典会占用大量内存,使用__slot__后,就不会存在__dict__了,在创建大量对象的场合就可以减少内存占用了,这也是设计__slot__的初衷。虽然可以减少内存的使用,但是我们也不用滥用它,除非你预计得到这个类会创建很多的实例,否则不要用它。因为当你用它之后,就不会存在实例属性的字典__dict__了,而Python的很多特性都依赖于这个字典实现的,因此可能会导致某些异常的问题。

19.3 @property魔法

@property装饰器广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查。

  1. 实例方法变成其同名属性

@property可以把一个实例方法变成其同名属性,以支持.号访问。

class Circle: def __init__(self, radius): self.radius = radius @property  # area本来是函数,使用@property装饰之后,使用area时可以像属性一样使用了def area(self): return 3.14 * self.radius ** 2if __name__ == "__main__":   c = Circle(4) print(c.radius)print(c.area)

area虽然是定义成一个方法的形式,但是加上@property后,可以通过访问c.area得到方法的返回值,而不必像c.area()调用它。

  1. 利用property校验属性的类型

例如,我们在Student类中定义一个age字段,合法值一般为包含0的正整数,但是在python中无正整数的类型,只能自己来校验。方法是对age方法用@age.setter装饰,见下面代码:

class Student:def __init__(self, name, age, address):self._name = None  # 注意,属性设置为私有的,这样避免在类外部直接对属性进行设置self._age = Noneself._address = None@propertydef name(self):return self._name  # 返回私有属性值@name.setterdef name(self, value):if not isinstance(value, str):  # 检查value类型raise ValueError("Must be string")self._name = value  # 设置私有属性值@propertydef age(self):return self._age@age.setterdef age(self, value):if isinstance(value, int) and value > 0:self._age = valueelse:raise ValueError("Must be positive number")@propertydef address(self):return self._address@address.setterdef address(self, value):if not isinstance(value, str):raise ValueError("Must be string")self._address = valueif __name__ == "__main__":# n1 = Student(12, 10, "beijing")n2 = Student("Jim", -1, "shanghai")

代码中,实例化n2时,传递的年龄是负数,执行代码,将会得到ValueError: Must be positive number异常。注意,要想使用@age.setter,一定要先@property装饰age属性。

上面的这段代码对name、age和address都进行了类似的限制。通过观察发现对name的检查和对address的检查是一样的,但是代码冗余太多。描述符提供了优雅、简洁、健壮和可重用的解决方案,关于描述符我们后面在介绍。

再来一个温度的例子。这个类接收摄氏度,内部自动转成华氏温度,可以通过fahrenheit属性访问到。属性必须标记为_开头,表示私有属性,不应在类外部直接赋值。

class Celsius:def __init__(self, temperature=0):self._temperature = temperature@propertydef fahrenheit(self):  # ②return (self._temperature * 1.8) + 32  # ③@propertydef temperature(self):print("Getting value")return self._temperature@temperature.setter  # ①def temperature(self, value):if value < -273:raise ValueError("Temperature below -273 is not possible")print(f"Setting value to {value}")self._temperature = valueif __name__ == "__main__":c = Celsius(37)print(c.temperature)print(c.fahrenheit)c.temperature = 10c.temperature = -300

19.4 描述符

利用property校验属性,可以看到有很多重复的代码。对于name和address这两个属性的校验代码完全一样。 有没有办法简化代码呢?有。这就是描述符。

在python中,如果一个类定义了__get____set____delete__方法中的一个或者多个,那么这个类的对象被称之为描述符descriptor。

描述符规定了对象的属性在获取、设置和删除时,可以做的额外操作。常见的“额外操作”是类型检查。

描述符的作用是用来代理一个类的属性,需要注意的是描述符不能定义在类的构造函数中,只能定义为类的属性,它只属于类的,不属于实例。

使用过Django中的Models类的同学,应该有印象,Models类字段能够自动校验数据类型,比如age=models.IntegerField(),会校验age设置的数据类型必须为Integer。下面我们看看如何用描述符实现这个功能。看一个例子,通过阅读注释,理解代码。

class StringPropertyDescriptor:  # 这就是一个描述符类def __init__(self, init_value=None):self.value = init_valuedef __get__(self, instance, cls):  # 属性被访问时调用。instance是使用描述符实例的类的实例(student),cls就是这个类(Student)print('call __get__', instance, cls)if instance is None:  # 作为类属性访问时,instance是Nonereturn self  # 返回描述符本身else:  # 作为实例属性访问return instance.__dict__[self.value]  # 使用实例字典操作def __set__(self, instance, value):  # 实例化类或者设置属性时,被调用print('call __set__', instance, value)if not isinstance(value, str):raise ValueError("Must be string")instance.__dict__[self.value] = value  # 使用实例字典操作def __delete__(self, instance): # 删除实例属性时,被调用print('call __delete__', instance)del instance.__dict__[self.value]  # 使用实例字典操作# 在这个类Student中使用描述符StringPropertyDescriptor
class Student:name = StringPropertyDescriptor()  # 【提醒!】Student的类属性设置成描述符的实例def __init__(self, name):self.name = name  # 【重要】类属性name赋值给实例属性nameif __name__ == '__main__':student = Student("春和景明")  # 实例化Student类会调用__set__student.name = "liuchunming"  # 设置Student类name属性,会调用__set__print(Student.name)  # 访问Student类的类属性name,会调用__get__,返回描述符本身。print(student.name)  # 访问Student类的实例属性name,会调用Student.name.__get__(),返回"春和景明"del student.name  # 删除Student类name属性,会调用__delete__# student.name = 1000  # 输出ValueError("Must be string")

如果一个对象定义了 __set__()__delete__(),则它会被视为数据描述符。 仅定义了 __get__()的描述符称为非数据描述符(它们通常被用于方法,但也可以有其他用途)。

描述符类中对实例的属性的操作,都是通过属性字典__dict__进行的,从而实现对属性访问、修改和删除时的额外操作。上一小节,我们介绍过__dict__是一个字典,包含完全独属于实例自己的属性,不包含从父类继承到的属性。

可以直接通过__dict__访问、设置、修改、删除属性,描述符类中通过instance.__dict__[self.value] = value 设置value属性,等效于通过类的对象实例self.value=value设置value属性。 student.name 等价于 StringPropertyDescriptor.__get__(name, student, Student)

上面的例子中__get__() 看上去有点复杂,主要是因为描述符被用作实例属性和类属性的不同。当一个描述符被当作一个类属性访问时,上面的例子中直接返回了描述符对象本身,当然也可以做一些其它操作。

如果给Student类增加一个address属性,也规定它必须是字符串,那么修改Student类,增加代码,相比使用@property简单很多:

class Student:name = PropertyDescriptor("初始值")  # 描述符的实例只能作为Student类的’类属性‘address = PropertyDescriptor("初始值")def __init__(self, name): self.name = name  # 【重要】类属性name赋值给实例属性nameself.address = address

描述符通常是那些使用到装饰器或元类的大型框架中的一个组件。同时它们的使用也被隐藏在后面。 举个例子,下面是一些更高级的基于描述符的代码,并涉及到一个类装饰器,实现的功能与上面StringPropertyDescriptor描述符一样:

class TypedAssertion:"""对name进行expected_type类型检查的描述符"""def __init__(self, name, expected_type):self.name = nameself.expected_type = expected_typedef __get__(self, instance, cls):if instance is None:return selfelse:return instance.__dict__[self.name]def __set__(self, instance, value):if not isinstance(value, self.expected_type):raise TypeError('Expected ' + str(self.expected_type))instance.__dict__[self.name] = valuedef __delete__(self, instance):del instance.__dict__[self.name]def type_decorator(**kwargs):"""检查实例属性类型的装饰器"""def decorate(cls):  # 对cls类进行装饰for name, expected_type in kwargs.items():# 给cls类添加属性类name,并给属性设置描述符实例setattr(cls, name, TypedAssertion(name, expected_type))  # 参考反射那节return clsreturn decorateif __name__ == '__main__':@type_decorator(brand=str, shares=int, price=float)class Stock:def __init__(self, brand, shares, price):self.brand = brandself.shares = sharesself.price = prices1 = Stock("nio", "14000", 10.2)s2 = Stock("nio", 14000, 10.2)

通过装饰器使用描述符更加优雅。

不过不要迷恋描述符,如果你只是想简单的对某个自定义类的单个属性进行访问控制时,使用@property技术会更加容易。

当程序中有很多重复代码的时候再考虑使用描述符,比如你想在你代码的很多地方使用描述符提供的功能或者将它作为一个函数库特性的时候,采用描述符才是合适的。

关于描述符的Python官方文档请参考:https://docs.python.org/zh-cn/3/howto/descriptor.html

19.5 属性的设置与读取的底层原理

实例对象通过.点操作符进行属性的访问和设置。例如访问实例d的title属性:

d=Document("demo", "chunming", "show you class")
print(d.title)

点号背后的原理是什么呢?

19.5.1 属性访问拦截器__getattribute__

访问类的属性时,其实都会经过当前类的或者父类的__getattribute__方法。这个方法是属性访问拦截器。Python中只要定义的类继承了object的类,就默认存在属性拦截器,只不过是拦截后没有进行任何操作,而是直接返回。如果我们想在访问属性时做一些操作,比如查看权限、打印log日志等,可以在自己定义的类中改写__getattribute__方法来实现相关功能。

比如在Document类中实现一个__getattribute__,目的是在访问属性之前打印一下log。

    def __getattribute__(self, item):print("属性拦截器,你正在访问属性{}".format(item))return super().__getattribute__(item)

之后通过d.title 访问title属性时,都会打印字符串"属性拦截器,你正在访问属性title"。注意在__getattribute__方法中访问当前实例的属性时,要通过父类的方法super().__getattribute__(item),以便避开无限循环的陷阱。什么是无限循环?请看一个例子:

class D:def __init__(self):self.test = 20self.test2 = 21def __getattribute__(self, name):if name == 'test':return 0.else:return self.__dict__[name]d = D()
print(d.test)
print(d.test2)

由于在__ getattribute__内访问 self .__ dict __属性,就会导致递归错误RecursionError: maximum recursion depth exceeded in comparison。正确的办法是,通过父类的__getattribute__方法访问当前实例的属性。因此需要上面的代码将return self.__dict__[name]修改为return super().__getattribute__(name)

还有一种递归错误的场景,

class People:def __init__(self, name):self.name = namedef __getattribute__(self, obj):if obj.endswith("e"):return super().__getattribute__(obj)else:return self.get_gender()def get_gender(self):return "性别"p = People("马克思")
print(p.name)  # name是以e结尾,所以打印出"马克思"
print(p.gender)

执行p.name时,因为name是以e结尾,所以直接返回name属性。但是执行p.gender时,先调用__getattribute__方法,经过判断后,gender不是以e结尾,返回的是self.get_gender(),即self.get_gender的执行结果,当调用get_gender属性时,又会去调用__getattribute__方法,反反复复,没完没了,形成了递归调用且没有退出机制。

19.5.2 属性访问拦截器__getattr__

Python提供了另外一个方法__getattr__,当访问某一个对象不存在的属性时被调用。

通常当访问某一个对象不存在的属性时,__getattribute__会抛出AttributeError异常。但是,如果对象所属的类定义了__getattr__()方法后, 访问某一个对象不存在的属性时,则返回这个方法的返回值,避免了AttributeError异常。

class Student(object):def __init__(self):  # 只定义了name属性,没定义score属性self.name = 'Michael'def __getattr__(self, attr):  # 当访问不存在的属性时,到这里找,比如找到score,则返回99,找不到则返回Noneprint("遇到了一个不存在的属性")if attr == 'score':return 99if __name__ == '__main__':s = Student()print(s.score)  # 99print(s.age)  # Noneprint(s.name)print(s.__dict__)

执行上面的代码将会得到如下输出:

遇到了一个不存在的属性
99
遇到了一个不存在的属性
None
Michael
{'name': 'Michael'}

可见当使用点号获取实例属性时,如果属性不存在就自动调用__getattr__方法。

这里说的属性不存在说的是,属性name没有在实例instance的__dict__或它构造类的__dict__或者父类的__dict__中。

当属性name可以通过正常机制追溯到时,__getattr__是不会被调用的。比如访问s.name就不会调用__getattr__方法。因为s.__dict__中包含这个属性:

{'name': 'Michael'}

19.5.3 属性设置拦截器__setattr__

与获取属性相对应的是设置属性,Python提供了__setattr__方法,当设置类实例属性时自动调用。

class Dict(dict):def __getattr__(self, key):try:print("调用原本没有定义的属性")return self[key]except KeyError:raise AttributeError(r"'Dict' object has no attribute '%s'" % key)def __setattr__(self, key, value):print("对实例的属性进行赋值")self.__dict__[key] = value  #【重要】不能直接给属性赋值,而通常的做法是使用__dict__属性。d = Dict(a=1, b=2)  # 从类定义看,其实没有a,b这两个属性
print(d)
print(d['a'])  # 调用__getattr__
print(d.a) # 调用__getattr__
d.a = 100  # 对实例的属性进行赋值的时候调用__setattr__
print(d['a'])

当执行d.a=100时,就会调用__setattr__方法。不能self.[key] = value设置属性,这样会导致无限循环。而操作__dict__字典中的键,不会触发__setattr__方法,可以避免无限循环。

19.6 属性查找顺序

https://www.cnblogs.com/xybaby/p/6270551.html#_label_3
通过前面的介绍,我们知道访问属性时用到了一系列get函数(__get__, __getattr__, __getattribute__) ,先执行哪个后执行哪个,是不是有点晕?现在我们就对属性的查找顺序组一个简单总结。

19.6.1 实例属性查找顺序

当一个实例通过点操作符访问实例属性的时候,例如instance.name,首先无条件调用__getattribute__方法,如果类定义了__getattr__方法,那么在__getattribute__抛出 AttributeError 的时候就会调用到__getattr__

__getattribute__内部的会根据情况获取属性值:

  1. 判断属性是否是数据描述符,是则调用数据描述符方法__get__获得属性,否则,
  2. 判断属性是否是实例属性,是则直接返回实例的属性,也就是通过实例的__dict__中的值,否则,
  3. 判断属性是否是非数据描述符,是则直接返回实例的属性,但是实例没有这个属性时,则调用非数据描述符的__get__方法。

也就是数据描述符优先于实例变量,实例变量优先于非数据描述符。

看一个例子:

class DataDescriptor(object):"""数据描述符"""def __init__(self, init_value):self.value = init_valuedef __get__(self, instance, typ):return 'DataDescriptor __get__'def __set__(self, instance, value):print('DataDescriptor __set__')self.value = valueclass NonDataDescriptor(object):"""非数据描述符"""def __init__(self, init_value):self.value = init_valuedef __get__(self, instance, typ):return 'NonDataDescriptor __get__'class Base(object):dd_base = DataDescriptor(0)ndd_base = NonDataDescriptor(0)class Derive(Base):dd_derive = DataDescriptor(0)ndd_derive = NonDataDescriptor(0)ndd_derive2 = NonDataDescriptor(0)same_name_attr = '单纯类属性(不是描述符)'def __init__(self):self.not_des_attr = 'I am not descriptor attr'self.same_name_attr = '实例属性'def __getattr__(self, key):return '__getattr__ with key %s' % keydef change_attr(self):"""dd_base和ndd_derive即是实例变量,又是类变量了"""self.__dict__['dd_base'] = 'dd_base now in object dict 'self.__dict__['ndd_derive'] = 'ndd_derive now in object dict 'if __name__ == '__main__':b = Base()d = Derive()# 1)same_name_attr和not_des_attr不是描述符,same_name_attr既是实例属性又是类属性,但实例属性优先。not_des_attr是实例属性则直接返回实例属性值。assert d.same_name_attr == '实例属性'assert d.not_des_attr == 'I am not descriptor attr'# 2)访问不存在的属性no_exists_key是调用__getattr__。assert d.no_exists_key == '__getattr__ with key no_exists_key'# 3)dd_base是数据描述符,其他是非数据描述符,而且他们都不是实例属性,所以访问他们的时候直接调对应的__get__方法。assert d.dd_base == "DataDescriptor __get__"assert d.ndd_derive == 'NonDataDescriptor __get__'assert d.ndd_base == 'NonDataDescriptor __get__'d.change_attr()  # 调用change_attr方法之后,给实例增加两个属性# 5) dd_base作为数据描述符,dd_base且变成了实例属性,则优先调用数据描述符的__get__方法assert d.dd_base == Base.dd_base  # 父类# 6) ndd_derive是非数据描述符,ndd_derive是实例属性,优先返回实例的__dict__['ndd_derive']assert d.ndd_derive == 'ndd_derive now in object dict '

从这个例子可以看出,访问一个实例的属性obj.attr 的顺序如下:

  1. 如果attr不是描述符,实例属性优先级高于类属性,就像上面代码中的same_name_attr
  2. 如果attr是描述符,但是只是类的属性,不是实例的属性, 则不管是数据描述符还是非数据描述符,都调用描述符的__get__方法;
  3. 如果attr是数据描述符,且是实例属性,则优先调用数据描述符的__get__方法
  4. 如果attr是非数据描述符,且是实例属性,则优先返回实例的属性,不调用非数据描述符的__get__方法

记住一句话,访问属性的优先级是:数据描述符优先于实例变量,实例变量优先于非数据描述符。

19.6.2 类属性查找顺序

类的也是对象,类是元类(metaclass)的实例,所以类属性的查找顺序基本同实例属性的查找顺序。

  1. 如果属性是一个描述符(不管是数据描述符还是非数据描述符),都调用其__get__方法
  2. 如果属性不是描述符,则直接返回类或者父类的__dict__['attr']

就想上面的例子中,这个部分:

# 3)dd_base是数据描述符,其他是非数据描述符,而且他们都不是实例属性,所以访问他们的时候直接调对应的__get__方法。assert d.dd_base == "DataDescriptor __get__"assert d.ndd_derive == 'NonDataDescriptor __get__'assert d.ndd_base == 'NonDataDescriptor __get__'

测试开发之Python核心笔记(19): 深入理解类的属性相关推荐

  1. 测试开发之Python核心笔记(7):输入与输出

    输入输出的设备主要有键盘.屏幕.网卡.文件等. 7.1 键盘和屏幕 input 接收键盘的输入,得到的是字符串,name = input('your name:'),要想的到数字,需要进行类型转换 p ...

  2. 测试开发之Python核心笔记(15):迭代器与生成器

    15.1 可迭代对象Iterable 还记得for循环吗?for循环可以循环迭代处理字符串以及列表.元组.字典.集合这些容器类型,知道为什么这些数据类型可以被for迭代吗?因为这些对象都是可迭代对象. ...

  3. 跟着王进老师学开发之Python篇第一季:基础入门篇-王进-专题视频课程

    跟着王进老师学开发之Python篇第一季:基础入门篇-2859人已学习 课程介绍         本季课程首先对Python简要介绍,然后演示如何搭建Python的开发环境,以及如何在IDE中调试Py ...

  4. 视频教程-跟着王进老师学开发之Python篇第一季:基础入门篇-Python

    跟着王进老师学开发之Python篇第一季:基础入门篇 教学风格独特,以学员视角出发设计课程,难易适度,重点突出,架构清晰,将实战经验融合到教学中.讲授技术同时传递方法.得到广大学员的高度认可. 王进 ...

  5. Swift网络开发之NSURLSession学习笔记

    为什么80%的码农都做不了架构师?>>>    Swift网络开发之NSURLSession学习笔记 先上效果图:        功能: -单个任务下载 -暂停下载任务 -取消下载任 ...

  6. 电影天堂APP项目开发之Python爬虫篇,共18课时/5时33分

    电影天堂APP项目开发之Python爬虫篇,共18课时/5时33分,是电影天堂APP项目开发课程的第一篇章,讲解使用requests和bs4库,爬取和解析电影天堂网站数据,并讲数据保存到SQLite数 ...

  7. Python学习笔记19:列表 III

    Python学习笔记19:列表 III 其实这篇笔记标题应该是列表扩展,从列表开始,将涵盖Python中的序列容器. 关于列表的基础知识,可以看我的前两篇文章: Python学习笔记1:列表. Pyt ...

  8. android语音播放工具类,Android开发之MediaPlayer多媒体(音频,视频)播放工具类

    本文实例讲述了Android开发之MediaPlayer多媒体(音频,视频)播放工具类.分享给大家供大家参考,具体如下: package com.android.imooc.chat; import ...

  9. Python学习笔记:创建分数类

    Python学习笔记:创建分数类 1.编写创建分数类.py # 创建分数类from math import gcd# 定义分数类 class Fraction: def __init__(self, ...

最新文章

  1. Java程序员年薪40W,他1年走了别人5年的路(技术提炼)
  2. “史上最全PyTorch资源汇总“(转载)
  3. linux socket 阻塞与非阻塞,同步与异步
  4. 【信息抽取】如何使用循环神经网络进行关系抽取
  5. 我心中的核心组件(可插拔的AOP)~第十五回 我的日志组件Logger.Core(策略,模版方法,工厂,单例等模式的使用)...
  6. 十年前乔布斯曾试图颠覆电视行业 如今苹果终于出手了
  7. Ubuntu系统下ntp服务器搭建2
  8. 对于C# 中事件的参数(object sender, EventArgs e)
  9. 待熟悉的工具或API清单列表
  10. 算法之BFS算法框架
  11. python画图的函数_python画图函数
  12. 小设备 大智慧——天霆交互技术让终端变得更智能
  13. 【探究服务】——服务的更多技巧
  14. 问题描述:vcenter上数据存储的事件中有大量的警告,还有偶尔的报错,存储设备naa...的路径冗余已降级,路径。。已关闭,受影响的数据存储 与存储设备。。。连接丢失,路径。。已断开,受影响的存储
  15. 【建议珍藏】2023年最新Android大厂面经分析,最终入职得物
  16. LSTM(long short term memory)长短期记忆网络
  17. 关键路径例题图表_关键路径法典型范例
  18. HTTP Content-type 对照表
  19. 2021年各大互联网大厂年终奖一览表~新年干劲十足
  20. c++矩阵转置_线性代数中的向量矩阵

热门文章

  1. [ecshop 支付接口 开发调试] ecshop 需要做一些支付接口,和接口升级,经常需要支付返回
  2. 【计算机毕业设计】基于微信小程序的高校学生选课系统
  3. Java中的集合多线程的理解
  4. 计算机电源原如何确定正常,电脑电源故障迅速准确判断方法
  5. 附件上传大小限制处理办法
  6. Mysql绿色版的安装(转载)
  7. 网络红人 赔本赚吆喝?炙手可热却苦水满腹图
  8. 国家自然科学基金结题报告下载
  9. SMBJ8.0CA 贴片TVS二极管
  10. 【shell】shell脚本读取给定参数|参数个数