Python的四大基本语法

Python的四大基本语法

学会这八个技术,你离BAT大厂不远了
红人榜第七期来咯~本期干货:HTTP、本周*受关注的技术关键词TOP8,往下看吧~ 在如今这个时间和知识都是碎片化的时代,C站根据C1-C4认证的成长路径,进行知识细化整理,形成系统化的知识图谱。 小编根据C1认证的成长路径整理了100篇HTTP协议的学习资料,从零基础带你进入HTTP协议的世界。 同时,小编调查了小伙伴们对本周发表在高校俱乐部博客和公众号上文章的关注度,盘点出*受关注的8个技术关键词,扫码即可获取。 欢迎关注订阅本公众号,每天获得*核心*新鲜的知识合集,助你稳过认证、通关…

02

大家好,我是营长,上期营长分享了“详解Pythonl两大特性”,这期营长接着为大家分享Python相关内容。

本期分享内容:Python四大基本语法

这期分享营长邀请的是zglg (某大厂算法工程师)和Alicia (美国顶尖学府 AI 博士后)两位为我们分享Python全栈精通之路。

作者有话说

很多人问为什么要写这个专栏?因为我也有过那段“自学” Python 的迷茫时期,所以深知一个好的系统学习规划和老师讲解,是能够达到事半功倍省下我们程序员更多青春的关键。

别的老师在介绍知识点时都会说“这东西是什么”,但我不想这样做。

我觉得“为什么这东西是这样”或者“在什么场景适应什么需求有什么好处才会用这东西”,反而更能让你们对知识本身会有更深刻的理解。

02

Python的四大基本语法

四大基本语法主要从变量命名基本规则,等方面总结 Python 的基本语法规则。

命名规则

Python 的变量命名规则主要包括两条:

允许包括英文、数字以及下划线(_),不能以数字开头。

名称区分大小写。

特别说明以下划线开头的变量是有特殊意义的:

类变量若以单下划线(_)开头,代表不能直接被访问,类似于C#的受保护型变量(protected),表示不能通过import module_name而导入。

类变量若以双下划(__)开头,表示为类的私有成员,不能被导入和其他类变量访问。

以双下划开头和双下划线结尾的变量是 Python 里的专用标识,有特殊的身份。如 Python 自定义类中都包括__init__和__add__方法,如果不重写__add__去执行两个类加法操作,程序会抛TypeError异常。只有重写后,才能正常执行加法操作。

Python 变量命名习惯一般遵守蛇形命名法(snake case):

一般变量命名,book_id,book_store_count;

类名首字符为大写,如Python内置模块collections.abc中的Iterable类,我们自定义的Book类等;

类方法名:get_store_count();

其他特殊变量,全部大写M_PI,MAX_VEHICLE_SPEED

注:这与Java命名方法不同,Java*典型的命名方法是驼峰命名法(camel case)。

缩进原则

python *具特色的地方就是用缩进代替Java, C++中的{},缩进的层级结构表示代码的逻辑层次。

比如,自定义一个Book类,重写__add__方法计算两类书的库存量和。Python 的缩进方法,一般为4个字符。

代码行class Book(object) 与代码行# 定义类的参数的缩进,此处为 4 个字符;代码行def __add__(self,book):与return所在行缩进也是 4 个字符;通过这种层级结构展现出代码的逻辑层次。

class Book(object):
# 定义类的参数
def __init__(self,book_id, book_name, book_store_count):
self.book_id = book_id
self.book_name = book_name
self.book_store_count = book_store_count
# 重写加法操作
def __add__(self,book):
return self.book_store_count + book.book_store_count

store_count = Book(1,’python入门书’,100) + Book(2,’机器学习入门书’,200)
print(store_count) # 300
缩进格式,行间空行数,变量和等号空格等 Python编码规范参考PEP8. autopep8包遵循PEP8的所有规范,安装此包后可自动实现遵循编码规范,推荐使用。

特殊关键字

Python 有 35 个关键字:

False      await      else       import     pass
None       break      except     in         raise
True       class      finally    is         return
and        continue   for        lambda     try
as         def        from       nonlocal   while
assert     del        global     not        with
async      elif       if         or         yield
自定义变量名不能与它们重复。

常用且不同于其他常用语言 c++和 Java 的关键字,如:

True和False 用于表示值的真假,在Java中true和flase;

逻辑反操作 Python 使用not而 Java 是!;

None表示空值,如数据库的字段取值为空,Java中null;

Python 两个条件同时满足使用 and而 Java 是&&;

两者满足其一,Python 使用or而Java使用||;

Python 使用elif而 Java 是else if;

其他比较特殊的关键字,如:

del用于删除可迭代对象中某个元素;

def用于定义函数;

带yield用于定义生成器(generator)函数;

global和nonlocal一种应用是 Python 函数式编程的闭包场景;

pass一种应用是 Python 中接口定义,也是 Python 语言特有的一个关键字。

这些关键字的用法会在后续文章中更为详细的介绍,在此先只要对它们形成一个整体上的认识就行。

特殊运算符

Python 的运算符包括:

+       –       *       **      /       //      %      @
<<      >>      &       |       ^       ~       :=
<       >       <=      >=      ==      !=
大部分运算符应该被熟知,重点介绍 3 个比较特殊的://, **, :=

//用于两个数值相除且向下取整,与 Python 的math模块中floor功能一致:

In [1]: 5//2
Out[1]: 2
In [2]: 5//4.5
Out[2]: 1.0
**用于幂运算:

In [1]: 2**3
Out[1]: 8
:=是 2019 年 Python3.8 版本里刚刚才支持的运算符,形象的称为海象运算符.

n = len(a)
if n > 10:
print(f”{n}大于10″)
如果使用海象运算符,写法上更为精简:

if (n := len(a)) > 10:
print(f”{n}大于10″)
Python 比较运算符还支持链式比较,应用起来非常方便,比如:

i = 3
print(1< i < 3) # False
print(1 < i <= 3) # True
另外,运算符 @ 用于装饰器功能,下面会介绍一种使用场景。

类和对象

类和对象

Python被称为面向对象语言,所以面向对象技术支持非常完整,例如,类、接口、继承、封装、多态等。

8.1 对象的魔法
对象可以看作数据以及可以操作这些数据的一系列方法的集合。为了区分全局函数,将这些写在类中的函数称为方法。想要访问这些类中的函数,必须要对类实例化,实例化后的产物被称为对象。实例化后,调用方法时需要先指定对象名称,然后才可调用这些方法。

面向对象的三大特征:

继承:当前类从其他类获得资源(数据和方法),以便更好的重用代码,并且可以描述类与类之间的关系。
封装:对外部世界隐藏对象的工作细节。
多态:只有在程序运行时才确定具体的类,从而导致该引用调用的具体方法随之改变,让程序可以选择多个运行状态,这就是多态。
python中没有接口的概念,想要使用接口,就直接使用对象好了,而且假定要调用的对象成员都存在。( 当一个方法在很多类中有不同的体现的时候,这个时候就可以将这个方法抽象出来做成一个接口,即一组规范 )。

8.2 类
8.2.1 创建自己的类
创建一个类,以及利用这个类创建两个对象,并调用其中的方法。

# 创建一个Person类
class Person:
# 定义setName方法
def setName(self, name):
self.name = name
# 定义getName方法
def getName(self):
return self.name
# 定义greet方法
def greet(self):
print(“Hello, I’m {name}.”.format(name = self.name))

