私信  •  关注

ShadowRanger

ShadowRanger 最近创建的主题
ShadowRanger 最近回复了
2 年前
回复了 ShadowRanger 创建的主题 » 分析Python的Dict insert运行时间有什么错?

事实上,重播 O(n) 成本,但考虑到它很少见,而且相对便宜(散列是预先计算和缓存的,并且您知道现有元素都不相等,所以可以假设一个完整的桶不相等),这通常不是什么大问题。

所以不,任何给定的操作实际上可能是 O(n) .但很像 list 扩张,这是 摊销 O(1) O(1) 插入;即使没有重新灰化,也可以进行给定的插入 O(n) 如果您设法确保插入的所有项都以相同的哈希值模化基础存储大小,但Python会将内置类型的哈希值最小化)。

它仍然存在的原因 O(1) 摊销是指每个元素(重新)插入的平均数量仍受一个常数乘数的约束,该乘数在大O术语中是可忽略的。你可能是在平均 O(2) O(3) 每次插入,但因为 数字 再烧一次就好了 向下 大小 (完成的工作)在重新灰烬中,它真正做的只是意味着你正在更频繁地进行插入工作,但这并没有增加 每个元素的平均值 以成本为基础 dict 生长。

1 年前
回复了 ShadowRanger 创建的主题 » Python是类在同一个类的定义中的实例

你可以参考 my_class 它的内部很好。你不能提及 我的班 当它被定义时(在任何方法之外,在类定义范围的顶层,或在函数的参数定义中),但是你可以在一个方法中引用它,直到你完成定义后才会被调用 我的班 .这里唯一的问题是基于字符串的注释有点难看,可以绕过自引用限制,这可以通过 __future__ 进口

from __future__ import annotations  # Supported as of 3.7, on by default beginning in 3.11

class my_class:
    def __add__(self, other: my_class) -> my_class:
        if isinstance(other, some_other_class):
            return other + self
        elif isinstance(other, my_class):
            return <special_adding_technique>
        return NotImplemented   # When you can't work with the other type, you're supposed
                                # to return the NotImplemented singleton, not raise
                                # NotImplementedError, where the latter is for completely
                                # unimplemented functionality, usually in an ABC

顺便说一句,Python类是 lowercase lowercase_with_underscores ; 类使用 CapWords ,所以这个类应该命名为 MyClass 遵守PEP8。

1 年前
回复了 ShadowRanger 创建的主题 » 将项目从python文件写入字典

x = len(f.readlines()) 消耗了你的整个文件,所以你接下来的循环就结束了 f 正在迭代耗尽的文件句柄,看不到剩余的行,并且立即存在。

这里不需要预先检查长度(也是你唯一使用的长度) x 试图给它编制索引,这毫无意义;你避开了 TypeError 仅仅是因为循环从未运行过),所以只需忽略这一点,然后使用 enumerate 要边走边获取数字,请执行以下操作:

def readScoresFile(fileAddr):
    dic = {}
    with open(fileAddr, "r") as f:
        for i, line in enumerate(f):  # Let enumerate manage the numbering for you
            dic["score_set{}".format(i)] = line  # If you're on 3.6+, dic[f'score_set{i}'] = line is nicer
    return dic

请注意,这实际上并不会将输入行转换为 list s的 int (你的原始代码也没有)。如果你想这样做,你可以改变:

dic[f'score_set{i}'] = line

致:

dic[f'score_set{i}'] = ast.literal_eval(line)  # Add import ast to top of file

要将该行解释为Python文字,或:

dic[f'score_set{i}'] = json.loads(line)  # Add import json to top of file

将每一行解释为JSON(速度更快,但支持的Python类型更少,一些合法的Python文本不是合法的JSON)。

一般来说,你根本不想使用 .readlines() ; 只需在文件句柄上进行迭代,就可以激活这些行,并避免与文件大小成比例的内存需求。(坦白地说,如果他们在Py3中处理掉它,我会更喜欢,因为 list(f) 如果你真的需要它,它也会得到同样的结果,而且它不会创建一个可见的方法来鼓励你经常做“错误的事情”)。

通过逐行操作,您最终可以存储所有 数据 ,但这比将解析后的数据存储在 dict 所有的字符串数据都来自 列表 .

