[编程开发] 为什么都说python简单,可是我学了之后并不这样觉得觉得?

[复制链接]
itestit 发表于 2023-10-14 01:20:37|来自:北京 | 显示全部楼层 |阅读模式
我是一名大一电子信息学生,在上个学期已经学了C语言,但依旧觉得python挺难的,感觉有很多疑问,比如生成器,迭代器,装饰器什么的,还有就是这种,我实在是不能理解它为什么顺序是这样的,如图:


希望有大佬能帮我解答一下,谢谢各位!
全部回复5 显示全部楼层
qqsweb 发表于 2023-10-14 01:20:58|来自:北京 | 显示全部楼层
Python 相对于其他编程语言算是简单的了,如果觉得学习 Python 有些难,可能是学习姿势不正确,另外因为每个人的自身情况不同,所以也可能是自己本身并不适合学习/从事编程,这个就需要自己根据自身情况自己来确定了。
俗话说:兴趣是最好的老师,因此,在学习的时候要从自己的兴趣点入手,然后找一份通俗易懂的教程来进行学习,比如:


官方文档
Python 学习手册
Python 面试指南
Python 入门必备知识
Python 经典实用练手项目
100 个 Python 小例子(练习题)
一张思维导图囊括所有 Python 基础知识
240个Python练习案例附源码(百看不如一练)
浙江大学内部Python教程(教材PDF, PPT课件, 源码)
Python面试大全PDF(基础+Web+爬虫+数据分析+算法等)
再分享一些 Python 实战项目,毕设可用。
Python实现商场管理系统
Python实现门禁管理系统(源码)
Python实现物流管理系统(附源码)
Python 实现学生在线考试管理系统
Python 实现的学生培养计划管理系统
Python实现文献数据挖掘系统(附源码)
Python实现教务信息管理系统(附源码)
Python实现学生教师刷脸签到系统(Flask)
Python招聘岗位信息聚合系统源码(爬虫爬取、数据分析、可视化、互动等功能)
Python笔记大全(入门+爬虫+数据分析可视化+机器学习+深度学习)
chenyumai 发表于 2023-10-14 01:21:25|来自:北京 | 显示全部楼层
这个同学我觉得你应该压根没怎么看PPT或者书,直接上来就抄代码运行了。
因为这个根本不是python难不难的问题,而是python最基本的数据结构:集合set你这个概念压根就不知道。
我简单的解释下,set是不重复、且无序的。
不重复指的是不能存在重复的字符,比如下面的set4和set5,虽然在创建的时候有重复的值,但是在输出的时候自动就把重复值去掉了。


无序指的是顺序对于set是无关紧要的,因为它会自动排序,你可以看到下图的set3,创建的时候是{16,1,4,9},但是输出的时候自动变成了{1,4,9,16}。


所以说,这不是难不难的问题,而是最基本的概念你没搞懂。
同样的,python里面有类似的数据结构,比如下图。


对于想要学下python的同学,可以参考下这个回答:
毫无基础的人如何入门 Python ?以上。
lianyuan1986 发表于 2023-10-14 01:22:02|来自:北京 | 显示全部楼层
题中描述的问题很简单,Python中set是“集合”,就像你在数学里学的集合一样,其中的元素是没有顺序的,因此每次输出都有可能按照不同的顺序输出。
<hr/>“简单”这个词是建立在不同意义上的。如果说C语言简单,那是因为C语言语法很少,内容量少。说Python简单,是因为Python上手很快,也没有指针等令人费解的概念,C语言要写一个基本的Hello World程序还要写个main函数,Python就是简单的一句print('Hello World!'),这就是为什么大家说Python简单。
如果平时就写点“求素数”这样的代码,那么你或许会觉得C也很简单:
#include <stdio.h>

int isPrime(int n)
{
    if (n <= 1)
    {
        return 0;
    }
    if (n == 2)
    {
        return 1;
    }
    if (n % 2 == 0)
    {
        return 0;
    }
    for (int i = 3; i <= sqrt(n); i += 2)
    {
        if (n % i == 0)
        {
            return 0;
        }
    }
    return 1;
}

int main()
{
    for (int i = 0; i < 100; i++)
    {
        if (isPrime(i))
        {
            printf("%d\n", i);
        }
    }
    return 0;
}相比之下,Python的代码似乎并没有更简单,只不过是少了点花括号:
def isPrime(n):
    if n <= 1:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    for i in range(3, int(n ** 0.5) + 1, 2):
        if n % i == 0:
            return False
    return True

for i in range(100):
    if isPrime(i):
        print(i)即使对于C语言指针的应用,一般上课学的也就是写点诸如swap这样的小函数。在这个层面上,自然不会感觉到Python比C“简单”,这是很正常的思路。