# ————-对类实例化——————-
# 对类实例化,实例化后的产物就是对象,即引用类时指定了一个变量,并分配了一块内存
person1 = Person()
person2 = Person()

# ————-给类中的变量赋值————–
# 调用类对象中的setName方法
person1.setName(“Bill Gates”)
# 调用类对象中的name属性
person2.name = “Bill Clinton”

# ————-调用类中的方法—————-
# 两种等价调用方式
print(person1.getName())
person1.greet()
# 两种等价调用方式
print(person2.name)
Person.greet(person2)

知识点:

python类使用class关键字定义,类名直接跟在class关键字的后面。
类名也是一个代码块,所以类名后面要跟着一个冒号(:).。
类中的方法其实就是函数,定义方式完全一样。
每一个方法的*个参数都是self,其实这是必须的。任何方法必须至少指定一个self参数,如果方法包含多个参数,*个参数将作为self参数使用。在调用方法时,这个参数值不需要自己传递。系统会将方法所属的对象传入这个参数。在方法内部可以利用这个参数调用对象本身的资源,如属性、方法等。
通过self参数添加的name变量是person类的属性,可以在外部访问。上例子中设置了person2对象的name属性值,与调用person.setName方法的效果完全相同。
调用对象的方法有两种方式。一种是直接通过对象变量调用方法,另一种是通过调用方法,并且将相应的对象传入方法的第1个参数。例如 Person.greet(person2)的方式调用了person2对象中的greet方法。
8.2.2 方法和私有化
python 类默认情况下,所有的方法都可以被外部访问。在python类中并没有提供private或类似的关键字将方法私有化,但可以迂回解决。方法私有化:只有类的内部方法才能访问私有化的方法,通过正常的方法是无法访问对象的私有化方法的,使用反射技术的除外。

在python类的方法名前面加双下划线(__)可以让该方法在外部不访问。

class person:
# 在外部可以访问
def method1(self):
print(“method1”)

# 在外部不可访问

def __method2(self):
print(“method2”)

def method3(self):
print(“method3”)
# 内部正常引用
self.__method2()

p = person()
p.method1()
p.method3()
p._person__method2() #外部正常引用
p.__method2()

p.__method2() 抛出异常,找不到相关类名

Traceback (most recent call last):
File “C:/Users/UFO/PycharmProjects/fullstack1/test.py”, line 14, in <module>
p.__method2()
AttributeError: ‘person’ object has no attribute ‘__method2’

原理分析:其实 __method2() 方法也不是*对不可访问的。python编译器在编译源代码时,并没有将 __method2() 方法私有化,而是一旦遇到方法名以双下划线开头的方法,就会将方法名改成 _ClassName__methodName 的形式。其中,ClassName表示该方法所在的类名,methodName表示方法名。

class MyClass:
def getName(self):
return self.name
def setName(self, name):
self.name = name
# 直接调用私有方法
self.__outName()

def __outName(self):
print(“Name = {}”.format(self.name))

myClass = MyClass()
# inspect模块中的getmembers方法可以输出myClass类中的所有成员方法,并输出方法名
import inspect
methods = inspect.getmembers(myClass, predicate=inspect.ismethod)
print(methods)

for method in methods:
print(method[0])

print(“————“)
myClass.setName(“Bill”)
print(myClass.getName())
myClass._MyClass__outName()
print(myClass.__outName())

8.2.3 类代码块
class语句与for、while语句一样,都是代码块,这就意味着,定义类其实就是执行代码块。

class person:
print(“person”)

# 输出结果:person

class MyClass:
print(“MyClass”)
count = 0
def counter(self):
self.count += 1

my = MyClass()
my.counter()
print(my.count)
my.counter()
print(my.count)
my.count = “abc”
print(my.count)
my.name = “Hello”
print(my.name)

执行结果

MyClass
1
2
abc
Hello

8.2.4 类的继承
所谓类的继承,就是指一个类(子类)从另外一个类(父类)中获得了所有的成员。父类的成员可以在子类中使用,就像子类本身的成员一样。(私有方法的调用另外考虑,取决于调用方法的名称方式)

class ParentClass:
name = 30
def method1(self):
print(“method1”)

# 类继承的定义方式
class ChildClass(ParentClass):
def method2(self):
print(“method2”)
print(self.name)

# 通过子类调用父类的方法
child = ChildClass()
child.method1()
child.method2()

8.2.5 检测继承关系
在很多场景中,需要知道一个类A是否是从另外一个类B继承的,这种效验主要是为了调用B类中的成员(方法和属性)。关键是要判断B是否为A的父类。

如果要判断类与类之间的关系可以使用 issubclass函数,该函数接收两个参数,第1个参数是子类,第2个参数是父类。如果第1个参数指定的类与第2个参数指定的类确实是继承关系,那么该函数返回True,否则返回False。

如果想要获得已知类的父类(可能有多个父类),可以直接使用 bases ,这是类的一个特殊属性。

也可以使用 isinstance 函数检测一个对象是否是某一个类的实例。isinstance函数有两个参数,第1个参数是要检测的对象,第2个参数是一个类。如果第1个参数指定的对象是第2个参数指定的类的实例,那么该函数返回True,否则返回False。

# 父类(爷爷)
class MyParentClass:
def method(self):
return 50
# 子类(爸爸)
class ParentClass(MyParentClass):
def method1(self):
print(“method1”)
class MyClass:
def method(self):
return 40
# 子类(儿子)
class ChildClass(ParentClass):
def method2(self):
print(“method2”)

# 判断继承关系,可以判断包括多层的继承关系
print(issubclass(ChildClass, ParentClass))
print(issubclass(ChildClass, MyClass))
print(issubclass(ChildClass, MyParentClass))

# 仅仅得到上一层的父类名称,不会输出多层父类名称
print(ChildClass.__bases__)
print(ParentClass.__bases__)

# 判断是否是类的实例,可以判断多层继承的类的实例关系
child = ChildClass()
print(isinstance(child, ChildClass))
print(isinstance(child, ParentClass))
print(isinstance(child, MyParentClass))
print(isinstance(child, MyClass))

8.2.6 多继承
python支持多继承。想要为某个类指定多个父类,需要在类名称后边的圆括号中设置。多个父类名之间用逗号(,)分隔。

如果多个父类中有相同的成员,例如,在两个或两个以上父类中有同名的方法,那么会按照父类书写的顺序继承。也就是说,写在前面的父类会覆盖写在后面的父类同名的方法。在python类中,不会根据方法参数个数和数据类型进行重载。

class Calculator:
def calculate(self,expression):
self.value = eval(expression)
def printResult(self):
print(“result:{}”.format(self.value))

class MyPrint:
def printResult(self):
print(“计算结果:{}”.format(self.value))
def aa(self,a):
return 30

# Calculator 在 MyPrint 前面,所以Calculator中的printResult方法会生效
class NewCalculator(Calculator, MyPrint):
# 如果类中没有代码,需要添加 pass
pass

# MyPrint 在 Calculator 前面,所以MyPrint中的printResult方法会生效
class NewCalculator1(MyPrint,Calculator):
# 如果类中没有代码,需要添加 pass
pass