1 年前
回复了 ShadowRanger 创建的主题 » Python对变量的重新声明在内部是如何工作的?

通过分解的媒介进行解释:

>>> dis.dis('''greeting = 'hello'
... greeting = f'y{greeting[1:len(greeting)]}'
... ''')
  1           0 LOAD_CONST               0 ('hello')
              2 STORE_NAME               0 (greeting)

  2           4 LOAD_CONST               1 ('y')
              6 LOAD_NAME                0 (greeting)
              8 LOAD_CONST               2 (1)
             10 LOAD_NAME                1 (len)
             12 LOAD_NAME                0 (greeting)
             14 CALL_FUNCTION            1
             16 BUILD_SLICE              2
             18 BINARY_SUBSCR
             20 FORMAT_VALUE             0
             22 BUILD_STRING             2
             24 STORE_NAME               0 (greeting)
             26 LOAD_CONST               3 (None)
             28 RETURN_VALUE

最左边的数字表示特定行字节码的起始位置。第1行非常简单,所以我将解释第2行。

正如您可能注意到的,您的f字符串在编译后无法保存;它变成了一堆原始操作码,将常量段的加载与格式占位符的计算混合在一起,最终导致堆栈顶部出现构成最终字符串的所有片段。当它们都在堆栈上时,它会将所有的片段放在一起,最后用 BUILD_STRING 2 (上面写着“从堆栈中取出最上面的两个值,并将它们组合成一个字符串”)。

greeting 只是一个有约束力的名字。它实际上并不包含一个值,只是对它当前绑定到的任何对象的引用。原始引用被推到堆栈上(使用 LOAD_NAME )完全是在 STORE_NAME 这会弹出堆栈顶部并重新绑定 招呼 .

简而言之,它之所以有效,是因为 招呼 被替换时不再需要;它被用来生成新字符串,然后被丢弃,取而代之的是新字符串。

1 年前
回复了 ShadowRanger 创建的主题 » 如何在向量类中添加不同的向量?python

fill_n 很明显 预定的 Vector ,但在实现时,不会执行类似的操作,因此会出现类型冲突。使其成为具有 @classmethod ,您将得到您想要的结果,而无需进行其他更改:

@classmethod                 # Ensures it passes the class even when called on an instance
def fill_n(cls, size, val):  # Added initial parameter, cls, the actually class called on
    value=[val]*size
    return cls(value)  # Changed from tuple to cls which will be Vector (or a subclass)

一般来说, 全部的 替代构造函数应该是 @类方法 s具有 @staticmethod ,您将不会对子类友好,因为未修饰的方法会忽略 self / cls 它只在类而不是实例上被调用时才起作用(这使得从现有实例的类构造新实例变得更加困难),使用实例方法,您可以 不能 在课堂上说吧。

4 年前
回复了 ShadowRanger 创建的主题 » Python动态循环范围大小

使用 range 将您锁定到特定的迭代次数; range(len(m)) 构造 使用的值 len(m) 范围 s是不变的)。你 这种行为,与您希望的方式相同:

s = '123'
i = int(s)
s = 'abc'

离开 i 123 ,而不是在 int 'abc' (允许 范围(len(m)) m 改变在道德上是对等的,同样是疯狂的)。

修改 list 当你重复它的时候,它是不受欢迎的,特别是当你 insert 列表 ,或删除元素。这太容易出错了;在这种情况下,当 j ,和 插入 i + 1 下一步),同时将其插入 j > i 将由外部循环而不是内部循环看到(在这两种情况下,值 m[j]

一般来说,更安全的解决方案是 从头开始,从现有的 列表 根据需要,以及新的元素到最后。速度也快得多 插入 列表 O(n) 对于 每个 插入 O(m * n) 哪里 是所需的插入次数, n 是输入大小),而 append O(1) (总成本 O(m + n) ).

4 年前
回复了 ShadowRanger 创建的主题 » Python动态循环范围大小

创建 新的 列表:

new = []
tag = None
for line in m:
    if line.startswith('['):
        if tag:
            new.append('Link = ' + tag)
        tag = line
    new.append(line)
4 年前
回复了 ShadowRanger 创建的主题 » 在python中用父类方法重写init