产生“Python比C语言更复杂”的想法,也合情合理。C语言关键字都没几个,也没有面向对象,连链表都要自己写,只要搞懂了指针,C语言就算是学懂了一大半了,当然十分简单。而反观Python,面向对象、切片、正则表达式、解包、生成器与迭代器、异常处理、装饰器……似乎每一个都很容易把人搞晕。
事实上,Python的实际复杂性甚至要远大于许多常见的语言,这要归功于Python极为庞大的标准库。稍微翻翻标准库,还能看到dataclass、type hints、setter/getter、asyncio……现在还没写完的《Fluent Python, 2nd》英文版早早就超过1000页了,而仅仅在五六年前,《Fluent Python》第一版还只有现在版本的大约一半。Python膨胀的速度看起来甚至有些恐怖。反观之下,C语言标准的更新就慢得多了,也很多年没有出现明显变化了。在这样的情况下,我们还能说Python“简单”吗?
答案是,如果要从“初学者友好”的角度,Python自然还是“简单”的。正如上面所说的,Python写个HelloWorld只要一行print("Hello World!"),这行代码没有任何值得费解的地方,一个叫作print()的函数,初学者只需要知道这样写能够打印文字,一个"Hello World!"字符串,理解起来也没有任何难度,然后就结束了。C语言的初学者也许还要问一下“int main()”是什么意思,为什么要有“return 0;”,又为什么要加入“#include<stdio.h>”,而要理解这些就要理解函数定义、返回值、库的概念。
Python更“简单”之处在于只需学习简单的语法就可以投入实用。Python跟着老师学点选择判断循环,学学import,然后就可以import urllib,开始写爬虫了。再学点面向对象的概念,就可以import pygame,写小游戏了。而C语言的情况则是,很多人学了好几个月,还是只能对着黑底白字看控制台输出。
Python那些更“高阶”的特性并不会使人产生负担。C++作者曾表示“当你不需要使用编程语言中的某个特性时,它不应让你产生负担”(虽然很明显C++中太多的语法特性确实让人产生负担了,这点上C++很失败),而Python在这一点上做得很好。迭代器、生成器、装饰器在初学时确实是令人费解的语法,但不理解它们并不影响你如何编写Python代码。每个人刚开始学习Python循环时就会遇到“for i in range(n)”,但很明显初学循环时不需要你去理解“range()”返回一个可迭代对象,大家一样很正常地写出了循环。其他语言特性当然也是同理。
那么,C语言真的就“简单”吗?如果要论语法的多少,那么估计除了Lisp等少数语言,没有几个高级语言比C语言更简单了。但一个连链表都要自己写的语言,也没有面向对象,一旦用来写稍微成规模的项目,函数名就会以不可预料的速度疯狂变长。当需要编写实用程序时,C语言就不能像平时写作业一样不释放内存了,各种乱飞的指针、溢出的栈区令人抓狂。
C语言不是真的“简单”,而是“简陋”,并且简陋到了在大多数C语言项目中,不得不大量使用宏定义魔法来使得项目不会随着规模的增长最终变得无法维护。且这些宏定义的编写必须非常小心,否则一不留神就会使得项目变得更加混乱。当然这种简陋也带给了C语言在底层开发及嵌入式开发中无法取代的优势,这就是另一个话题了。
<hr/>实际上,Python中许多相比起C语言的令人感到“费解”的特性,都是在应用中被广泛证实的、有助于写出更清晰、更易维护代码的特性。Python中的很多特性并非其独有,问题描述中的生成器、迭代器和装饰器,都是被许多语言采纳并广泛运用的特性。
迭代器的目的是让人远离底层的计数变量变化,直接遍历可迭代对象中的元素。几乎在任何语言中,在能换成迭代的地方把计数循环改成迭代,都能使代码简化:
Java中:
int[] arr = { 1, 2, 3, 4, 5 };
// 传统计数循环
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr);
}
// 迭代
for (int num: arr) {
    System.out.println(num);
}C#中:
int[] arr = { 1, 2, 3, 4, 5 };
// 传统计数循环
for (int i = 0; i < arr.Length; i++)
{
    Console.WriteLine(arr);
}
// 迭代
foreach (int num in arr)
{
    Console.WriteLine(num);
}
JavaScript中:
const arr = [1, 2, 3, 4, 5]
// 传统计数循环
for (let i = 0; i < arr.length; i++) {
  console.log(arr)
}
// 迭代
for (const num of arr) {
  console.log(num)
}
C++中:
#include <cstdio>
#include <iostream>
#include <array>
#define LEN(array, type) (sizeof(array) / sizeof(type))
using namespace std;