# 多继承,并调用父类方法,优先的同名方法
calc = NewCalculator()
calc.calculate(“1 + 3 * 5”)
calc.printResult()
# 仅仅输出上层继承的多个类的名称,不是输出多层继承的父类名
print(NewCalculator.__bases__)

# 多继承,并调用父类方法,优先的同名方法
calc1 = NewCalculator1()
print(NewCalculator1.__bases__)
calc1.calculate(“1 + 3 * 5”)
calc1.printResult()

注意:多继承会提高代码重用率,但也会增加代码复杂度。

8.2.7 接口
在很多面向对象语言中都有接口的概念。接口其实就是一个规范,指定了一个类中有哪些成员。接口也经常用在多态中,一个类可以有多个接口,也就是多个规范。不过python语言中并没有这些东西,在调用一个对象时,就假设这个方法在对象中存在。当然,更稳妥的方法就是在调用方法之前先使用hasattr函数检测一下,如果方法在对象中存在,该函数返回True,否则返回False。

还可以使用getattr函数实现同样的功能。该函数有三个参数,前2个参数与hasattr函数完全一样(对象、成员),当第2个参数指定的成员不存在时,getattr函数会返回第3个参数指定的默认值。

与getattr函数对应的是setattr函数,第3个参数是用来设置对象中成员的值。(有则更新,无则添加)

class MyClass:
def method1(self):
print(“method1”)
def default(self):
print(“default”)

my = MyClass()

# my对象中是否存在method1方法
if hasattr(my, ‘method1′):
my.method1()
else:
print(“method2方法不存在”)

# my对象中是否存在method2方法
if hasattr(my,’method2’):
my.method2()
else:
print(“method2方法不存在”)

# my对象中method2方法不存在,则返回”default”值
method = getattr(my, ‘method2’,my.default)
method()

# 对象中成员不存在,则添加成员
def method2():
print(“动态添加的method2”)
setattr(my, ‘method2’, method2)
my.method2()

python:String-模版替换操作

python:String-模版替换操作

capwords函数

将序列中的每个单词变成首字母大写

def capwords(s, sep=None):
“””capwords(s [,sep]) -> string
Split the argument into words using split, capitalize each
word using capitalize, and join the capitalized words using
join. If the optional second argument sep is absent or None,
runs of whitespace characters are replaced by a single space
and leading and trailing whitespace are removed, otherwise
sep is used to split and join the words.
“””
return (sep or ‘ ‘).join(x.capitalize() for x in s.split(sep))
从源码中我们在return处可以看到是先使用了split进行切割(默认走空格切割),然后在使用join进行结果的合并

栗子:

import string

s = ‘this is old old man’
print(s)

print(string.capwords(s))
运行后的结果如下

this is old old man
This Is Old Old Man

Process finished with exit code 0
从运行结果看,可以清晰的看出原本小写的单词,*后变成首字母大写了…

 

模版

模版拼接也是内置拼接的代替方法,使用string.Template拼接时,要在名字前加前缀$来标识变量(例如:$var),如果要和周围的变量区分的话,那就加一个花括号(例如:${var})。

栗子:

import string

values = {‘var’: 66}

t = string.Template(“””
variable : $var
escape : $var
variable in text : ${var}和我拼接
“””)
print(t.substitute(values))
print(t.safe_substitute(values))
运行后的结果如下

variable : 66
escape : 66
variable in text : 66和我拼接

variable : 66
escape : 66
variable in text : 66和我拼接
从上述结果来看,数据是已经替换成功了,唯一不好的就是数据都是字符型,如果参数的类型需要更换,就得再做一层处理… 具体怎么做可自行动手操作

 

substitute和safe_substitute方法

substitute处理替换时,存在就替换,不存在则抛KeyError错误

safe_substitute处理替换时,存在就替换,不存在就是原参数返回,不会抛错误

栗子:substitute方法

import string

values = {‘var’: 66}

t = string.Template(“””
variable : $var
escape : $var
missing : $missing
variable in text : ${var}和我拼接
“””)
print(t.substitute(values))
运行后的结果如下

Traceback (most recent call last):
File “/Users/lifeng/python-projects/Test/pythonScripts/python_string.py”, line 21, in <module>
print(t.substitute(values))
File “/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/string.py”, line 121, in substitute
return self.pattern.sub(convert, self.template)
File “/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/string.py”, line 114, in convert
return str(mapping[named])
KeyError: ‘missing’
从上述运行结果可以看出,报错是KeyError:’missing’

 

栗子:safe_substitute方法

import string

values = {‘var’: 66}

t = string.Template(“””
variable : $var
escape : $var
missing : $missing
variable in text : ${var}和我拼接
“””)
print(t.safe_substitute(values))
运行后的结果如下

variable : 66
escape : 66
missing : $missing
variable in text : 66和我拼接

Process finished with exit code 0
从上述运行结果看,没有参数替换的参数,就原参数返回,且不会报错

 

 

可能有些时候我们觉得$符号不舒服,想更换一种符号用,那这样我们就可以继承Template进行重写操作

class MyTemplate(string.Template):
delimiter = ‘!’

l = MyTemplate(“””
variable : !var
escape : !var
missing : $missing
variable in text : ${var}和我拼接
“””)
print(l.substitute(values))
在上述代码中我们只是把源码中的delimiter变量的值修改了,运行后的结果如下

上述代码中我们把定界符$符号修改成了!符号,参数替换也是成功的,当然你如果想重写别处代码也是可以的,因为Template这个类本身就有正则表达式,只要源码看得懂那就改起来吧…

 

 

JavaScript函数

JavaScript函数

JavaScript函数
文章目录
JavaScript函数
一、函数定义
二、函数参数
三、函数的返回值与表达式
函数返回值return
函数表达式
四、匿名函数
五、箭头函数
六、小结
七、函数全局变量与局部变量
1.介绍
2.示例
一、函数定义
用来封装你的重复性代码
在python定义函数需要用到关键字def
在 js 中使用 function 定义函数
语法:

// 语法
function [函数名](形参1,形参2,形参3…){
[函数体代码];
}

// 无参函数
function func1() {
console.log(‘hello world’);
}
func1(); // hello world

// 有参函数
function func2(a, b) {
console.log(a, b);
}
func2(1, 2); // 1 2
func2(1, 2, 3, 4, 5, 6, 7, 8, 9); // 1 2(多了没关系 只要对应的数据)
func2(1); // 1 undefined(少了也没关系, 未定义的会用undefined填补)

二、函数参数
参数一般五个以下
函数内引用的参数大于传入的参数会以 “undefined” 替代
函数内引用的参数小于传入的参数, 那么只会取出相应的参数
function cook(isBad,a,b,c){ // isbad形式参数
if (isBad){
alert(‘做饭’);

}else{
alert(‘点一个外卖’);
}
}
var bad = false; //刀坏了
cook(bad); // 点一个外卖

arguments 参数
函数中的arguments参数: 能够获取到函数接受到的所有的参数

function func3(a, b, c) {
console.log(arguments);
}
func3(1, 2, 3);
// 执行结果:
/*
Arguments(3) [1, 2, 3, callee: ƒ, Symbol(Symbol.iterator): ƒ]
0: 1
1: 2
2: 3
*/