super().with_two_legs('Human') 事实上 Animal with_two_legs ,但它通过了 Human 作为 cls ,不是 动物 . super() 使代理对象仅用于帮助方法查找,它不会更改传递的内容(它仍然是相同的 self cls公司 它起源于)。在这种情况下, 超级() 什么都没用,因为 不覆盖 有两条腿 ,所以:

super().with_two_legs('Human')

意思是“呼叫 有两条腿 一等以上 在定义它的层次结构中”,以及:

cls.with_two_legs('Human')

意思是“呼叫 有两条腿 在层次结构中的第一个类上 cls公司 这就是它的定义”。只要下面没有课 动物 定义它,它们做同样的事情。

这意味着你的代码在 return cls(name_full, 2) ,因为 cls公司 仍然 ,以及您的 Human.__init__ 不带任何争论 自己 . 即使你想让它起作用(例如添加两个你忽略的可选参数),这也会导致无限循环,如 人类__ 打电话 Animal.with_two_legs ,它反过来试图构造 ,呼叫 人类__ 再一次。

您要做的并不是一个好主意;根据其性质,备用构造函数依赖于类的核心构造函数/初始值设定项。如果尝试生成依赖于备用构造函数的核心构造函数/初始值设定项,则创建了循环依赖项。

在这种情况下,我建议避免使用备用构造函数,而是显式地提供 legs 始终计数,或使用中间值 TwoLeggedAnimal 类执行备用构造函数的任务。如果你想重用代码,第二个选项就意味着你的“从名称生成完整名称的非常长的代码”可以进入 双腿动物 __init__ ;在第一个选项中,您只需编写 staticmethod 这就排除了代码的因素,所以这两种代码都可以使用 有两条腿 以及其他需要使用它的构造函数。

类层次结构类似于:

class Animal:
    def __init__(self, name, legs):
        self.legs = legs
        print(name)

class TwoLeggedAnimal(Animal)
    def __init__(self, name):
        # extremely long code to generate name_full from name
        name_full = name
        super().__init__(name_full, 2)

class Human(TwoLeggedAnimal):
    def __init__(self):
        super().__init__('Human')

相反,通用代码方法类似于:

class Animal:
    def __init__(self, name, legs):
        self.legs = legs
        print(name)

    @staticmethod
    def _make_two_legged_name(basename):
        # extremely long code to generate name_full from name
        return name_full

    @classmethod 
    def with_two_legs(cls, name):
        return cls(cls._make_two_legged_name(name), 2)

class Human(Animal):
    def __init__(self):
        super().__init__(self._make_two_legged_name('Human'), 2)

旁注:即使你处理递归,你所要做的也不会起作用,因为 __初始__ 制作 新实例,它初始化现有实例。所以即使你打电话 super()。有两条腿(“人类”) 它以某种方式工作,它生成并返回一个完全不同的实例,但对 自己 接收人 __初始__ 这就是真正被创造出来的东西。你能做的最好的事情是:

def __init__(self):
    self_template = super().with_two_legs('Human')
    # Cheaty way to copy all attributes from self_template to self, assuming no use
    # of __slots__
    vars(self).update(vars(self_template))

无法在中调用备用构造函数 __初始__ 换个衣服 自己 含蓄地。我能想到的唯一办法是,在不创建helper方法和保留备用构造函数的情况下,使用 __new__ 而不是 __初始__ (这样您就可以返回一个由另一个构造函数创建的实例),并使用备用构造函数来显式调用顶级类的 __新的__ 要避免循环调用依赖项:

class Animal:
    def __new__(cls, name, legs):  # Use __new__ instead of __init__
        self = super().__new__(cls)  # Constructs base object
        self.legs = legs
        print(name)
        return self  # Returns initialized object
    @classmethod
    def with_two_legs(cls, name):
        # extremely long code to generate name_full from name
        name_full = name
        return Animal.__new__(cls, name_full, 2)  # Explicitly call Animal's __new__ using correct subclass

class Human(Animal):
    def __new__(cls):
        return super().with_two_legs('Human')  # Return result of alternate constructor
5 年前
回复了 ShadowRanger 创建的主题 » python tempfile读写

临时文件仍然是文件;它们有一个指向文件中当前位置的“指针”。对于新编写的文件,指针位于最后一次写入的末尾,因此如果 write 没有 seek ing,你从文件的末尾读,什么也得不到。只需添加:

tmp.seek(0)

之后 你会在接下来的时间里收到你写的东西 read / readlines .

如果目标仅仅是使数据对其他按名称打开文件的程序可见,例如 nano 在注释掉的代码中,可以跳过 寻求 ,但您确实需要确保数据从缓冲区刷新到磁盘,因此在 ,您将添加:

tmp.flush()
4 年前
回复了 ShadowRanger 创建的主题 » 在python中,如何使用不可变类型对copy执行浅更新?(结构共享)

namedtuple 支持此行为 the ._replace method (尽管前面有下划线,但这是一个公共方法,它的前缀只是下划线,以避免与用户定义的字段冲突),而且它实际上是不可变的(覆盖 __setattr__ 并不是完全不变的)。

from collections import namedtuple

Foo = namedtuple('Foo', 'year album')

foo1 = Foo(1996, 'Album1')
foo2 = foo1._replace(year=1997)

如果不需要与较旧版本的Python兼容,并且希望有一种简单的方法向类中添加其他行为, the newer typing.NamedTuple 是一种更灵活的方法来命名命名元组。

4 年前
回复了 ShadowRanger 创建的主题 » 多线程python访问似乎是同步的

问题是写操作是被缓冲的,所以gil实际上并没有被释放(它只在缓冲区实际被写出来时才被释放,通常只有在缓冲区已满或文件显式地 flush ED或 close d)由于每个线程所做的工作非常少,它们永远不会运行足够长的时间来释放gil,因为超时,并且永远不会真正写入磁盘,它们永远不会因为开始阻塞系统调用而释放gil。

如果你成功了 脸红 对于每一行(或者使缓冲区足够小,以至于在完成所有的 write s),您将看到预期的交错。一种方法是改变:

with open(temp_filename, mode='a') as writer:

致:

with open(temp_filename, mode='a', buffering=1) as writer:

在哪里? buffering=1 表示缓存线。

4 年前
回复了 ShadowRanger 创建的主题 » 将egcd公式转换为python

a / b 在python 3中是“真除法”,结果是不截断浮点除法,即使两个操作数都是 int S.

修复,要么使用 // 相反(这是楼层划分):

q = a // b

use divmod 要同时执行除法和余数运算,请替换这两行:

q = a / b
r = a % b

只有:

q, r = divmod(a, b)
5 年前
回复了 ShadowRanger 创建的主题 » python在创建新对象时设置属性__

你必须明确地绕过你自己的班级 __setattr__ 打电话给 super 或根 object 第二组 . 所以你会改变:

obj.customAttr = someVal

到:

object.__setattr__(obj, 'customAttr', someVal)