int main()
{
    array<int, 5> arr = {1, 2, 3, 4, 5};
    // 传统计数循环
    for (auto i = 0; i < LEN(arr, int); i++)
    {
        cout << arr << endl;
    }
    // 迭代
    for (auto num : arr)
    {
        cout << num << endl;
    }
}
Go中:
arr := [5]int{1, 2, 3, 4, 5}
// 传统计数循环
for i := 0; i < len(arr); i++ {
        fmt.Println(arr)
}
// 迭代
for _, num := range arr {
        fmt.Println(num)
}
Python中:
lst = [1, 2, 3, 4, 5]
# 传统计数循环
for i in range(len(lst)):
    print(lst)
# 迭代
for num in lst:
    print(num)生成器的价值在于避免占用不必要的内存,并使函数代码更加清晰,同时也可用于生成无限长可迭代对象:
在JavaScript中:
// 生成0~n的平方数序列
function * generateSquares (n) {
  for (let i = 0; i < n; i++) {
    yield i ** 2
  }
}

for (const n of generateSquares(10)) {
  console.log(n)
}

// 生成无限长斐波那契数列
function * generateFibonacci () {
  let a = 1
  let b = 1
  while (true) {
    yield a
    ;[a, b] = [b, a + b]
  }
}

for (const n of generateFibonacci()) {
  if (n > 100) break
  console.log(n)
}
在Python中:
# 生成0~n的平方数序列
def generate_squares(n):
    for i in range(n):
        yield i ** 2

for n in generate_squares(10):
    print(n)

# 生成无限长斐波那契数列
def generate_fibonacci():
    a = b = 1
    while True:
        yield a
        a, b = b, a + b

for n in generate_fibonacci():
    if (n > 100):
        break
    print(n)

# 使用生成器表达式减少内存占用
print(sum(n ** 2 for n in range(10000000)))
# 而不是print(sum([n ** 2 for n in range(10000000)]))装饰器的作用是简化函数/方法,用来进行输入检查、预操作等。装饰器的目的仅仅是为了“面向切面编程”,方便地为函数/类/方法等添加功能:
在TypeScript中(TS中的装饰器只能用在方法或类上):
function cacheIt (
  target: any,
  propertyKey: string,
  descriptor: PropertyDescriptor
): PropertyDescriptor {
  const originalMethod = descriptor.value
  const cache = new Map<any[], any>()
  descriptor.value = function (...args: any[]) {
    for (const key of cache.keys()) {
      if (key.every((value, index) => value === args[index])) {
        return cache.get(key)
      }
    }
    const result = originalMethod.apply(this, args)
    cache.set(args, result)
    return result
  }
  return descriptor
}

class E {
  @cacheIt
  public fibonacci (n: number): number {
    if (n <= 1) {
      return 1
    }
    return this.fibonacci(n - 1) + this.fibonacci(n - 2)
  }
}

const e = new E()
console.log(e.fibonacci(100))
在Python中:
def cache_it(func):
    cache = {}
    def wrapper(*args):
        if args in cache:
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

@cache_it
def fibonacci(n):
    if n <= 1:
        return 1
    else:
        return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(50))