// 使用arguments判断传参的确切性
function func4(a, b) {
if (arguments.length < 2) {
console.log(‘参数传少了!’);
} else if (arguments.length > 2) {
console.log(‘参数传多了!’);
} else {
console.log(‘正常执行’);
}
}
func4(1, 2, 3); // 参数传多了!
func4(1); // 参数传少了!
func4(1, 2); // 正常执行

三、函数的返回值与表达式
函数返回值return
函数的返回值: 使用的也是等同于python中的关键字return
function func5() { // 返回一个值
return 666
}
console.log(func5()); // 666

function func6(a, b) { // 返回多个值, 多个值必须被包含不然就会返回逗号前面的那一个值.
return a, b
}
console.log(func6(1, 2)); // 2

function func7(a, b) {
return [a, b]
}
console.log(func7(1, 2)); // [1, 2]

函数表达式
var division = function(a,b){
return a/b;
}
// 调用
division(10,2) // 5

四、匿名函数
没有名字的函数
函数可以被赋值, 但对于一个匿名函数来说赋予名字没有意义
匿名函数一般与 map( ) 之类的函数组合使用
/*function () {
console.log(‘哈哈哈’);
}*/
let res = function() { // 函数还可以被赋值, 对于匿名函数这也赋值虽然没有什么意义, 但是为了然函数能正常执行, 还是赋值一个变量吧~
console.log(‘哈哈哈’);
};
res(); // 哈哈哈

五、箭头函数
箭头函数: 主要用来处理简单的业务逻辑 类似于python中的匿名函
ES6中允许使用“箭头”(=>)定义函数
// 如果箭头函数不需要参数或需要多个参数,就是用圆括号代表参数部分
var f = () => 5;

// 等同于
var f = function(){return 5};

let func8 = v => v*2; // 箭头左边的是形参 右边的是返回值
console.log(func8(2)); // 4

let func9 = function (v) { // 上面与下面作用等同
return v * 2;
};
console.log(func9(2)); // 4`

六、小结
/*
函数定义: 无参, 有参
函数调用: 无参直接调用. 有参调用传值多余部分不要, 少的部分用undefined提补.
arguments: 可以接受所有参数, 可以通过arguments.length获取传入的参数个数.
函数返回值: 单个返回值. 多个返回值需被包含, 如果不包含只返回逗号前面一个值.
匿名函数: 匿名函数可被赋值.
箭头函数:
无参: let a = () => 返回值;
有参: let a = 形参 => 返回值;
*/

七、函数全局变量与局部变量
1.介绍
局部变量:

在JavaScript函数内部声明的变量(使用 var)是局部变量,所以只能在函数内部访问它(该变量的作用域是函数内部)。只要函数运行完毕,本地变量就会被删除。
全局变量:

在函数外声明的变量是全局变量,网页上的所有脚本和函数都能访问它。
变量生存周期:

JavaScript变量的生命期从它们被声明的时间开始。
局部变量会在函数运行以后被删除。
全局变量会在页面关闭后被删除。
作用域

首先在函数内部查找变量,找不到则到外层函数查找,逐步找到*外层。与python作用域关系查找一模一样
2.示例
跟python查找变量的顺序一致

示例1
var city = “BeiJing”;
function f() {
var city = “ShangHai”;
function inner(){
var city = “ShenZhen”;
console.log(city);
}
inner();
}
f(); // 输出结果是? ShenZhen

// ES6推荐使用let
let city = “BeiJing”;
function f() {
let city = “ShangHai”;
function inner(){
let city = “ShenZhen”;
console.log(city);
}
inner();
}
f(); // 输出结果是? ShenZhen

示例2
var city = “BeiJing”;
function Bar() {
console.log(city);
}
function f() {
var city = “ShangHai”;
return Bar;
}
var ret = f();
ret(); // 打印结果是? BeiJing

// ES6推荐使用let
let city = “BeiJing”;
function Bar() {
console.log(city);
}
function f() {
let city = “ShangHai”;
return Bar;
}
let ret = f();
ret(); // 打印结果是? BeiJing

示例3:闭包函数
var city = “BeiJing”;
function f(){
var city = “ShangHai”;
function inner(){
console.log(city);
}
return inner;
}
var ret = f();
ret(); // ShangHai

// ES6推荐使用let
let city = “BeiJing”;
function f(){
let city = “ShangHai”;
function inner(){
console.log(city);
}
return inner;
}
let ret = f();
ret(); // ShangHai

探寻Python类的鼻祖——元类

探寻Python类的鼻祖——元类

Python中万物皆对象
Python是一门面向对象的语言,所以Python中数字、字符串、列表、集合、字典、函数、类等都是对象。

利用 type() 来查看Python中的各对象类型

In [11]: # 数字

In [12]: type(10)
Out[12]: int

In [13]: type(3.1415926)
Out[13]: float

In [14]: # 字符串

In [15]: type(‘a’)
Out[15]: str

In [16]: type(“abc”)
Out[16]: str

In [17]: # 列表

In [18]: type(list)
Out[18]: type

In [19]: type([])
Out[19]: list

In [20]: # 集合

In [21]: type(set)
Out[21]: type

In [22]: my_set = {1, 2, 3}

In [23]: type(my_set)
Out[23]: set

In [24]: # 字典

In [25]: type(dict)
Out[25]: type

In [26]: my_dict = {‘name’: ‘hui’}

In [27]: type(my_dict)
Out[27]: dict

In [28]: # 函数

In [29]: def func():
…: pass
…:

In [30]: type(func)
Out[30]: function

In [31]: # 类

In [32]: class Foo(object):
…: pass
…:

In [33]: type(Foo)
Out[33]: type

In [34]: f = Foo()

In [35]: type(f)
Out[35]: __main__.Foo

In [36]: # type

In [37]: type(type)
Out[37]: type

可以看出

数字 1 是 int类型 的对象
字符串 abc 是 str类型 的对象
列表、集合、字典是 type类型 的对象,其创建出来的对象才分别属于 list、set、dict 类型
函数 func 是 function类型 的对象
自定义类 Foo 创建出来的对象 f 是 Foo 类型,其类本身 Foo 则是 type类型 的对象。
连 type 本身都是type类型的对象

1. 类也是对象
类就是拥有相等功能和相同的属性的对象的集合

在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。在 Python 中这一点仍然成立:

In [1]: class ObjectCreator(object):
…: pass
…:

In [2]: my_object = ObjectCreator()

In [3]: print(my_object)
<__main__.ObjectCreator object at 0x0000021257B5A248>

但是,Python中的类还远不止如此。类同样也是一种对象。是的,没错,就是对象。只要你 使用关键字 class,Python解释器在执行的时候就会创建一个对象。

下面的代码段:

>>> class ObjectCreator(object):
… pass

将在内存中创建一个对象,名字就是 ObjectCreator。这个 对象(类对象ObjectCreator)拥有创建对象(实例对象)的能力。但是,它的本质仍然是一个对象,于是乎你可以对它做如下的操作:

你可以将它赋值给一个变量
你可以拷贝它
你可以为它增加属性
你可以将它作为函数参数进行传递
如下示例:

In [39]: class ObjectCreator(object):
…: pass
…:

In [40]: print(ObjectCreator)
<class ‘__main__.ObjectCreator’>

In [41]:# 当作参数传递

In [41]: def out(obj):
…: print(obj)
…:

In [42]: out(ObjectCreator)
<class ‘__main__.ObjectCreator’>