不太普遍的方法(不适用于 __slots__ 是直接分配给 __dict__ 使用 dict 操作:

obj.__dict__['customAttr'] = someVal  # Equivalently: vars(obj)['customAttr'] = someVal

第一种方法是 the newly __slots__ -ed uuid.UUID 现在使用; before it became __slots__ -ed, it used the second approach . 在这两种情况下都需要这样做,因为他们使用相同的 第二组 使类型尽可能不可变的诀窍(不必麻烦地进行子类化 tuple , a la typing.NamedTuple )。

5 年前
回复了 ShadowRanger 创建的主题 » python:从文件中读取最后的'n'行[重复]

在评论者的要求下发布答案 my answer to a similar question 使用相同的技术来改变文件的最后一行,而不仅仅是得到它。

对于一个大文件, mmap 是最好的办法。改善现有的 MMAP 答:这个版本在Windows和Linux之间是可移植的,并且应该运行得更快(尽管在32位Python上,如果文件在GB范围内,如果不做一些修改,它将无法工作,请参阅 other answer for hints on handling this, and for modifying to work on Python 2 )

import io  # Gets consistent version of open for both Py2.7 and Py3.x
import itertools
import mmap

def skip_back_lines(mm, numlines, startidx):
    '''Factored out to simplify handling of n and offset'''
    for _ in itertools.repeat(None, numlines):
        startidx = mm.rfind(b'\n', 0, startidx)
        if startidx < 0:
            break
    return startidx

def tail(f, n, offset=0):
    # Reopen file in binary mode
    with io.open(f.name, 'rb') as binf, mmap.mmap(binf.fileno(), 0, access=mmap.ACCESS_READ) as mm:
        # len(mm) - 1 handles files ending w/newline by getting the prior line
        startofline = skip_back_lines(mm, offset, len(mm) - 1)
        if startofline < 0:
            return []  # Offset lines consumed whole file, nothing to return
            # If using a generator function (yield-ing, see below),
            # this should be a plain return, no empty list

        endoflines = startofline + 1  # Slice end to omit offset lines

        # Find start of lines to capture (add 1 to move from newline to beginning of following line)
        startofline = skip_back_lines(mm, n, startofline) + 1

        # Passing True to splitlines makes it return the list of lines without
        # removing the trailing newline (if any), so list mimics f.readlines()
        return mm[startofline:endoflines].splitlines(True)
        # If Windows style \r\n newlines need to be normalized to \n, and input
        # is ASCII compatible, can normalize newlines with:
        # return mm[startofline:endoflines].replace(os.linesep.encode('ascii'), b'\n').splitlines(True)

这假定尾线的数量足够小,你可以安全地将它们全部读入内存中;也可以使这成为一个生成器函数,并通过替换最后一行来手动读取一行。

        mm.seek(startofline)
        # Call mm.readline n times, or until EOF, whichever comes first
        # Python 3.2 and earlier:
        for line in itertools.islice(iter(mm.readline, b''), n):
            yield line

        # 3.3+:
        yield from itertools.islice(iter(mm.readline, b''), n)

最后,以二进制模式读取(必须使用 MMAP )所以它给了 str 行(py2)和 bytes 线条(PY3);如果需要 unicode (PY2)或 STR (py3),可以调整迭代方法来为您解码和/或修复换行符:

        lines = itertools.islice(iter(mm.readline, b''), n)
        if f.encoding:  # Decode if the passed file was opened with a specific encoding
            lines = (line.decode(f.encoding) for line in lines)
        if 'b' not in f.mode:  # Fix line breaks if passed file opened in text mode
            lines = (line.replace(os.linesep, '\n') for line in lines)
        # Python 3.2 and earlier:
        for line in lines:
            yield line
        # 3.3+:
        yield from lines

注意:我在一台无法访问python进行测试的机器上输入这些内容。请告诉我,如果我打字什么,这是足够的相似。 my other answer 那我 认为 它应该可以工作,但是调整(例如处理 offset )可能会导致微妙的错误。如果有任何错误,请在评论中告诉我。

5 年前
回复了 ShadowRanger 创建的主题 » 使用python 3中的换行符将字符串写入csv

根据您的评论,您正在接受的数据实际上不包括回车或换行,它包括表示 逃逸 对于回车和换行(所以它实际上有一个反斜杠, r 反斜杠, n 在数据中)。它已经在你想要的形式中了,所以你不需要涉及 csv 模块中,只需解释转义到正确的值,然后直接写入数据。

这是相对简单的使用 unicode-escape 编解码器(也处理ASCII转义):

import codecs  # Needed for text->text decoding

# ... retrieve data here, store to res ...

# Converts backslash followed by r to carriage return, by n to newline,
# and so on for other escapes
decoded = codecs.decode(res, 'unicode-escape')

# newline='' means don't perform line ending conversions, so you keep \r\n
# on all systems, no adding, no removing characters
# You may want to explicitly specify an encoding like UTF-8, rather than
# relying on the system default, so your code is portable across locales
with open(title, 'w', newline='') as f:
    f.write(decoded)

如果收到的字符串实际上是用引号括起来的(所以 print(repr(s)) 包括两端的引号),它们可能被解释为JSON字符串。在这种情况下,只需更换 import 创造 decoded 用:

import json


decoded = json.loads(res)
5 年前
回复了 ShadowRanger 创建的主题 » python-为什么不可变的对象不占用相同的内存

因为广义实习 全部的 解释器中不可变的对象是复杂的,并且添加了大量的代码,这些代码很少能保存任何有价值的东西。

也就是说,你的代码 使用 tuple 在cpython参考解释器上。这是一个实现细节,所以每个解释器都可以在这里做出自己的决定,我猜微丝盒并没有选择这样做(可能是为了让解释器足够简单,能够在较弱的硬件上运行)。

看起来Micropython执行缓存 int 常量,但不适用于 元组 S; 元组 s很难处理(至少在最初,cpython没有在主a s t阶段执行此操作,它只是对生成的字节代码运行一个窥视孔优化器来转换 LOAD_CONST 其次是 BUILD_TUPLE 仅使用 装入常数 结果到 装入常数 结果的 元组 )并且所涉及的额外工作可能被认为是不值得的。

5 年前
回复了 ShadowRanger 创建的主题 » python高效的字典中的并行列表排序

zip 将键放在一起,根据相关项对键函数进行排序,然后 拉链 再次恢复原始表单:

sorted_value_groups = sorted(zip(*unsorted_my_dict.values()), key=lambda _, it=iter(unsorted_my_dict['key_three']): next(it))
sorted_values = zip(*sorted_value_groups)
sorted_my_dict = {k: list(newvals) for k, newvals in zip(unsorted_my_dict, sorted_values)}

一点也不干净,我主要是为了好玩才贴的。一个班轮是:

sorted_my_dict = {k: list(newvals) for k, newvals in zip(unsorted_my_dict, zip(*sorted(zip(*unsorted_my_dict.values()), key=lambda _, it=iter(unsorted_my_dict['key_three']): next(it))))}

这是因为,当 dict 迭代顺序不保证在3.7之前,对于未修改的订单,该顺序保证可重复。 双关语 . 同样, key 函数从开始到结束都是按顺序执行的,所以通过重复迭代来提取键是安全的。我们只需分离所有值,按索引对它们进行分组,按索引键对组进行排序,按键对它们进行重新分组,然后将它们重新附加到原始键上。

输出完全按照要求进行(原始键的顺序保留在cpython 3.6或任何python 3.7或更高版本上):

sorted_my_dict = {
   'key_one': [1,6,3,2],
   'key_two': [4,1,7,9],
   'key_three': [1,2,3,4]
}
5 年前
回复了 ShadowRanger 创建的主题 » 没有in的python列表理解

让我们把它分成几行:

vocab = [           # line0
         x          # line1
         for        # line2
         x, count   # line3
         in
         c.items()
         if
         count>=2]  # line7

tuple c.items() 由一个键组成, x (数的东西)和 count (看到该键的次数)。

在每个循环中,您可以想象下一个循环 元组 被提取,然后解包,这样就不需要在索引中使用单个值 0 1 ,您可以通过名字来引用它们; anontuple[0] 变成 X , anontuple[1] 变成 计数 .

这个 count>=2 行然后过滤结果;如果 计数 小于 2 ,我们停止处理此项目,并拉下一个项目。

平原 X 最左边是要生成的项;当过滤检查通过时,我们推送相应的 X 在结果中 list 未修改的

转换为常规循环时,将如下所示(与listcomp行匹配的行):

vocab = []                  # line0
for x, count in c.items():  # lines 2-5
    if count >= 2:          # lines 6-7
        vocab.append(x)     # line1

如果解包对您来说很困惑,您可以将其想象为:

vocab = []              # line0
for item in c.items():  # lines 2, 4 and 5
    x = item[0]         # line3
    count = item[1]     # line3
    if count >= 2:      # line 6-7
        vocab.append(x) # line1
5 年前
回复了 ShadowRanger 创建的主题 » 修改python字典的值

AS Francisco notes A dict 理解将有助于创造新的 双关语 更换钥匙。您也可以修改 双关语 到位,或者使用显式循环:

for k, v in my_dicts.items():
    if v == 'true':
        my_dicts[k] = True

或通过使用 双关语 理解和更新原文 双关语 结果是:

my_dicts.update({k: True if v == 'true' else v for k, v in my_dicts.items()})

第一种方法一般来说可能更快,因为它避免了 双关语 ,并且甚至不会尝试更新密钥,除非该值是 "true" 要替换的字符串。

很明显,这是 正常地 修改不安全 双关语 当你重复它的时候,如果你担心第一个循环的话,你会得到原谅。但在这种情况下,它是好的;突变的危险 双关语 当您迭代时,它来自于添加或删除键,但是由于您设置的每个键都已经存在于 双关语 ,您只更新现有密钥的值(这是安全/合法的),而不更改密钥集本身(这会咬到您)。