类似的例子还有很多。例如dataclass的作用是快速创建一个只用于存储数据的简单类,Java 17中也加入了Record,有类似的作用,同理C#中也包含record,而类似的特性在Scala等语言中也早有存在。又如asyncio中async/await的作用是简化异步逻辑,避免过多的回调和不易维护的多线程,这在JS和C#中也有出现。再如type hints的作用是在保留Python动态特性的前提下使代码的可维护性更强。
可以看到,所有看似复杂的语法实际目的都是为了使得编程变得“简单”。事实上,迭代器、生成器、装饰器本就是面向对象中很常见的设计模式,只不过Python将它们变成了实实在在的语法特性。这不是一件坏事,太多“设计模式”的出现本就是因为语言设计存在一些缺陷,导致人们不得不记住特定的模板来编写代码。如果这些惯用的设计模式被语言本身支持,那实际上是简化了编程人员的心智负担。
事实上,我更愿意称Python是“简洁”的而不是“简单”的,且它在“简洁”和“易用”上做到了非常好的平衡。它不像C++一样因历史原因不得不在本就不够简洁的设计上不断堆砌新特性,使得越来越多的特性反而使其变成了一个令人望而生畏的庞然大物;它也不像Ruby一样追求大量的语法糖,一种行为可以以很多种不同的实现方式,导致每个人写出来的代码都有极强的个人风格,难以统一;它也不像Scala等偏函数式的语言一样在语言层面上就需要涉及不少数学层面的概念,天生有较高的学习门槛;它不像Go一样追求“仅包含必要的语言特性”,导致代码不够美观;它不像JavaScript一样有太重的历史包袱,最初的糟粕设计一直延续至今;它不像Java一样追求完全面向对象与编译期安全,导致代码死板冗长;它更不像Rust一样追求极致的安全,导致程序员不得不与编译器斗智斗勇。
我并不认为Python真的比其他语言“更好”,但在“简洁”这方面,我认为它在常见的编程语言中是做得最好的。正如之前提到的,Python随着版本号的不断迭代实际上已经变成了一个庞然大物,但为什么我们好像没有像其他很多语法特性膨胀的语言一样感到Python“复杂难学”呢?照我个人的想法,这很大一部分原因是Python在基本语法层面上保持着一定程度的“克制”。
Python只将那些经过验证的、最实用的语法放到基本语法中,而其他在特定情况下实用的语法都包含于标准库中。例如一开始,reduce()函数实际上是和map()函数一样可直接调用的,后来则加入了functools标准库。而asyncio中的async/await关键字,最初是需要使用"@async/@await"调用的,而后来考虑到异步操作非常常见,因而可以不需要使用"@"。
可以看到,Python往往将一些有用而不是那么常用的语法特性独立到标准库或隐藏到诸多装饰器中,当你不需要它们时,它们就仿佛完全不存在。这很大程度上减小了语法膨胀给人带来的压力。这也是为什么好像其他语言的程序员总倾向于不断学习,担忧跟不上语言的发展速度,而Python程序员大多不会这样,而是倾向于以“尝鲜”的方式接触新特性。Python将“可选的语言特性”真正变成了可选,而不会给程序员太多心智负担。这当然很大程度上要归功于Python独特的社区生态,但Python语言本身的设计也有不可忽视的作用。
<hr/>我想在最后,以Python之禅结尾再合适不过了:
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
优美、清晰、简单、易理解(即使需要多写一些代码)、扁平、朴素、可读、减少特例、不放过异常、不模棱两可、有且最好仅有一种方法解决一种问题、可解释、命名空间,这些词差不多总结了Python之禅中的内容。从这里就可以看到,Python从理念上就对简单、优雅与易理解非常重视,我想正是这些造就了Python如今的成功。
網絡被詐騙錢財 发表于 2023-10-14 01:22:53|来自:北京 | 显示全部楼层
以列表初始化set对象时,在C语言层面,做了一次按位与的操作,
static int
set_add_entry(PySetObject *so, PyObject *key, Py_hash_t hash)
{
...
    size_t mask;
...
    mask = so->mask;
    i = (size_t)hash & mask;

    while (1) {
        entry = &so->table;
...
}cpython的代码里面,取数据的时候,是把1,4,9,16 hash之后,再mask按位与,
那mask是什么,定义如下
/* The table contains mask + 1 slots, and that's a power of 2.
* We store the mask instead of the size because the mask is more
* frequently needed.
*/
Py_ssize_t mask;mask是初始化set大小再减1,set初始化大小是8,那mask就是7
>>> 1&7
1
>>> 4&7
4
>>> 9&7
1
>>> 16&7
0依次按位与之后,结果是1,4,1,0,
所以16在最前面,1和9的结果都是1,1是第一个,9是冲突的那一个
perturb >>= PERTURB_SHIFT;
i = (i * 5 + 1 + perturb) & mask;冲突的会再进行上面两行的计算,
perturb就是hash的值,为9
PERTURB_SHIFT固定值,为5
perturb右位移之后,值为0
那么i就等于 (1 * 5 + 1 + 0) & 7 = 6
所以最终输出的是 16,1,4,9
一抹白云 发表于 2023-10-14 01:23:33|来自:北京 | 显示全部楼层
装饰器这些属于“让Python发挥出特有优势”的语法糖一类的进阶技能,不会不影响你把这玩意当c写。而且语法糖这种东西你不会就是你没有这个需求,当你要自己去对一堆函数包装个计时之类的壳然后写代码被自己恶心到的时候你自然就会用了。
而你下面这个疑问属于中学数学没学好和什么语言关系不大,集合本来就是不讲顺序的,对集合进行迭代理所应当不应该对其顺序有任何的期待,程序以任何顺序输出集合元素、在多轮迭代中以不同次序输出都是完全合理的事情,毕竟是你亲自显式告诉程序“我不在乎这堆元素的顺序”的。
不服你大可以在命令行里打一句{1,4,9,16}=={16,1,4,9}

快速回帖

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则