In [43]: # hasattr 判断一个类是否有某种属性

In [44]: hasattr(ObjectCreator, ‘name’)
Out[44]: False

In [45]: # 新增类属性

In [46]: ObjectCreator.name = ‘hui’

In [47]: hasattr(ObjectCreator, ‘name’)
Out[47]: True

In [48]: ObjectCreator.name
Out[48]: ‘hui’

In [49]: # 将类赋值给变量

In [50]: obj = ObjectCreator

In [51]: obj()
Out[51]: <__main__.ObjectCreator at 0x212596a7248>

In [52]:

2. 动态地创建类
因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用 class 关键字即可。

def cls_factory(cls_name):
“””
创建类工厂
:param: cls_name 创建类的名称
“””
if cls_name == ‘Foo’:
class Foo():
pass
return Foo # 返回的是类,不是类的实例

elif cls_name == ‘Bar’:
class Bar():
pass
return Bar

IPython 测验

MyClass = cls_factory(‘Foo’)

In [60]: MyClass
Out[60]: __main__.cls_factory.<locals>.Foo # 函数返回的是类,不是类的实例

In [61]: MyClass()
Out[61]: <__main__.cls_factory.<locals>.Foo at 0x21258b1a9c8>

但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。

当你使用class关键字时,Python解释器自动创建这个对象。但就和Python中的大多数事情一样,Python仍然提供给你手动处理的方法。

3. 使用 type 创建类
type 还有一种完全不同的功能,动态的创建类。

type可以接受一个类的描述作为参数,然后返回一个类。(要知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)

type 可以像这样工作:

type(类名, 由父类名称组成的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

比如下面的代码:

In [63]: class Test:
…: pass
…:

In [64]: Test()
Out[64]: <__main__.Test at 0x21258b34048>

In [65]:

可以手动像这样创建:

In [69]:# 使用type定义类

In [69]: Test2 = type(‘Test2’, (), {})

In [70]: Test2()
Out[70]: <__main__.Test2 at 0x21259665808>

我们使用 Test2 作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。即 type函数 中第1个实参,也可以叫做其他的名字,这个名字表示类的名字

In [71]: UserCls = type(‘User’, (), {})

In [72]: print(UserCls)
<class ‘__main__.User’>

In [73]:

使用 help 来测试这2个类

In [74]: # 用 help 查看 Test类

In [75]: help(Test)
Help on class Test in module __main__:

class Test(builtins.object)
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)

In [76]: # 用 help 查看 Test2类

In [77]: help(Test2)
Help on class Test2 in module __main__:

class Test2(builtins.object)
| Data descriptors defined here:
|
| __dict__
| dictionary for instance variables (if defined)
|
| __weakref__
| list of weak references to the object (if defined)

In [78]:

4. 使用type创建带有属性的类
type 接受一个字典来为类定义属性,因此

Parent = type(‘Parent’, (), {‘name’: ‘hui’})
1
可以翻译为:

class Parent(object):
name = ‘hui’
1
2
并且可以将 Parent 当成一个普通的类一样使用:

In [79]: Parent = type(‘Parent’, (), {‘name’: ‘hui’})

In [80]: print(Parent)
<class ‘__main__.Parent’>

In [81]: Parent.name
Out[81]: ‘hui’

In [82]: p = Parent()

In [83]: p.name
Out[83]: ‘hui’

当然,你可以继承这个类,代码如下:

class Child1(Parent):
name = ‘jack’
sex = ‘男’

class Child2(Parent):
name = ‘mary’
sex = ‘女’

就可以写成:

Child1 = type(‘Child1’, (Parent, ), {‘name’: ‘jack’, ‘sex’: ‘男’})

In [85]: Child2 = type(‘Child2’, (Parent, ), {‘name’: ‘mary’, ‘sex’: ‘女’})

In [87]: Child1.name, Child1.sex
Out[87]: (‘jack’, ‘男’)

In [88]: Child2.name, Child2.sex
Out[88]: (‘mary’, ‘女’)

注意:

type 的第2个参数,元组中是父类的名字,而不是字符串
添加的属性是 类属性,并不是实例属性

5. 使用type创建带有方法的类
*终你会希望为你的类增加方法。只需要定义一个有着恰当签名的函数并将其作为属性赋值就可以了。

添加实例方法
In [89]: Parent = type(‘Parent’, (), {‘name’: ‘hui’})

In [90]: # 定义函数

In [91]: def get_name(self):
…: return self.name
…:

In [92]: Child3 = type(‘Child3’, (Parent, ), {‘name’: ‘blob’, ‘get_name’: get_name})

In [93]: c3 = Child3()

In [94]: c3.name
Out[94]: ‘blob’

In [95]: c3.get_name()
Out[95]: ‘blob’

添加静态方法
In [96]: Parent = type(‘Parent’, (), {‘name’: ‘hui’})

In [97]: # 定义静态方法

In [98]: @staticmethod
…: def test_static():
…: print(‘static method called…’)
…:

In [100]: Child4 = type(‘Child4’, (Parent, ), {‘name’: ‘zhangsan’, ‘test_static’: test_static})

In [101]: c4 = Child4()

In [102]: c4.test_static()
static method called…

In [103]: Child4.test_static()
static method called…

添加类方法
In [105]: Parent = type(‘Parent’, (), {‘name’: ‘hui’})

In [106]: # 定义类方法

In [107]: @classmethod
…: def test_class(cls):
…: print(cls.name)
…:

In [108]: Child5 = type(‘Child5’, (Parent, ), {‘name’: ‘lisi’, ‘test_class’: test_class})

In [109]: c5 = Child5()

In [110]: c5.test_class()
lisi

In [111]: Child5.test_class()
lisi

你可以看到,在Python中,类也是对象,你可以动态的创建类。这就是当你使用关键字 class 时 Python 在幕后做的事情,就是通过元类来实现的。

较为完整的使用 type 创建类的方式:

class Animal(object):

def eat(self):
print(‘吃东西’)

def dog_eat(self):
print(‘喜欢吃骨头’)

def cat_eat(self):
print(‘喜欢吃鱼’)

Dog = type(‘Dog’, (Animal, ), {‘tyep’: ‘哺乳类’, ‘eat’: dog_eat})

Cat = type(‘Cat’, (Animal, ), {‘tyep’: ‘哺乳类’, ‘eat’: cat_eat})

# ipython 测验
In [125]: animal = Animal()

In [126]: dog = Dog()

In [127]: cat = Cat()

In [128]: animal.eat()
吃东西

In [129]: dog.eat()
喜欢吃骨头

In [130]: cat.eat()
喜欢吃鱼

 

6. 到底什么是元类(终于到主题了)
元类就是用来创建类的【东西】。你创建类就是为了创建类的实例对象,不是吗?但是我们已经学习到了Python中的类也是对象。

元类就是用来创建这些类(对象)的,元类就是类的类,你可以这样理解为:

MyClass = MetaClass() # 使用元类创建出一个对象,这个对象称为“类”
my_object = MyClass() # 使用“类”来创建出实例对象
1
2
你已经看到了type可以让你像这样做:

MyClass = type(‘MyClass’, (), {})
1
这是因为函数 type 实际上是一个元类。type 就是 Python在背后用来创建所有类的元类。现在你想知道那为什么 type 会全部采用小写形式而不是 Type 呢?好吧,我猜这是为了和 str 保持一致性,str是用来创建字符串对象的类,而 int 是用来创建整数对象的类。type 就是创建类对象的类。你可以通过检查 __class__ 属性来看到这一点。因此 Python中万物皆对象

现在,对于任何一个 __class__ 的 __class__ 属性又是什么呢?

In [136]: a = 10

In [137]: b = ‘acb’

In [138]: li = [1, 2, 3]

In [139]: a.__class__.__class__
Out[139]: type

In [140]: b.__class__.__class__
Out[140]: type

In [141]: li.__class__.__class__
Out[141]: type

In [142]: li.__class__.__class__.__class__
Out[142]: type

因此,元类就是创建类这种对象的东西。type 就是 Python的内建元类,当然了,你也可以创建自己的元类。

7. __metaclass__ 属性
你可以在定义一个类的时候为其添加 __metaclass__ 属性。

class Foo(object):
__metaclass__ = something…
…省略…

如果你这么做了,Python就会用元类来创建类Foo。小心点,这里面有些技巧。你首先写下 class Foo(object),但是类Foo还没有在内存中创建。Python会在类的定义中寻找 __metaclass__ 属性,如果找到了,Python就会用它来创建类Foo,如果没有找到,就会用内建的 type 来创建这个类。

class Foo(Bar):
pass

Python做了如下的操作:

Foo中有 __metaclass__ 这个属性吗?如果有,Python会通过 __metaclass__ 创建一个名字为Foo的类(对象)
如果Python没有找到 __metaclass__,它会继续在 Bar(父类) 中寻找 __metaclass__ 属性,并尝试做和前面同样的操作。
如果Python在任何父类中都找不到 __metaclass__,它就会在模块层次中去寻找 __metaclass__,并尝试做同样的操作。
如果还是找不到 __metaclass__ ,Python就会用内置的 type 来创建这个类对象。
现在的问题就是,你可以在 __metaclass__ 中放置些什么代码呢?

答案就是:可以创建一个类的东西。那么什么可以用来创建一个类呢?type,或者任何使用到type或者子类化的type都可以。

8. 自定义元类
元类的主要目的就是为了当创建类时能够自动地改变类。

假想一个很傻的例子,你决定在你的模块里所有的类的属性都应该是大写形式。有好几种方法可以办到,但其中一种就是通过在模块级别设定 __metaclass__。采用这种方法,这个模块中的所有类都会通过这个元类来创建,我们只需要告诉元类把所有的属性都改成大写形式就万事大吉了。

幸运的是,__metaclass__ 实际上可以被任意调用,它并不需要是一个正式的类。所以,我们这里就先以一个简单的函数作为例子开始。

python2中
# -*- coding:utf-8 -*-
def upper_attr(class_name, class_parents, class_attr):

# class_name 会保存类的名字 Foo
# class_parents 会保存类的父类 object
# class_attr 会以字典的方式保存所有的类属性

# 遍历属性字典,把不是__开头的属性名字变为大写
new_attr = {}
for name, value in class_attr.items():
if not name.startswith(“__”):
new_attr[name.upper()] = value

# 调用type来创建一个类
return type(class_name, class_parents, new_attr)

class Foo(object):
__metaclass__ = upper_attr # 设置Foo类的元类为upper_attr
bar = ‘bip’

print(hasattr(Foo, ‘bar’))
# Flase
print(hasattr(Foo, ‘BAR’))
# True

f = Foo()
print(f.BAR)

python3中
# -*- coding:utf-8 -*-
def upper_attr(class_name, class_parents, class_attr):

#遍历属性字典,把不是__开头的属性名字变为大写
new_attr = {}
for name,value in class_attr.items():
if not name.startswith(“__”):
new_attr[name.upper()] = value

#调用type来创建一个类
return type(class_name, class_parents, new_attr)

# 再类的继承()中使用metaclass
class Foo(object, metaclass=upper_attr):
bar = ‘bip’

print(hasattr(Foo, ‘bar’))
# Flase
print(hasattr(Foo, ‘BAR’))
# True

f = Foo()
print(f.BAR)

再做一次,这一次用一个真正的 class 来当做元类。

class UpperAttrMetaClass(type):

def __new__(cls, class_name, class_parents, class_attr):
# 遍历属性字典,把不是__开头的属性名字变为大写
new_attr = {}
for name, value in class_attr.items():
if not name.startswith(“__”):
new_attr[name.upper()] = value

# 方法1:通过’type’来做类对象的创建
return type(class_name, class_parents, new_attr)

# 方法2:复用type.__new__方法
# 这就是基本的OOP编程,没什么魔法
# return type.__new__(cls, class_name, class_parents, new_attr)

# python3的用法
class Foo(object, metaclass=UpperAttrMetaClass):
bar = ‘bip’

# python2的用法
class Foo(object):
__metaclass__ = UpperAttrMetaClass
bar = ‘bip’

print(hasattr(Foo, ‘bar’))
# 输出: False
print(hasattr(Foo, ‘BAR’))
# 输出: True

f = Foo()
print(f.BAR)
# 输出: ‘bip’

__new__ 是在__init__之前被调用的特殊方法
__new__是用来创建对象并返回之的方法
而__init__只是用来将传入的参数初始化给对象
这里,创建的对象是类,我们希望能够自定义它,所以我们这里改写__new__

就是这样,除此之外,关于元类真的没有别的可说的了。但就元类本身而言,它们其实是很简单的:

拦截类的创建
修改类
返回修改之后的类

究竟为什么要使用元类?
现在回到我们的大主题上来,究竟是为什么你会去使用这样一种容易出错且晦涩的特性?

好吧,一般来说,你根本就用不上它:

“元类就是深度的魔法,99%的用户应该根本不必为此操心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。” —— Python界的领袖 Tim Peters

 

第十二届蓝桥杯填空题

第十二届蓝桥杯填空题

版权
目录

空间

卡片

直线

路径

空间
问题描述:

小蓝准备用 256MB 的内存空间开一个数组,数组的每个元素都是 32 位二进制整数,如果不考虑程序占用的空间和维护内存需要的辅助空间,请问256MB 的空间可以存储多少个 32 位二进制整数?

思路分析:

先将MB转换为字节Byte,也就是Byte(B),1MB = 1024KB, 1KB = 1024B,1B = 8bit(位)所以256 MB = 256 * 1024 * 1024B,32位二进制整数的存储空间也即32bit = 32 / 8 = 4B,所以可以存在 256 * 1024 * 1024 / 4,使用程序计算出结果为:67108864

if __name__ == ‘__main__’:
print(256 * 1024 * 1024 // 4)
卡片
问题描述:小蓝有很多数字卡片,每张卡片上都是数字 0 到 9。小蓝准备用这些卡片来拼一些数,他想从 1 开始拼出正整数,每拼一个,就保存起来,卡片就不能用来拼其它数了。小蓝想知道自己能从 1 拼到多少。例如,当小蓝有 30 张卡片,其中 0 到 9 各 3 张,则小蓝可以拼出 1 到 10,但是拼 11 时卡片 1 已经只有一张了,不够拼出 11。现在小蓝手里有 0 到 9 的卡片各 2021 张,共 20210 张,请问小蓝可以从1拼到多少?提示:建议使用计算机编程解决问题。

思路分析:

分析题目可以知道模拟整个过程即可,我们可以使用一个循环,只要可以拼出当前的数字i那么循环继续,否则break输出i – 1(本来是送分题在比赛的时候不知道啥情况模拟这个过程还是算错了),使用一个方法check来检查当前数字i是否可以拼出即可,使用check方法来检查会比较保险,我可能在一开始的时候没有使用方法来检查导致所有代码写在一起错了。答案为3181。

cards = [2021] * 10

# 检查当前的数字n是否是拼出来
def check(n: int):
while n:
t = n % 10
if cards[t] – 1 < 0: return False
cards[t] -= 1
n //= 10
return True

if __name__ == ‘__main__’:
i = 1
while True:
if check(i):
i += 1
else:
print(i – 1)
break
直线
问题描述:

在平面直角坐标系中,两点可以确定一条直线。如果有多点在一条直线上,那么这些点中任意两点确定的直线是同一条。给定平面上 2 × 3 个整点 {(x,y)|0 ≤ x < 2; 0 ≤ y < 3; ,即横坐标是 0 到 1 (包含 0 和 1) 之间的整数、纵坐标是 0 到 2 (包含 0 和 2) 之间的整数的点。这些点一共确定了 11 条不同的直线。给定平面上 20 × 21 个整点 {(x,y)|0 ≤ x < 20; 0 ≤ y < 21; ,即横坐标是 0 到 19 (包含 0 和 19) 之间的整数、纵坐标是 0 到 20 (包含 0 和 20) 之间的整数的点。请问这些点一共确定了多少条不同的直线?

思路分析:

一开始的是没有想出来,其实可以枚举所有可能的点的坐标,计算两点确定的直线斜率k与截距b,因为使用的是python语言,所以可以将k,b作为元组(k,b确定的就是一条直线)存储到列表中,然后对列表进行排序,先对斜率排序,斜率相同对截距排序,因为浮点数在存储的时候是有误差的所以需要判断两条直线的斜率或者截距大于1e-8那么判定为不同的直线。答案为:40257

if __name__ == ‘__main__’:
# 使用列表中元组作为元素这样方便后面方便排序
lines = list()
for x1 in range(20):
for y1 in range(21):
for x2 in range(20):
for y2 in range(21):
# x1 == x2的时候斜率不存在
if x1 != x2:
k = (y2 – y1) / (x2 – x1)
b = y1 – k * x1
lines.append((k, b))
# lambda表达式规定先对元组的*个元素斜率k排序, k相同那么则对b排序
lines.sort(key=lambda x: (x[0], x[1]))
# 循环中是从第二条直线开始的
res = 1
for i in range(1, len(lines)):
# 误差大于1e-8判定为不是同一条直线
if lines[i][0] – lines[i – 1][0] > 10 ** -8 or lines[i][1] – lines[i – 1][1] > 10 ** -8:
res += 1
# 加上斜率不存在的20条直线
print(res + 20)
路径
问题描述:

小蓝学习了*短路径之后特别高兴,他定义了一个特别的图,希望找到图中的*短路径。小蓝的图由 2021 个结点组成,依次编号 1 至 2021。对于两个不同的结点 a, b,如果 a 和 b 的差的*对值大于 21,则两个结点之间没有边相连;如果 a 和 b 的差的*对值小于等于 21,则两个点之间有一条长度为 a 和 b 的*小公倍数的无向边相连。例如:结点 1 和结点 23 之间没有边相连;结点 3 和结点 24 之间有一条无向边,长度为 24;结点 15 和结点 25 之间有一条无向边,长度为 75。请计算,结点 1 和结点 2021 之间的*短路径长度是多少。
提示:建议使用计算机编程解决问题。

思路分析:

① 分析题目可以知道我们就可以将节点的边看成是有向边,我们要求解的是从起点1到终点2021的*短路径,所以本质上求解的是*短路径,使用dijkstra算法或者是spfa算法都可以求解,下面使用spfa算法求解单源*短路径,可以知道题目没有任何限制条件所以直接使用spfa算法模板求解接即可(*短路径的裸题)。首先需要创建图,因为使用的是python语言所以使用字典来构建图,键表示顶点,值为列表类型,将顶点能够到达的终点以及对应的权重作为元组的元素添加到字典键对应的列表中即可。创建图之后接下来使用spfa算法的模板即可。

import collections
import sys

# 求解*大公约数
def gcd(a: int, b: int):
while b:
a, b = b, a % b
return a

if __name__ == ‘__main__’:
# 使用spfa单源路径进行求解
# 首先是建图, 看成是有向图
graph = collections.defaultdict(list)
n = 2100
for i in range(1, 2022):
for j in range(i + 1, 2022):
if abs(i – j) <= 21:
# 权重为两个顶点的*小公倍数, 即为两个数相乘除以他们的*大公约数
w = i * j // gcd(i, j)
# 构建当前顶点对应的终点以及边的权重
graph[i].append((j, w))
queue = collections.deque()
s = 1
queue.append(s)
dis = [sys.maxsize] * (n + 1)
dis[s] = 0
used = [0] * (n + 1)
used[s] = 1
while queue:
cur = queue.popleft()
used[cur] = 0
for next in graph[cur]:
if dis[cur] + next[1] < dis[next[0]]:
dis[next[0]] = dis[cur] + next[1]
if used[next[0]] == 0:
used[next[0]] = 1
queue.append(next[0])
print(dis[2021])
② 上面的*短路径还是很容易看出来的,而且题目中求解的问题就是*短路径,除了*短路径算法我们还可以使用动态规划的思路求解。这道题目有点类似于多叉树,从起点出发有很多条路径到达下一个顶点,我们每到达一个顶点那么计算从之前的顶点到达当前顶点的*短路径从而每一次都更新节点的*短距离,这样通过一步步的递推一直到第2021个顶点。

import sys

# 求解*大公约数
def gcd(a: int, b: int):
while b:
a, b = b, a % b
return a

if __name__ == ‘__main__’:
dis = [sys.maxsize] * 2100
dis[1] = 0
for i in range(2, 2022):
for j in range(1, i):
if abs(i – j) <= 21:
# 看成是有向边, 从当前节点之前的节点来更新到达当前节点的*短路径
dis[i] = min(dis[i], dis[j] + i * j // gcd(i, j))
print(dis[2021])

iOS中数组遍历的方法及比较

数组遍历是编码中很常见的一种需求,我们来扒一拔iOS里面都有什么样的方法来实现,有什么特点。

因为iOS是兼容C语言的,所以C语言里面的**常见的for循环遍历是没有问题的。

本文中用的数组是获取的系统的语言数组,大约有30多个数据,虽然还不够模拟大批量的数据,但对于方法的验证是没有问题的了。

NSArray *langArray = [[NSUserDefaults standardUserDefaults] arrayForKey:@”AppleLanguages”];
*种方法是**熟悉的C语言演化过来的:

for (int i = 0; i<langArray.count; i++) {
NSLog(@”langArray[%d]=%@”, i, langArray[i]);
}
这个方法*普通,效率也一般,但它也有好处,一是方便针对下标的处理,就是说如果我要处理i==10的情况是很简便的,另一个是可以比较方便的反向遍历。

Objective-C 1.0里面的NSEnumerator也可以进行遍历,代码如下:

NSEnumerator *enumerator = [langArray objectEnumerator];
id object;
while ((object = [enumerator nextObject]) != nil) {
NSLog(@”langArray=%@”, object);
}
这里我们可以看到没有下标了,通过nextObject的方法来遍历。这个方法的好处是对于遍历NSDictionary和NSSet代码也比较类似,不便的是对于下标的处理会不方便,另外反向遍历需要用reverseObjectEnumerator方法。

Objective-C发展到2.0时又有了快速遍历功能,代码如下:

for (id object in langArray) {
NSLog(@”langArray=%@”, object);
}
这里代码简洁清晰,很长时间是我写代码的首选,号称效率也*高,不过不便之处同样明显,如果算法要求知道数组的下标,这个方法就抓瞎了。另外,反向需要通过[langArray reverseObjectEnumerator]来实现。

等到block出来后,iOS里面新增加了enumerateObjectsUsingBlock:的方法,代码如下:

[langArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(@”idx=%d, id=%@”, idx, obj);
}];
这里我们看到block里面的参数包括object,下标以及是否停止遍历,应该说,这个能满足基本所有的遍历需求了,有下标,有运行的对象,还有是否继续遍历的标志。不过反向遍历呢?苹果提供了另外一个方法:

[langArray enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSLog(@”idx=%d, id=%@”, idx, obj);
}];
这个enumerateObjectsWithOptions:usingBlock:方法比enumerateObjectsUsingBlock:方法多传了一个参数,这个参数指定了遍历的顺序。

说到这里,如果我们选择正向遍历,那么这两种方法是一样的么?答案也是否定的。在enumerateObjectsWithOptions:usingBlock:方法里面,如果指定了NSEnumerationConcurrent顺序,那么底层通过GCD来处理并发执行事宜,具体实现可能会用到dispatch group。也就是说,这个会用多线程来并发实现,并不保证按照顺序执行,但效率肯定是杠杠的!

我们来看一下打印结果:

2014-06-17 15:46:44.413 testStoryboard[2703:3503] idx=32, id=hu
2014-06-17 15:46:44.413 testStoryboard[2703:1303] idx=16, id=ru
2014-06-17 15:46:44.416 testStoryboard[2703:3503] idx=33, id=vi
2014-06-17 15:46:44.412 testStoryboard[2703:60b] idx=0, id=zh-Hant
2014-06-17 15:46:44.417 testStoryboard[2703:1303] idx=17, id=pl
2014-06-17 15:46:44.417 testStoryboard[2703:60b] idx=1, id=zh-Hans
2014-06-17 15:46:44.417 testStoryboard[2703:1303] idx=18, id=tr
2014-06-17 15:46:44.419 testStoryboard[2703:60b] idx=2, id=en
2014-06-17 15:46:44.419 testStoryboard[2703:1303] idx=19, id=uk
2014-06-17 15:46:44.421 testStoryboard[2703:60b] idx=3, id=fr
2014-06-17 15:46:44.421 testStoryboard[2703:1303] idx=20, id=ar
2014-06-17 15:46:44.421 testStoryboard[2703:60b] idx=4, id=de
2014-06-17 15:46:44.422 testStoryboard[2703:60b] idx=5, id=ja
2014-06-17 15:46:44.422 testStoryboard[2703:60b] idx=6, id=nl
2014-06-17 15:46:44.421 testStoryboard[2703:1303] idx=21, id=hr
2014-06-17 15:46:44.423 testStoryboard[2703:60b] idx=7, id=it
2014-06-17 15:46:44.423 testStoryboard[2703:1303] idx=22, id=cs
2014-06-17 15:46:44.423 testStoryboard[2703:60b] idx=8, id=es
2014-06-17 15:46:44.424 testStoryboard[2703:1303] idx=23, id=el
2014-06-17 15:46:44.424 testStoryboard[2703:60b] idx=9, id=ko
2014-06-17 15:46:44.424 testStoryboard[2703:1303] idx=24, id=he
2014-06-17 15:46:44.425 testStoryboard[2703:60b] idx=10, id=pt
2014-06-17 15:46:44.425 testStoryboard[2703:60b] idx=11, id=pt-PT
2014-06-17 15:46:44.425 testStoryboard[2703:1303] idx=25, id=ro
2014-06-17 15:46:44.426 testStoryboard[2703:60b] idx=12, id=da
2014-06-17 15:46:44.426 testStoryboard[2703:1303] idx=26, id=sk
2014-06-17 15:46:44.426 testStoryboard[2703:60b] idx=13, id=fi
2014-06-17 15:46:44.426 testStoryboard[2703:1303] idx=27, id=th
2014-06-17 15:46:44.427 testStoryboard[2703:60b] idx=14, id=nb
2014-06-17 15:46:44.427 testStoryboard[2703:1303] idx=28, id=id
2014-06-17 15:46:44.428 testStoryboard[2703:60b] idx=15, id=sv
2014-06-17 15:46:44.428 testStoryboard[2703:1303] idx=29, id=ms
2014-06-17 15:46:44.429 testStoryboard[2703:1303] idx=30, id=en-GB
2014-06-17 15:46:44.429 testStoryboard[2703:1303] idx=31, id=ca
从这个结果我们可以看出,确实遍历了整个数组,但并发按照顺序从头到尾——也就是说,用到了dispatch group。这在遍历大数组而有相互独立时对于效率的提高是相当有利的,赞一个!

在iOS中,除数组外,还有NSDictionary和NSSet数据也是称为collection数据的,遍历有类似的地方,不过遍历没有数组那么频繁,方法上是差不多的。

 

iOS历史回顾(iOS1~iOS8)

苹果在9月推出了iOS8,推出了iPhone6和iPhone6+,想想我还看到身边有朋友在用iPhone1呢,当然,升级不到这么高版本了,但用的居然还是好好的,不得不说苹果的手机质量还是可圈可点的。

下面列出历史上的图:

%title插图%num

%title插图%num

%title插图%num

我们可以看到,苹果实际上每年都在推陈出新,iPhone的ID一直有变化,尺寸变薄,屏幕变大等等,也是市场的走向。但iOS软件UI的设计,实际上理念在一开始就确定了,后续有所变化,尤其是iOS7的扁平化,但还是谈不到突破旧天地。对*早的iOS1来说,Idle界面和主菜单界面合二为一,全触摸操作,块状图标。。。确确实实是伟大的作品。

UI的变迁实际上是苹果精益求精的过程,我们看几个自带应用的图标吧:

%title插图%num

%title插图%num

%title插图%num

这里我们可以看到,苹果每次发布实际都有微调,一开始是走拟物化,这条路走到巅峰之后推出了扁平化。扁平化一直很有争议,是好是坏我也说不上来,个人不是很喜欢,但确实感觉变朴素了一点。

图标本身有一定延续性,整体来说提示性比较清晰而且尽量避免使用文字。

iPhone到现在追求ID还是希望走到*致,iPhone6看到的参数,觉得做这么薄意义不是很大。看到消息说有人发现iPhone6容易弯,其实iPhone5就有这个问题。这么薄的手机,里面还要有很多器件,凭借苹果精湛的工业设计做了出来,但估计器件选型,内部散热,干涉等等都要做很多让步,而我个人实在不觉得多厚一点点销量会大幅下降——多少人都是冲着苹果超酷的体验,完善的软件生态链来的,冲着这么薄的ID似乎没怎么听说。Anyway,这个和主题无关。