标签归档:python-3.x

如何使用open with语句打开文件

问题:如何使用open with语句打开文件

我正在研究如何在Python中进行文件输入和输出。我编写了以下代码,以将文件列表中的名称列表(每行一个)读入另一个文件中,同时对照文件中的名称检查名称并将文本附加到文件中。该代码有效。可以做得更好吗?

我想对with open(...输入文件和输出文件都使用该语句,但是看不到它们如何位于同一块中,这意味着我需要将名称存储在一个临时位置。

def filter(txt, oldfile, newfile):
    '''\
    Read a list of names from a file line by line into an output file.
    If a line begins with a particular name, insert a string of text
    after the name before appending the line to the output file.
    '''

    outfile = open(newfile, 'w')
    with open(oldfile, 'r', encoding='utf-8') as infile:
        for line in infile:
            if line.startswith(txt):
                line = line[0:len(txt)] + ' - Truly a great person!\n'
            outfile.write(line)

    outfile.close()
    return # Do I gain anything by including this?

# input the name you want to check against
text = input('Please enter the name of a great person: ')    
letsgo = filter(text,'Spanish', 'Spanish2')

I’m looking at how to do file input and output in Python. I’ve written the following code to read a list of names (one per line) from a file into another file while checking a name against the names in the file and appending text to the occurrences in the file. The code works. Could it be done better?

I’d wanted to use the with open(... statement for both input and output files but can’t see how they could be in the same block meaning I’d need to store the names in a temporary location.

def filter(txt, oldfile, newfile):
    '''\
    Read a list of names from a file line by line into an output file.
    If a line begins with a particular name, insert a string of text
    after the name before appending the line to the output file.
    '''

    outfile = open(newfile, 'w')
    with open(oldfile, 'r', encoding='utf-8') as infile:
        for line in infile:
            if line.startswith(txt):
                line = line[0:len(txt)] + ' - Truly a great person!\n'
            outfile.write(line)

    outfile.close()
    return # Do I gain anything by including this?

# input the name you want to check against
text = input('Please enter the name of a great person: ')    
letsgo = filter(text,'Spanish', 'Spanish2')

回答 0

Python允许将多个open()语句放在一个语句中with。您用逗号分隔。您的代码将是:

def filter(txt, oldfile, newfile):
    '''\
    Read a list of names from a file line by line into an output file.
    If a line begins with a particular name, insert a string of text
    after the name before appending the line to the output file.
    '''

    with open(newfile, 'w') as outfile, open(oldfile, 'r', encoding='utf-8') as infile:
        for line in infile:
            if line.startswith(txt):
                line = line[0:len(txt)] + ' - Truly a great person!\n'
            outfile.write(line)

# input the name you want to check against
text = input('Please enter the name of a great person: ')    
letsgo = filter(text,'Spanish', 'Spanish2')

不,通过return在函数的末尾放置一个显式字符不会获得任何收益。您可以使用return提前退出,但最后要退出,并且该函数将在没有退出的情况下退出。(当然,对于返回值的函数,您可以使用return来指定要返回的值。)

引入该语句时,Python 2.5 或Python 2.6 不支持open()与一起使用多个项目,但Python 2.7和Python 3.1或更高版本with支持使用多个项目with

http://docs.python.org/reference/compound_stmts.html#the-with-statement http://docs.python.org/release/3.1/reference/compound_stmts.html#the-with-statement

如果要编写必须在Python 2.5、2.6或3.0中运行的代码,则将with语句嵌套为建议的其他答案或使用contextlib.nested

Python allows putting multiple open() statements in a single with. You comma-separate them. Your code would then be:

def filter(txt, oldfile, newfile):
    '''\
    Read a list of names from a file line by line into an output file.
    If a line begins with a particular name, insert a string of text
    after the name before appending the line to the output file.
    '''

    with open(newfile, 'w') as outfile, open(oldfile, 'r', encoding='utf-8') as infile:
        for line in infile:
            if line.startswith(txt):
                line = line[0:len(txt)] + ' - Truly a great person!\n'
            outfile.write(line)

# input the name you want to check against
text = input('Please enter the name of a great person: ')    
letsgo = filter(text,'Spanish', 'Spanish2')

And no, you don’t gain anything by putting an explicit return at the end of your function. You can use return to exit early, but you had it at the end, and the function will exit without it. (Of course with functions that return a value, you use the return to specify the value to return.)

Using multiple open() items with with was not supported in Python 2.5 when the with statement was introduced, or in Python 2.6, but it is supported in Python 2.7 and Python 3.1 or newer.

http://docs.python.org/reference/compound_stmts.html#the-with-statement http://docs.python.org/release/3.1/reference/compound_stmts.html#the-with-statement

If you are writing code that must run in Python 2.5, 2.6 or 3.0, nest the with statements as the other answers suggested or use contextlib.nested.


回答 1

这样使用嵌套块

with open(newfile, 'w') as outfile:
    with open(oldfile, 'r', encoding='utf-8') as infile:
        # your logic goes right here

Use nested blocks like this,

with open(newfile, 'w') as outfile:
    with open(oldfile, 'r', encoding='utf-8') as infile:
        # your logic goes right here

回答 2

您可以将块嵌套。像这样:

with open(newfile, 'w') as outfile:
    with open(oldfile, 'r', encoding='utf-8') as infile:
        for line in infile:
            if line.startswith(txt):
                line = line[0:len(txt)] + ' - Truly a great person!\n'
            outfile.write(line)

这比您的版本更好,因为outfile即使您的代码遇到异常,您也可以保证将其关闭。显然,您可以通过try / finally进行操作,但这with是正确的方法。

或者,正如我刚学到的,您可以在@steveha描述的with语句中包含多个上下文管理器。在我看来,这比嵌套是更好的选择。

对于您的最后一个小问题,退货没有实际目的。我会删除它。

You can nest your with blocks. Like this:

with open(newfile, 'w') as outfile:
    with open(oldfile, 'r', encoding='utf-8') as infile:
        for line in infile:
            if line.startswith(txt):
                line = line[0:len(txt)] + ' - Truly a great person!\n'
            outfile.write(line)

This is better than your version because you guarantee that outfile will be closed even if your code encounters exceptions. Obviously you could do that with try/finally, but with is the right way to do this.

Or, as I have just learnt, you can have multiple context managers in a with statement as described by @steveha. That seems to me to be a better option than nesting.

And for your final minor question, the return serves no real purpose. I would remove it.


回答 3

有时,您可能想打开不同数量的文件,并对待每个文件相同,可以使用 contextlib

from contextlib import ExitStack
filenames = [file1.txt, file2.txt, file3.txt]

with open('outfile.txt', 'a') as outfile:
    with ExitStack() as stack:
        file_pointers = [stack.enter_context(open(file, 'r')) for file in filenames]                
            for fp in file_pointers:
                outfile.write(fp.read())                   

Sometimes, you might want to open a variable amount of files and treat each one the same, you can do this with contextlib

from contextlib import ExitStack
filenames = [file1.txt, file2.txt, file3.txt]

with open('outfile.txt', 'a') as outfile:
    with ExitStack() as stack:
        file_pointers = [stack.enter_context(open(file, 'r')) for file in filenames]                
            for fp in file_pointers:
                outfile.write(fp.read())                   

如何在Python 3.x和Python 2.x上使用pip

问题:如何在Python 3.x和Python 2.x上使用pip

我安装了Python 3.x(在Ubuntu上除了Python 2.x之外),然后慢慢开始配对在Python 2.x中使用的模块。

所以我想知道,应该为Python 2.x和Python 3.x使用pip来简化生活吗?

I installed Python 3.x (besides Python 2.x on Ubuntu) and slowly started to pair modules I use in Python 2.x.

So I wonder, what approach should I take to make my life easy by using pip for both Python 2.x and Python 3.x?


回答 0

您应该采取的方法是安装 pip为Python 3.2。

您可以通过以下方式执行此操作:

$ curl -O https://bootstrap.pypa.io/get-pip.py
$ sudo python3.2 get-pip.py

然后,您可以使用来安装适用于Python 3.2的内容pip-3.2,并使用来安装适用于Python 2-7的内容pip-2.7。该pip命令最终将指向其中之一,但是我不确定是哪一个,因此您必须进行检查。

The approach you should take is to install pip for Python 3.2.

You do this in the following way:

$ curl -O https://bootstrap.pypa.io/get-pip.py
$ sudo python3.2 get-pip.py

Then, you can install things for Python 3.2 with pip-3.2, and install things for Python 2-7 with pip-2.7. The pip command will end up pointing to one of these, but I’m not sure which, so you will have to check.


回答 1

您还可以做的是使用apt-get:

apt-get install python3-pip

以我的经验,这也很流利,而且您会从apt-get中获得所有好处。

What you can also do is to use apt-get:

apt-get install python3-pip

In my experience this works pretty fluent too, plus you get all the benefits from apt-get.


回答 2

首先,使用以下命令安装Python 3 pip:

sudo apt-get install python3-pip

然后,Python 3 pip使用:

pip3 install <module-name>

对于Python 2 pip使用:

pip install <module-name>

First, install Python 3 pip using:

sudo apt-get install python3-pip

Then, to use Python 3 pip use:

pip3 install <module-name>

For Python 2 pip use:

pip install <module-name>

回答 3

如果您不想每次使用pip时都指定版本:

安装点子:

$ curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python3

并导出路径:

$ export PATH=/Library/Frameworks/Python.framework/Versions/<version number>/bin:$PATH

If you don’t want to have to specify the version every time you use pip:

Install pip:

$ curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py | python3

and export the path:

$ export PATH=/Library/Frameworks/Python.framework/Versions/<version number>/bin:$PATH

回答 4

最短的方法:

python3 -m pip install package
python -m pip install package

The shortest way:

python3 -m pip install package
python -m pip install package

回答 5

这在OS X上对我有用:(我之所以这样说,是因为有时Mac对于每个开放源代码工具都拥有“自己的”版本,这是一种痛苦,并且您无法删除它,因为“其改进”使它对于其他苹果产品来说都是独一无二的,如果您将其删除,事情就会开始恶化)

我按照@Lennart Regebro提供的步骤来获取python 3的pip,但是python 2的pip仍然是第一个出现的路径,所以…我要做的是在/ usr / bin内创建指向python 3的符号链接(实际上,我也做了同样的事情来让我的2条python和平运行):

ln -s /Library/Frameworks/Python.framework/Versions/3.4/bin/pip /usr/bin/pip3

请注意,我3在末尾添加了一个,因此基本上您要做的是使用pip3而不是pip

该帖子很旧,但我希望有一天能对某人有所帮助。从理论上讲,这应该适用于任何LINUX系统。

This worked for me on OS X: (I say this because sometimes is a pain that mac has “its own” version of every open source tool, and you cannot remove it because “its improvements” make it unique for other apple stuff to work, and if you remove it things start falling appart)

I followed the steps provided by @Lennart Regebro to get pip for python 3, nevertheless pip for python 2 was still first on the path, so… what I did is to create a symbolic link to python 3 inside /usr/bin (in deed I did the same to have my 2 pythons running in peace):

ln -s /Library/Frameworks/Python.framework/Versions/3.4/bin/pip /usr/bin/pip3

Notice that I added a 3 at the end, so basically what you have to do is to use pip3 instead of just pip.

The post is old but I hope this helps someone someday. this should theoretically work for any LINUX system.


回答 6

在Suse Linux 13.2上,pip调用python3,但是pip2可用于使用旧版本的python。

On Suse Linux 13.2, pip calls python3, but pip2 is available to use the older python version.


回答 7

在Windows中,先安装Python 3.7,然后再安装Python 2.7。然后,使用命令提示符:

pip安装python2-module-name

pip3安装python3-module-name

就这样

In Windows, first installed Python 3.7 and then Python 2.7. Then, use command prompt:

pip install python2-module-name

pip3 install python3-module-name

That’s all


回答 8

请注意,在msys2上,我发现以下命令会有所帮助:

$ pacman -S python3-pip
$ pip3 install --upgrade pip
$ pip3 install --user package_name

Please note that on msys2 I’ve found these commands to be helpful:

$ pacman -S python3-pip
$ pip3 install --upgrade pip
$ pip3 install --user package_name

回答 9

以为这是个老问题,我想我有更好的解决方案

  1. 要将pip用于python 2.x环境,请使用以下命令-

    py -2 -m pip安装-r requirements.txt

  2. 要将pip用于python 3.x环境,请使用以下命令-

    py -3 -m pip install -r requirements.txt

Thought this is old question, I think I have better solution

  1. To use pip for a python 2.x environment, use this command –

    py -2 -m pip install -r requirements.txt

  2. To use pip for python 3.x environment, use this command –

    py -3 -m pip install -r requirements.txt


Python“提高”用法

问题:Python“提高”用法

raiseraise fromPython 和有什么不一样?

try:
    raise ValueError
except Exception as e:
    raise IndexError

产生

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError
IndexError

try:
    raise ValueError
except Exception as e:
    raise IndexError from e

产生

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError from e
IndexError

What’s the difference between raise and raise from in Python?

try:
    raise ValueError
except Exception as e:
    raise IndexError

which yields

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError
IndexError

and

try:
    raise ValueError
except Exception as e:
    raise IndexError from e

which yields

Traceback (most recent call last):
  File "tmp.py", line 2, in <module>
    raise ValueError
ValueError

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "tmp.py", line 4, in <module>
    raise IndexError from e
IndexError

回答 0

区别在于,当您使用时from,会设置__cause__属性,并且消息会指出异常是由引起的。如果您省略,from__cause__设置no ,但是也可以设置该__context__属性,然后回溯显示上下文,就像处理其他事件时一样

__context__如果raise在异常处理程序中使用过,则设置发生的情况;如果您raise在其他任何地方使用过,则也不会__context__设置。

如果__cause__设置了a,那么__suppress_context__ = True还会在异常上设置一个标志;当__suppress_context__设置为时,在打印回溯时True__context__忽略。

从异常处理程序,你养的时候希望显示上下文(不想处理另一个异常发生时,然后用消息)raise ... from None来设置__suppress_context__True

换句话说,Python 在异常上设置了一个上下文,因此您可以自省引发异常的位置,让您查看是否用它替换了另一个异常。您还可以将原因添加到异常中,使回溯明确地显示其他异常(使用不同的措辞),并且上下文将被忽略(但在调试时仍可以自省)。使用raise ... from None使您可以禁止打印上下文。

请参阅raise语句documenation

from子句用于异常链接:如果给定,则第二个表达式必须是另一个异常类或实例,然后将其作为__cause__属性(可写)附加到引发的异常上。如果未处理引发的异常,则将打印两个异常:

>>> try:
...     print(1 / 0)
... except Exception as exc:
...     raise RuntimeError("Something bad happened") from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

如果在异常处理程序或finally子句中引发异常,则类似的机制会隐式工作:然后,将先前的异常附加为新异常的__context__属性:

>>> try:
...     print(1 / 0)
... except:
...     raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

另请参阅内置异常文档,以获取有关上下文的详细信息,并提供附加到异常的原因信息。

The difference is that when you use from, the __cause__ attribute is set and the message states that the exception was directly caused by. If you omit the from then no __cause__ is set, but the __context__ attribute may be set as well, and the traceback then shows the context as during handling something else happened.

Setting the __context__ happens if you used raise in an exception handler; if you used raise anywhere else no __context__ is set either.

If a __cause__ is set, a __suppress_context__ = True flag is also set on the exception; when __suppress_context__ is set to True, the __context__ is ignored when printing a traceback.

When raising from a exception handler where you don’t want to show the context (don’t want a during handling another exception happened message), then use raise ... from None to set __suppress_context__ to True.

In other words, Python sets a context on exceptions so you can introspect where an exception was raised, letting you see if another exception was replaced by it. You can also add a cause to an exception, making the traceback explicit about the other exception (use different wording), and the context is ignored (but can still be introspected when debugging). Using raise ... from None lets you suppress the context being printed.

See the raise statement documenation:

The from clause is used for exception chaining: if given, the second expression must be another exception class or instance, which will then be attached to the raised exception as the __cause__ attribute (which is writable). If the raised exception is not handled, both exceptions will be printed:

>>> try:
...     print(1 / 0)
... except Exception as exc:
...     raise RuntimeError("Something bad happened") from exc
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

A similar mechanism works implicitly if an exception is raised inside an exception handler or a finally clause: the previous exception is then attached as the new exception’s __context__ attribute:

>>> try:
...     print(1 / 0)
... except:
...     raise RuntimeError("Something bad happened")
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ZeroDivisionError: int division or modulo by zero

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
RuntimeError: Something bad happened

Also see the Built-in Exceptions documentation for details on the context and cause information attached to exceptions.


Python 3.3+中的软件包不需要__init__.py吗

问题:Python 3.3+中的软件包不需要__init__.py吗

我正在使用Python 3.5.1。我在这里阅读了文档和包部分:https : //docs.python.org/3/tutorial/modules.html#packages

现在,我具有以下结构:

/home/wujek/Playground/a/b/module.py

module.py

class Foo:
    def __init__(self):
        print('initializing Foo')

现在,在/home/wujek/Playground

~/Playground $ python3
>>> import a.b.module
>>> a.b.module.Foo()
initializing Foo
<a.b.module.Foo object at 0x100a8f0b8>

同样,现在在家里,超级文件夹Playground

~ $ PYTHONPATH=Playground python3
>>> import a.b.module
>>> a.b.module.Foo()
initializing Foo
<a.b.module.Foo object at 0x10a5fee10>

实际上,我可以做各种事情:

~ $ PYTHONPATH=Playground python3
>>> import a
>>> import a.b
>>> import Playground.a.b

为什么这样做?我虽然__init__.py两者都需要文件(空文件可以工作),a并且bmodule.py在Python路径指向Playground文件夹时可导入?

这似乎与Python 2.7有所不同:

~ $ PYTHONPATH=Playground python
>>> import a
ImportError: No module named a
>>> import a.b
ImportError: No module named a.b
>>> import a.b.module
ImportError: No module named a.b.module

随着__init__.py在这两个~/Playground/a~/Playground/a/b它工作正常。

I am using Python 3.5.1. I read the document and the package section here: https://docs.python.org/3/tutorial/modules.html#packages

Now, I have the following structure:

/home/wujek/Playground/a/b/module.py

module.py:

class Foo:
    def __init__(self):
        print('initializing Foo')

Now, while in /home/wujek/Playground:

~/Playground $ python3
>>> import a.b.module
>>> a.b.module.Foo()
initializing Foo
<a.b.module.Foo object at 0x100a8f0b8>

Similarly, now in home, superfolder of Playground:

~ $ PYTHONPATH=Playground python3
>>> import a.b.module
>>> a.b.module.Foo()
initializing Foo
<a.b.module.Foo object at 0x10a5fee10>

Actually, I can do all kinds of stuff:

~ $ PYTHONPATH=Playground python3
>>> import a
>>> import a.b
>>> import Playground.a.b

Why does this work? I though there needed to be __init__.py files (empty ones would work) in both a and b for module.py to be importable when the Python path points to the Playground folder?

This seems to have changed from Python 2.7:

~ $ PYTHONPATH=Playground python
>>> import a
ImportError: No module named a
>>> import a.b
ImportError: No module named a.b
>>> import a.b.module
ImportError: No module named a.b.module

With __init__.py in both ~/Playground/a and ~/Playground/a/b it works fine.


回答 0

Python 3.3+具有隐式命名空间包,允许它创建不带__init__.py文件的包。

允许隐式命名空间包意味着可以完全放弃提供__init__.py文件的要求,并使其受到影响。

__init__.py文件的旧方法仍然可以在Python 2中使用。

Python 3.3+ has Implicit Namespace Packages that allow it to create a packages without an __init__.py file.

Allowing implicit namespace packages means that the requirement to provide an __init__.py file can be dropped completely, and affected … .

The old way with __init__.py files still works as in Python 2.


回答 1

重要

@Mike的回答是正确的,但过于精确。确实,Python 3.3+支持隐式命名空间包,这允许它创建不带__init__.py文件的包。

但是,这仅适用于EMPTY__init__.py文件。因此,EMPTY__init__.py文件不再是必需的,可以省略。如果要在导入软件包或其任何模块或子软件包时运行特定的初始化脚本,则仍然需要__init__.py文件。这是一个很好的Stack Overflow答案,它说明了为什么您想使用__init__.py文件进行一些进一步的初始化,以防您想知道为什么这样做有任何用处。

目录结构示例:

  parent_package/
     __init__.py            <- EMPTY, NOT NECESSARY in Python 3.3+
     child_package/
          __init__.py       <- STILL REQUIRED if you want to run an initialization script
          child1.py
          child2.py
          child3.py

parent_package/child_package/__init__.py

print("from parent")

例子

以下示例演示了如何在执行初始化脚本时执行初始化脚本。 child_package导入或其中一个模块。

范例1

from parent_package import child_package  # prints "from parent"

范例2

from parent_package.child_package import child1  # prints "from parent"

IMPORTANT

@Mike’s answer is correct but too imprecise. It is true that Python 3.3+ supports Implicit Namespace Packages that allows it to create a package without an __init__.py file.

This however, ONLY applies to EMPTY __init__.py files. So EMPTY __init__.py files are no longer necessary and can be omitted. If you want to run a particular initialization script when the package or any of its modules or sub-packages are imported, you still require an __init__.py file. This is a great Stack Overflow answer for why you would want to use an __init__.py file to do some further initialization in case you wondering why this is in any way useful.

Directory Structure Example:

  parent_package/
     __init__.py            <- EMPTY, NOT NECESSARY in Python 3.3+
     child_package/
          __init__.py       <- STILL REQUIRED if you want to run an initialization script
          child1.py
          child2.py
          child3.py

parent_package/child_package/__init__.py:

print("from parent")

EXAMPLES

The below examples demonstrate how the initialization script is executed when the child_package or one of its modules is imported.

Example 1:

from parent_package import child_package  # prints "from parent"

Example 2:

from parent_package.child_package import child1  # prints "from parent"

回答 2

如果你有 setup.py在项目中并在其中使用find_packages(),则必须__init__.py在每个目录中都有一个文件,以便自动找到软件包。

程序包仅在包含__init__.py文件的情况下才被识别

UPD:如果你想使用隐式命名空间包,而__init__.py你只需要使用find_namespace_packages()替代

文件

If you have setup.py in your project and you use find_packages() within it, it is necessary to have an __init__.py file in every directory for packages to be automatically found.

Packages are only recognized if they include an __init__.py file

UPD: If you want to use implicit namespace packages without __init__.py you just have to use find_namespace_packages() instead

Docs


回答 3

我要说的是,__init__.py只有一个人想要拥有隐式命名空间包时,才应该省略它。如果您不知道这意味着什么,则可能不想要它,因此您应该__init__.py在Python 3中继续使用even。

I would say that one should omit the __init__.py only if one wants to have the implicit namespace package. If you don’t know what it means, you probably don’t want it and therefore you should continue to use the __init__.py even in Python 3.


TypeError:并非在字符串格式化python期间转换所有参数

问题:TypeError:并非在字符串格式化python期间转换所有参数

该程序应采用两个名称,如果它们的长度相同,则应检查它们是否相同。如果是相同的单词,则将显示“名称相同”。如果它们的长度相同但字母不同,则会显示“名称不同但长度相同”。我有问题的部分在底部的4行中。

#!/usr/bin/env python
# Enter your code for "What's In (The Length Of) A Name?" here.
name1 = input("Enter name 1: ")
name2 = input("Enter name 2: ")
len(name1)
len(name2)
if len(name1) == len(name2):
    if name1 == name2:
        print ("The names are the same")
    else:
        print ("The names are different, but are the same length")
    if len(name1) > len(name2):
        print ("'{0}' is longer than '{1}'"% name1, name2)
    elif len(name1) < len(name2):
        print ("'{0}'is longer than '{1}'"% name2, name1)

当我运行此代码时,它显示:

Traceback (most recent call last):
  File "program.py", line 13, in <module>
    print ("'{0}' is longer than '{1}'"% name1, name2)
TypeError: not all arguments converted during string formatting

任何建议,高度赞赏。

The program is supposed to take in two names, and if they are the same length it should check if they are the same word. If it’s the same word it will print “The names are the same”. If they are the same length but with different letters it will print “The names are different but the same length”. The part I’m having a problem with is in the bottom 4 lines.

#!/usr/bin/env python
# Enter your code for "What's In (The Length Of) A Name?" here.
name1 = input("Enter name 1: ")
name2 = input("Enter name 2: ")
len(name1)
len(name2)
if len(name1) == len(name2):
    if name1 == name2:
        print ("The names are the same")
    else:
        print ("The names are different, but are the same length")
    if len(name1) > len(name2):
        print ("'{0}' is longer than '{1}'"% name1, name2)
    elif len(name1) < len(name2):
        print ("'{0}'is longer than '{1}'"% name2, name1)

When I run this code it displays:

Traceback (most recent call last):
  File "program.py", line 13, in <module>
    print ("'{0}' is longer than '{1}'"% name1, name2)
TypeError: not all arguments converted during string formatting

Any suggestions are highly appreciated.


回答 0

您正在混合使用不同的格式功能。

旧式%格式化使用%代码进行格式化:

'It will cost $%d dollars.' % 95

新型{}格式使用{}代码和.format方法

'It will cost ${0} dollars.'.format(95)

请注意,使用旧格式时,必须使用元组指定多个参数:

'%d days and %d nights' % (40, 40)

就您而言,由于您使用的是{}格式说明符,请使用.format

"'{0}' is longer than '{1}'".format(name1, name2)

You’re mixing different format functions.

The old-style % formatting uses % codes for formatting:

'It will cost $%d dollars.' % 95

The new-style {} formatting uses {} codes and the .format method

'It will cost ${0} dollars.'.format(95)

Note that with old-style formatting, you have to specify multiple arguments using a tuple:

'%d days and %d nights' % (40, 40)

In your case, since you’re using {} format specifiers, use .format:

"'{0}' is longer than '{1}'".format(name1, name2)

回答 1

错误在于您的字符串格式。

使用’%’运算符使用传统字符串格式的正确方法是使用printf样式的格式字符串(此处的Python文档:http : //docs.python.org/2/library/string.html#format-字符串语法):

"'%s' is longer than '%s'" % (name1, name2)

但是,’%’运算符将来可能会被弃用。新的PEP 3101做事方式是这样的:

"'{0}' is longer than '{1}'".format(name1, name2)

The error is in your string formatting.

The correct way to use traditional string formatting using the ‘%’ operator is to use a printf-style format string (Python documentation for this here: http://docs.python.org/2/library/string.html#format-string-syntax):

"'%s' is longer than '%s'" % (name1, name2)

However, the ‘%’ operator will probably be deprecated in the future. The new PEP 3101 way of doing things is like this:

"'{0}' is longer than '{1}'".format(name1, name2)

回答 2

对我来说,此错误是在我试图将元组传递给字符串格式方法时引起的。

我从这个问题/答案中找到了解决方案

从链接中复制并粘贴正确答案(“不做我的工作”)

>>> thetuple = (1, 2, 3)
>>> print "this is a tuple: %s" % (thetuple,)
this is a tuple: (1, 2, 3)

用感兴趣的元组作为唯一项(即(tutuple)部分)制作单例元组是这里的关键。

For me, This error was caused when I was attempting to pass in a tuple into the string format method.

I found the solution from this question/answer

Copying and pasting the correct answer from the link (NOT MY WORK):

>>> thetuple = (1, 2, 3)
>>> print "this is a tuple: %s" % (thetuple,)
this is a tuple: (1, 2, 3)

Making a singleton tuple with the tuple of interest as the only item, i.e. the (thetuple,) part, is the key bit here.


回答 3

就我而言,这是因为我只需要一个%s,我就缺少值输入。

In my case, it’s because I need only a single %s, i missing values input.


回答 4

除了其他两个答案,我认为缩进在最后两个条件下也是不正确的。条件是一个名字要长于另一个名字,并且它们必须以“ elif”开头且没有缩进。如果将其放在第一个条件下(通过从边缘开始给它四个缩进),则最终会导致矛盾,因为名称的长度不能同时相等且不同。

    else:
        print ("The names are different, but are the same length")
elif len(name1) > len(name2):
    print ("{0} is longer than {1}".format(name1, name2))

In addition to the other two answers, I think the indentations are also incorrect in the last two conditions. The conditions are that one name is longer than the other and they need to start with ‘elif’ and with no indentations. If you put it within the first condition (by giving it four indentations from the margin), it ends up being contradictory because the lengths of the names cannot be equal and different at the same time.

    else:
        print ("The names are different, but are the same length")
elif len(name1) > len(name2):
    print ("{0} is longer than {1}".format(name1, name2))

回答 5

其他一些答案中也指出了很多问题。

  1. 正如nneonneo所指出的,您正在混合使用不同的字符串格式化方法。
  2. 正如GuyP指出的那样,您的缩进也已关闭。

我既提供了.format的示例,也提供了将元组传递给%s的参数说明符的方法。在这两种情况下,缩进均已固定,因此长度匹配时大于/小于检查范围。也将随后的if语句更改为elif,以便仅在先前的同一级别语句为False时才运行。

使用.format进行字符串格式化

name1 = input("Enter name 1: ")
name2 = input("Enter name 2: ")
len(name1)
len(name2)
if len(name1) == len(name2):
    if name1 == name2:
        print ("The names are the same")
    else:
        print ("The names are different, but are the same length")
elif len(name1) > len(name2):
    print ("{0} is longer than {1}".format(name1, name2))
elif len(name1) < len(name2):
    print ("{0} is longer than {1}".format(name2, name1))

使用%s和元组进行字符串格式化

name1 = input("Enter name 1: ")
name2 = input("Enter name 2: ")
len(name1)
len(name2)
if len(name1) == len(name2):
    if name1 == name2:
        print ("The names are the same")
    else:
        print ("The names are different, but are the same length")
elif len(name1) > len(name2):
    print ("%s is longer than %s" % (name1, name2))
elif len(name1) < len(name2):
    print ("%s is longer than %s" % (name2, name1))

There is a combination of issues as pointed out in a few of the other answers.

  1. As pointed out by nneonneo you are mixing different String Formatting methods.
  2. As pointed out by GuyP your indentation is off as well.

I’ve provided both the example of .format as well as passing tuples to the argument specifier of %s. In both cases the indentation has been fixed so greater/less than checks are outside of when length matches. Also changed subsequent if statements to elif’s so they only run if the prior same level statement was False.

String Formatting with .format

name1 = input("Enter name 1: ")
name2 = input("Enter name 2: ")
len(name1)
len(name2)
if len(name1) == len(name2):
    if name1 == name2:
        print ("The names are the same")
    else:
        print ("The names are different, but are the same length")
elif len(name1) > len(name2):
    print ("{0} is longer than {1}".format(name1, name2))
elif len(name1) < len(name2):
    print ("{0} is longer than {1}".format(name2, name1))

String Formatting with %s and a tuple

name1 = input("Enter name 1: ")
name2 = input("Enter name 2: ")
len(name1)
len(name2)
if len(name1) == len(name2):
    if name1 == name2:
        print ("The names are the same")
    else:
        print ("The names are different, but are the same length")
elif len(name1) > len(name2):
    print ("%s is longer than %s" % (name1, name2))
elif len(name1) < len(name2):
    print ("%s is longer than %s" % (name2, name1))

回答 6

在python 3.7及更高版本中,有一种新的简便方法。语法如下:

name = "Eric"
age = 74
f"Hello, {name}. You are {age}."

输出:

Hello, Eric. You are 74.

In python 3.7 and above there is a new and easy way. here is the syntax:

name = "Eric"
age = 74
f"Hello, {name}. You are {age}."

OutPut:

Hello, Eric. You are 74.

回答 7

我也遇到错误

_mysql_exceptions.ProgrammingError: not all arguments converted during string formatting 

但是列表参数工作良好。

我使用mysqlclient python lib。lib似乎不接受元组args。像这样传递列表参数['arg1', 'arg2'] 将起作用。

I encounter the error as well,

_mysql_exceptions.ProgrammingError: not all arguments converted during string formatting 

But list args work well.

I use mysqlclient python lib. The lib looks like not to accept tuple args. To pass list args like ['arg1', 'arg2'] will work.


回答 8

Django原始SQL查询视图

"SELECT * FROM VendorReport_vehicledamage WHERE requestdate BETWEEN '{0}' AND '{1}'".format(date_from, date_to)

models.py

class VehicleDamage(models.Model):
    requestdate = models.DateTimeField("requestdate")
    vendor_name = models.CharField("vendor_name", max_length=50)
    class Meta:
        managed=False

views.py

def location_damageReports(request):
    #static date for testing
    date_from = '2019-11-01'
    date_to = '2019-21-01'
    vehicle_damage_reports = VehicleDamage.objects.raw("SELECT * FROM VendorReport_vehicledamage WHERE requestdate BETWEEN '{0}' AND '{1}'".format(date_from, date_to))
    damage_report = DashboardDamageReportSerializer(vehicle_damage_reports, many=True)
    data={"data": damage_report.data}
    return HttpResponse(json.dumps(data), content_type="application/json")

注意:使用python 3.5和Django 1.11

django raw sql query in view

"SELECT * FROM VendorReport_vehicledamage WHERE requestdate BETWEEN '{0}' AND '{1}'".format(date_from, date_to)

models.py

class VehicleDamage(models.Model):
    requestdate = models.DateTimeField("requestdate")
    vendor_name = models.CharField("vendor_name", max_length=50)
    class Meta:
        managed=False

views.py

def location_damageReports(request):
    #static date for testing
    date_from = '2019-11-01'
    date_to = '2019-21-01'
    vehicle_damage_reports = VehicleDamage.objects.raw("SELECT * FROM VendorReport_vehicledamage WHERE requestdate BETWEEN '{0}' AND '{1}'".format(date_from, date_to))
    damage_report = DashboardDamageReportSerializer(vehicle_damage_reports, many=True)
    data={"data": damage_report.data}
    return HttpResponse(json.dumps(data), content_type="application/json")

Note: using python 3.5 and django 1.11


回答 9

对我来说,因为我在一个打印调用中存储了许多值,所以解决方案是创建一个单独的变量以将数据存储为元组,然后调用打印函数。

x = (f"{id}", f"{name}", f"{age}")
print(x) 

For me, as I was storing many values within a single print call, the solution was to create a separate variable to store the data as a tuple and then call the print function.

x = (f"{id}", f"{name}", f"{age}")
print(x) 

如何使用类型提示指定“可空”返回类型

问题:如何使用类型提示指定“可空”返回类型

假设我有一个函数:

def get_some_date(some_argument: int=None) -> %datetime_or_None%:
    if some_argument is not None and some_argument == 1:
        return datetime.utcnow()
    else:
        return None

如何为可以指定的东西指定返回类型None

Suppose I have a function:

def get_some_date(some_argument: int=None) -> %datetime_or_None%:
    if some_argument is not None and some_argument == 1:
        return datetime.utcnow()
    else:
        return None

How do I specify the return type for something that can be None?


回答 0

您正在寻找Optional

由于您的返回类型可以是datetime(从返回datetime.utcnow()),None也应该使用Optional[datetime]

from typing import Optional

def get_some_date(some_argument: int=None) -> Optional[datetime]:
    # as defined

在有关打字的文档中,Optional是以下内容的简写:

Optional[X]等同于Union[X, None]

其中Union[X, Y]表示类型X或的值Y


如果由于担心其他人可能偶然发现Optional而没有意识到它的含义而希望变得明确,则可以始终使用Union

from typing import Union

def get_some_date(some_argument: int=None) -> Union[datetime, None]:

但是我怀疑这是一个好主意,Optional是一个指示性名称,并且确实节省了几次击键。

正如@ Michael0x2a的注释中指出的那样,Union[T, None]已转换为,Union[T, type(None)]因此无需在type此处使用。

在视觉上,这两种方法可能有所不同,但在编程上,结果是完全相同的Union[datetime.datetime, NoneType]将是存储在get_some_date.__annotations__*中的类型:

>>> from typing import get_type_hints
>>> print(get_type_hints(get_some_date))
{'return': typing.Union[datetime.datetime, NoneType],
 'some_argument': typing.Union[int, NoneType]}

*typing.get_type_hints抢的对象的__annotations__直接访问它的属性来代替。

You’re looking for Optional.

Since your return type can either be datetime (as returned from datetime.utcnow()) or None you should use Optional[datetime]:

from typing import Optional

def get_some_date(some_argument: int=None) -> Optional[datetime]:
    # as defined

From the documentation on typing, Optional is shorthand for:

Optional[X] is equivalent to Union[X, None].

where Union[X, Y] means a value of type X or Y.


If you want to be explicit due to concerns that others might stumble on Optional and not realize it’s meaning, you could always use Union:

from typing import Union

def get_some_date(some_argument: int=None) -> Union[datetime, None]:

But I doubt this is a good idea, Optional is an indicative name and it does save a couple of keystrokes.

As pointed out in the comments by @Michael0x2a Union[T, None] is tranformed to Union[T, type(None)] so no need to use type here.

Visually these might differ but programatically, in both cases, the result is exactly the same; Union[datetime.datetime, NoneType] will be the type stored in get_some_date.__annotations__*:

>>> from typing import get_type_hints
>>> print(get_type_hints(get_some_date))
{'return': typing.Union[datetime.datetime, NoneType],
 'some_argument': typing.Union[int, NoneType]}

*Use typing.get_type_hints to grab the objects’ __annotations__ attribute instead of accessing it directly.


什么是“ 1 ..__ truediv__”?Python是否具有..(“点点”)表示法语法?

问题:什么是“ 1 ..__ truediv__”?Python是否具有..(“点点”)表示法语法?

最近,我遇到了一种语法,这种语法在我学习python时从未见过,在大多数教程中,这种..表示法看起来像这样:

f = 1..__truediv__ # or 1..__div__ for python 2

print(f(8)) # prints 0.125 

我发现它和(当然,它更长)完全一样:

f = lambda x: (1).__truediv__(x)
print(f(8)) # prints 0.125 or 1//8

但是我的问题是:

  • 它怎么做呢?
  • 这两个点实际上意味着什么?
  • 如何在更复杂的语句中使用它(如果可能)?

将来可能会为我节省很多代码行… :)

I recently came across a syntax I never seen before when I learned python nor in most tutorials, the .. notation, it looks something like this:

f = 1..__truediv__ # or 1..__div__ for python 2

print(f(8)) # prints 0.125 

I figured it was exactly the same as (except it’s longer, of course):

f = lambda x: (1).__truediv__(x)
print(f(8)) # prints 0.125 or 1//8

But my questions are:

  • How can it do that?
  • What does it actually mean with the two dots?
  • How can you use it in a more complex statement (if possible)?

This will probably save me many lines of code in the future…:)


回答 0

您所拥有的是一个float不带尾随零的文字,然后您可以访问的__truediv__方法。它本身不是运算符;第一个点是float值的一部分,第二个点是用于访问对象属性和方法的点运算符。

您可以通过执行以下操作达到相同的目的。

>>> f = 1.
>>> f
1.0
>>> f.__floordiv__
<method-wrapper '__floordiv__' of float object at 0x7f9fb4dc1a20>

另一个例子

>>> 1..__add__(2.)
3.0

在这里,我们将1.0加到2.0,显然得出3.0。

What you have is a float literal without the trailing zero, which you then access the __truediv__ method of. It’s not an operator in itself; the first dot is part of the float value, and the second is the dot operator to access the objects properties and methods.

You can reach the same point by doing the following.

>>> f = 1.
>>> f
1.0
>>> f.__floordiv__
<method-wrapper '__floordiv__' of float object at 0x7f9fb4dc1a20>

Another example

>>> 1..__add__(2.)
3.0

Here we add 1.0 to 2.0, which obviously yields 3.0.


回答 1

该问题已经得到足够的答案(即@Paul Rooney的答案),但也可以验证这些答案的正确性。

让我回顾一下现有的答案:这..不是一个语法元素!

您可以检查源代码如何“标记化”。这些标记表示代码的解释方式:

>>> from tokenize import tokenize
>>> from io import BytesIO

>>> s = "1..__truediv__"
>>> list(tokenize(BytesIO(s.encode('utf-8')).readline))
[...
 TokenInfo(type=2 (NUMBER), string='1.', start=(1, 0), end=(1, 2), line='1..__truediv__'),
 TokenInfo(type=53 (OP), string='.', start=(1, 2), end=(1, 3), line='1..__truediv__'),
 TokenInfo(type=1 (NAME), string='__truediv__', start=(1, 3), end=(1, 14), line='1..__truediv__'),
 ...]

因此,字符串1.被解释为数字,第二个.是OP(运算符,在这种情况下为“ get attribute”运算符),而则__truediv__是方法名称。因此,这只是访问__truediv__float 的方法1.0

查看生成的字节码的另一种方法是对其进行汇编。这实际上显示了执行某些代码时执行的指令: dis

>>> import dis

>>> def f():
...     return 1..__truediv__

>>> dis.dis(f)
  4           0 LOAD_CONST               1 (1.0)
              3 LOAD_ATTR                0 (__truediv__)
              6 RETURN_VALUE

基本上说的一样。它加载__truediv__常量的属性1.0


关于你的问题

以及如何在更复杂的语句中使用它(如果可能)?

即使您可能永远也不要这样写代码,只是因为不清楚代码在做什么。因此,请不要在更复杂的语句中使用它。我什至会走得更远,以至于您不应该在如此“简单”的语句中使用它,至少您应该使用括号将指令分开:

f = (1.).__truediv__

这肯定会更具可读性-但类似于:

from functools import partial
from operator import truediv
f = partial(truediv, 1.0)

会更好!

使用的方法partial还保留了python的数据模型(该1..__truediv__方法没有!),可以通过以下小片段进行演示:

>>> f1 = 1..__truediv__
>>> f2 = partial(truediv, 1.)

>>> f2(1+2j)  # reciprocal of complex number - works
(0.2-0.4j)
>>> f2('a')   # reciprocal of string should raise an exception
TypeError: unsupported operand type(s) for /: 'float' and 'str'

>>> f1(1+2j)  # reciprocal of complex number - works but gives an unexpected result
NotImplemented
>>> f1('a')   # reciprocal of string should raise an exception but it doesn't
NotImplemented

这是因为1. / (1+2j)不是由- float.__truediv__而是通过complex.__rtruediv__operator.truediv进行评估的,请确保在正常操作返回时调用了反向操作,NotImplemented__truediv__直接操作时没有这些后备。这种“预期行为”的丧失是您(通常)不应直接使用魔术方法的主要原因。

The question is already sufficiently answered (i.e. @Paul Rooneys answer) but it’s also possible to verify the correctness of these answers.

Let me recap the existing answers: The .. is not a single syntax element!

You can check how the source code is “tokenized”. These tokens represent how the code is interpreted:

>>> from tokenize import tokenize
>>> from io import BytesIO

>>> s = "1..__truediv__"
>>> list(tokenize(BytesIO(s.encode('utf-8')).readline))
[...
 TokenInfo(type=2 (NUMBER), string='1.', start=(1, 0), end=(1, 2), line='1..__truediv__'),
 TokenInfo(type=53 (OP), string='.', start=(1, 2), end=(1, 3), line='1..__truediv__'),
 TokenInfo(type=1 (NAME), string='__truediv__', start=(1, 3), end=(1, 14), line='1..__truediv__'),
 ...]

So the string 1. is interpreted as number, the second . is an OP (an operator, in this case the “get attribute” operator) and the __truediv__ is the method name. So this is just accessing the __truediv__ method of the float 1.0.

Another way of viewing the generated bytecode is to disassemble it. This actually shows the instructions that are performed when some code is executed:

>>> import dis

>>> def f():
...     return 1..__truediv__

>>> dis.dis(f)
  4           0 LOAD_CONST               1 (1.0)
              3 LOAD_ATTR                0 (__truediv__)
              6 RETURN_VALUE

Which basically says the same. It loads the attribute __truediv__ of the constant 1.0.


Regarding your question

And how can you use it in a more complex statement (if possible)?

Even though it’s possible you should never write code like that, simply because it’s unclear what the code is doing. So please don’t use it in more complex statements. I would even go so far that you shouldn’t use it in so “simple” statements, at least you should use parenthesis to separate the instructions:

f = (1.).__truediv__

this would be definetly more readable – but something along the lines of:

from functools import partial
from operator import truediv
f = partial(truediv, 1.0)

would be even better!

The approach using partial also preserves python’s data model (the 1..__truediv__ approach does not!) which can be demonstrated by this little snippet:

>>> f1 = 1..__truediv__
>>> f2 = partial(truediv, 1.)

>>> f2(1+2j)  # reciprocal of complex number - works
(0.2-0.4j)
>>> f2('a')   # reciprocal of string should raise an exception
TypeError: unsupported operand type(s) for /: 'float' and 'str'

>>> f1(1+2j)  # reciprocal of complex number - works but gives an unexpected result
NotImplemented
>>> f1('a')   # reciprocal of string should raise an exception but it doesn't
NotImplemented

This is because 1. / (1+2j) is not evaluated by float.__truediv__ but with complex.__rtruediv__operator.truediv makes sure the reverse operation is called when the normal operation returns NotImplemented but you don’t have these fallbacks when you operate on __truediv__ directly. This loss of “expected behaviour” is the main reason why you (normally) shouldn’t use magic methods directly.


回答 2

首先,两个点可能有点尴尬:

f = 1..__truediv__ # or 1..__div__ for python 2

但这与写作相同:

f = 1.0.__truediv__ # or 1.0.__div__ for python 2

因为float文字可以用三种形式编写:

normal_float = 1.0
short_float = 1.  # == 1.0
prefixed_float = .1  # == 0.1

Two dots together may be a little awkward at first:

f = 1..__truediv__ # or 1..__div__ for python 2

But it is the same as writing:

f = 1.0.__truediv__ # or 1.0.__div__ for python 2

Because float literals can be written in three forms:

normal_float = 1.0
short_float = 1.  # == 1.0
prefixed_float = .1  # == 0.1

回答 3

什么f = 1..__truediv__

f是在值为1的float上绑定的特殊方法。特别,

1.0 / x

在Python 3中,调用:

(1.0).__truediv__(x)

证据:

class Float(float):
    def __truediv__(self, other):
        print('__truediv__ called')
        return super(Float, self).__truediv__(other)

和:

>>> one = Float(1)
>>> one/2
__truediv__ called
0.5

如果这样做:

f = one.__truediv__

我们保留绑定到该绑定方法的名称

>>> f(2)
__truediv__ called
0.5
>>> f(3)
__truediv__ called
0.3333333333333333

如果我们在一个紧密的循环中执行该点分查找,则可以节省一些时间。

解析抽象语法树(AST)

我们可以看到,解析表达式的AST可以告诉我们,我们__truediv__在浮点数上获取属性1.0

>>> import ast
>>> ast.dump(ast.parse('1..__truediv__').body[0])
"Expr(value=Attribute(value=Num(n=1.0), attr='__truediv__', ctx=Load()))"

您可以从以下获得相同的结果函数:

f = float(1).__truediv__

要么

f = (1.0).__truediv__

扣除

我们也可以通过扣除到达那里。

让我们建立它。

1本身是一个int

>>> 1
1
>>> type(1)
<type 'int'>

1,之后是句点:

>>> 1.
1.0
>>> type(1.)
<type 'float'>

下一个点本身就是SyntaxError,但它会在float实例上开始点分查找:

>>> 1..__truediv__
<method-wrapper '__truediv__' of float object at 0x0D1C7BF0>

没有人提到这一点 -这现在是浮动的“绑定方法”1.0

>>> f = 1..__truediv__
>>> f
<method-wrapper '__truediv__' of float object at 0x127F3CD8>
>>> f(2)
0.5
>>> f(3)
0.33333333333333331

我们可以更容易地完成相同的功能:

>>> def divide_one_by(x):
...     return 1.0/x
...     
>>> divide_one_by(2)
0.5
>>> divide_one_by(3)
0.33333333333333331

性能

divide_one_by函数的缺点是它需要另一个Python堆栈框架,这使其比绑定方法要慢一些:

>>> def f_1():
...     for x in range(1, 11):
...         f(x)
...         
>>> def f_2():
...     for x in range(1, 11):
...         divide_one_by(x)
...         
>>> timeit.repeat(f_1)
[2.5495760687176485, 2.5585621018805469, 2.5411816588331888]
>>> timeit.repeat(f_2)
[3.479687248616699, 3.46196088706062, 3.473726342237768]

当然,如果您仅可以使用普通文字,那就更快了:

>>> def f_3():
...     for x in range(1, 11):
...         1.0/x
...         
>>> timeit.repeat(f_3)
[2.1224895628296281, 2.1219930218637728, 2.1280188256941983]

What is f = 1..__truediv__?

f is a bound special method on a float with a value of one. Specifically,

1.0 / x

in Python 3, invokes:

(1.0).__truediv__(x)

Evidence:

class Float(float):
    def __truediv__(self, other):
        print('__truediv__ called')
        return super(Float, self).__truediv__(other)

and:

>>> one = Float(1)
>>> one/2
__truediv__ called
0.5

If we do:

f = one.__truediv__

We retain a name bound to that bound method

>>> f(2)
__truediv__ called
0.5
>>> f(3)
__truediv__ called
0.3333333333333333

If we were doing that dotted lookup in a tight loop, this could save a little time.

Parsing the Abstract Syntax Tree (AST)

We can see that parsing the AST for the expression tells us that we are getting the __truediv__ attribute on the floating point number, 1.0:

>>> import ast
>>> ast.dump(ast.parse('1..__truediv__').body[0])
"Expr(value=Attribute(value=Num(n=1.0), attr='__truediv__', ctx=Load()))"

You could get the same resulting function from:

f = float(1).__truediv__

Or

f = (1.0).__truediv__

Deduction

We can also get there by deduction.

Let’s build it up.

1 by itself is an int:

>>> 1
1
>>> type(1)
<type 'int'>

1 with a period after it is a float:

>>> 1.
1.0
>>> type(1.)
<type 'float'>

The next dot by itself would be a SyntaxError, but it begins a dotted lookup on the instance of the float:

>>> 1..__truediv__
<method-wrapper '__truediv__' of float object at 0x0D1C7BF0>

No one else has mentioned this – This is now a “bound method” on the float, 1.0:

>>> f = 1..__truediv__
>>> f
<method-wrapper '__truediv__' of float object at 0x127F3CD8>
>>> f(2)
0.5
>>> f(3)
0.33333333333333331

We could accomplish the same function much more readably:

>>> def divide_one_by(x):
...     return 1.0/x
...     
>>> divide_one_by(2)
0.5
>>> divide_one_by(3)
0.33333333333333331

Performance

The downside of the divide_one_by function is that it requires another Python stack frame, making it somewhat slower than the bound method:

>>> def f_1():
...     for x in range(1, 11):
...         f(x)
...         
>>> def f_2():
...     for x in range(1, 11):
...         divide_one_by(x)
...         
>>> timeit.repeat(f_1)
[2.5495760687176485, 2.5585621018805469, 2.5411816588331888]
>>> timeit.repeat(f_2)
[3.479687248616699, 3.46196088706062, 3.473726342237768]

Of course, if you can just use plain literals, that’s even faster:

>>> def f_3():
...     for x in range(1, 11):
...         1.0/x
...         
>>> timeit.repeat(f_3)
[2.1224895628296281, 2.1219930218637728, 2.1280188256941983]

“ pip install –user…”的目的是什么?

问题:“ pip install –user…”的目的是什么?

来自pip install --help

 --user      Install to the Python user install directory for your platform. Typically ~/.local/, or %APPDATA%\Python on
             Windows. (See the Python documentation for site.USER_BASE for full details.)

site.USER_BASE的文档是一个令人困扰的有趣的* NIX主题,我不明白。

--user普通英语的目的是什么?为什么安装软件包很~/.local/重要?为什么不将可执行文件放在$ PATH中?

From pip install --help:

 --user      Install to the Python user install directory for your platform. Typically ~/.local/, or %APPDATA%\Python on
             Windows. (See the Python documentation for site.USER_BASE for full details.)

The documentation for site.USER_BASE is a terrifying wormhole of interesting *NIX subject matter that I don’t understand.

What is the purpose of --user in plain english? Why would intalling the package to ~/.local/ matter? Why not just put an executable somewhere in my $PATH?


回答 0

pip默认将Python软件包安装到系统目录(例如/usr/local/lib/python3.4)。这需要root访问。

--user 而是在您的主目录中进行pip安装软件包,该软件包不需要任何特殊特权。

pip defaults to installing Python packages to a system directory (such as /usr/local/lib/python3.4). This requires root access.

--user makes pip install packages in your home directory instead, which doesn’t require any special privileges.


回答 1

--user安装在中site.USER_SITE

就我而言,是/Users/.../Library/Python/2.7/bin。因此,我已将其添加到我的PATH(~/.bash_profile文件中):

export PATH=$PATH:/Users/.../Library/Python/2.7/bin

--user installs in site.USER_SITE.

For my case, it was /Users/.../Library/Python/2.7/bin. So I have added that to my PATH (in ~/.bash_profile file):

export PATH=$PATH:/Users/.../Library/Python/2.7/bin

回答 2

其他答案提到site.USER_SITE放置Python包的位置。如果您要查找二进制文件,请输入{site.USER_BASE}/bin

如果要将此目录添加到外壳程序的搜索路径中,请使用:

export PATH="${PATH}:$(python3 -c 'import site; print(site.USER_BASE)')/bin"

Other answers mention site.USER_SITE as where Python packages get placed. If you’re looking for binaries, these go in {site.USER_BASE}/bin.

If you want to add this directory to your shell’s search path, use:

export PATH="${PATH}:$(python3 -c 'import site; print(site.USER_BASE)')/bin"

回答 3

只是警告:

根据此问题--user当前在虚拟环境中无效pip,因为用户位置对于虚拟环境而言实际上没有任何意义。

因此,请勿pip install --user some_pkg 在虚拟环境内使用,否则,虚拟环境pip会被混淆。有关更多详细信息,请参见此答案

Just a warning:

According to this issue, --user is currently not valid inside a virtual env’s pip, since a user location doesn’t really make sense for a virtual environment.

So do not use pip install --user some_pkg inside a virtual environment, otherwise, virtual environment’s pip will be confused. See this answer for more details.


回答 4

最好的方法是安装virtualenv,不需要--user混乱。您将获得更大的灵活性,而不必担心每次pip安装软件包时都会破坏不同的python版本和项目。

https://virtualenv.pypa.io/en/stable/

Best way to is install virtualenv and not require the --user confusion. You will get more flexibility and not worry about clobbering the different python versions and projects everytime you pip install a package.

https://virtualenv.pypa.io/en/stable/


回答 5

在macOS上,使用该--user标志的原因是确保我们不会破坏OS依赖的库。一个保守许多的MacOS用户的做法是为了避免在安装或使用,需要一个命令更新点子sudo。因此,这包括安装到/usr/local/bin

参考:为Neovim安装python(https://github.com/zchee/deoplete-jedi/wiki/Setting-up-Python-for-Neovim

我不是所有清楚为什么安装到/usr/local/bin是鉴于该系统只依赖于Python的二进制文件在Mac上的危险/Library/Frameworks//usr/bin。我怀疑是因为如上所述,安装到/usr/local/binrequire sudo可以打开导致系统库代价高昂的错误的大门。因此,安装到内部~/.local/bin是避免这种风险的可靠方法。

参考:在Mac上使用python(https://docs.python.org/2/using/mac.html

最后,程度有安装软件包到的好处/usr/local/bin,我不知道它是有道理的,从改变目录的所有者rootuser这样可以避免必须使用,sudo同时仍可以防止进行依赖于系统的更改。*这是否是安全性默认设置,它是过去更频繁地使用Unix系统(作为服务器)的结果吗?或者至少,对于不托管服务器的Mac用户,这是一个好方法吗?

*注意:Mac的系统完整性保护(SIP)功能似乎还可以保护用户免于更改依赖于系统的库。

-E

On macOS, the reason for using the --user flag is to make sure we don’t corrupt the libraries the OS relies on. A conservative approach for many macOS users is to avoid installing or updating pip with a command that requires sudo. Thus, this includes installing to /usr/local/bin

Ref: Installing python for Neovim (https://github.com/zchee/deoplete-jedi/wiki/Setting-up-Python-for-Neovim)

I’m not all clear why installing into /usr/local/bin is a risk on a Mac given the fact that the system only relies on python binaries in /Library/Frameworks/ and /usr/bin. I suspect it’s because as noted above, installing into /usr/local/bin requires sudo which opens the door to making a costly mistake with the system libraries. Thus, installing into ~/.local/bin is a sure fire way to avoid this risk.

Ref: Using python on a Mac (https://docs.python.org/2/using/mac.html)

Finally, to the degree there is a benefit of installing packages into the /usr/local/bin, I wonder if it makes sense to change the owner of the directory from root to user? This would avoid having to use sudo while still protecting against making system-dependent changes.* Is this a security default a relic of how Unix systems were more often used in the past (as servers)? Or at minimum, just a good way to go for Mac users not hosting a server?

*Note: Mac’s System Integrity Protection (SIP) feature also seems to protect the user from changing the system-dependent libraries.

– E


回答 6

没有虚拟环境

pip <command> --user 更改当前pip命令的范围,使其在当前用户帐户的本地python软件包安装位置(而不是系统范围的软件包安装位置)上起作用,这是默认设置。

  • 请参阅《 PIP用户指南》中的“ 用户安装 ”。

这仅在多用户计算机上才真正重要。安装到系统位置的所有内容都将对所有用户可见,因此,安装到该用户位置将使该软件包安装与其他用户分开(他们不会看到该软件包,因此必须自己单独安装才能使用)。因为可能存在版本冲突,所以安装具有其他软件包所需要的依赖关系的软件包可能会引起问题,因此最好不要将给定用户使用的所有软件包推送到系统安装位置。

  • 如果是单用户计算机,则安装到该--user位置几乎没有差异。它将被安装到一个不同的文件夹中,该文件夹可能或不需要添加到路径中,具体取决于软件包及其使用方式(许多软件包都安装了命令行工具,这些工具必须位于要从外壳程序运行的路径上) 。
  • 如果它是多用户计算机,--user则首选使用root / sudo或要求管理员安装并影响每个用户的Python环境,除非管理员希望默认情况下使所有用户都能使用常规软件包。
    • 注意: 根据评论,在大多数Unix / Linux安装中,已经指出系统安装应使用常规的软件包管理器,例如apt,而不是pip

使用虚拟环境

--user在活动的venv / virtualenv环境中,该选项将安装到本地用户python位置(与没有虚拟环境时相同)。

默认情况下,软件包已安装到虚拟环境,但是如果使用--user它,将强制将其安装在虚拟环境之外的用户python脚本目录中(在Windows中,当前c:\users\<username>\appdata\roaming\python\python37\scripts适用于Python 3.7)。

但是,您将无法从虚拟环境中访问系统或用户安装(即使您--user在虚拟环境中使用过)。

如果使用--system-site-packages参数安装虚拟环境,则可以访问python的系统脚本文件夹。我相信这也包括用户python脚本文件夹,但是我不确定。但是,这样做可能会产生意想不到的后果,并且它不是使用虚拟环境的预期方式。


Python系统和本地用户安装文件夹的位置

您可以使用找到python用户安装文件夹的位置python -m site --user-base。我在“问题与解答”,文档中发现有冲突的信息,并且实际上在PC上使用此命令来确定默认值,但它们位于用户主目录(~* nix中的快捷方式,c:\users\<username>通常用于Windows)下。


其他详情

--user选项并非对每个命令都有效。例如,pip uninstall将在安装了软件包的位置(用户文件夹,虚拟环境文件夹等)中查找和卸载软件包,并且该--user选项无效。

随同pip install --user安装的东西将安装在本地位置,该位置只能由当前用户帐户查看,并且不需要root用户访问权限(在* nix上)或管理员访问权限(在Windows上)。

--user选项会修改所有 pip接受该命令的命令,以便在用户安装文件夹中查看/操作,因此,如果使用pip list --user该选项,则只会显示您使用进行安装的软件包pip install --user

Without Virtual Environments

pip <command> --user changes the scope of the current pip command to work on the current user account’s local python package install location, rather than the system-wide package install location, which is the default.

This only really matters on a multi-user machine. Anything installed to the system location will be visible to all users, so installing to the user location will keep that package installation separate from other users (they will not see it, and would have to install it themselves separately to use it). Because there can be version conflicts, installing a package with dependencies needed by other packages can cause problems, so it’s best not to push all packages a given user uses to the system install location.

  • If it is a single-user machine, there is little or no difference to installing to the --user location. It will be installed to a different folder, that may or may not need to be added to the path, depending on the package and how it’s used (many packages install command-line tools that must be on the path to run from a shell).
  • If it is a multi-user machine, --user is preferred to using root/sudo or requiring administrator installation and affecting the Python environment of every user, except in cases of general packages that the administrator wants to make available to all users by default.
    • Note: Per comments, on most Unix/Linux installs it has been pointed out that system installs should use the general package manager, such as apt, rather than pip.

With Virtual Environments

The --user option in an active venv/virtualenv environment will install to the local user python location (same as without a virtual environment).

Packages are installed to the virtual environment by default, but if you use --user it will force it to install outside the virtual environments, in the users python script directory (in Windows, this currently is c:\users\<username>\appdata\roaming\python\python37\scripts for me with Python 3.7).

However, you won’t be able to access a system or user install from within virtual environment (even if you used --user while in a virtual environment).

If you install a virtual environment with the --system-site-packages argument, you will have access to the system script folder for python. I believe this included the user python script folder as well, but I’m unsure. However, there may be unintended consequences for this and it is not the intended way to use virtual environments.


Location of the Python System and Local User Install Folders

You can find the location of the user install folder for python with python -m site --user-base. I’m finding conflicting information in Q&A’s, the documentation and actually using this command on my PC as to what the defaults are, but they are underneath the user home directory (~ shortcut in *nix, and c:\users\<username> typically for Windows).


Other Details

The --user option is not a valid for every command. For example pip uninstall will find and uninstall packages wherever they were installed (in the user folder, virtual environment folder, etc.) and the --user option is not valid.

Things installed with pip install --user will be installed in a local location that will only be seen by the current user account, and will not require root access (on *nix) or administrator access (on Windows).

The --user option modifies all pip commands that accept it to see/operate on the user install folder, so if you use pip list --user it will only show you packages installed with pip install --user.


回答 7

为什么不将可执行文件放在$ PATH中

~/.local/bin directory从理论上讲应该在您的计算机中$PATH

根据这些人 说法,$PATH在使用时不添加它是一个错误systemd

该答案对其进行了更广泛的解释。

但是,即使你的发行版包括~/.local/bin目录$PATH,它可能是以下形式(内~/.profile):

if [ -d "$HOME/.local/bin" ] ; then
    PATH="$HOME/.local/bin:$PATH"
fi

如果该目录以前不在目录中,则需要注销并再次登录

Why not just put an executable somewhere in my $PATH

~/.local/bin directory is theoretically expected to be in your $PATH.

According to these people it’s a bug not adding it in the $PATH when using systemd.

This answer explains it more extensively.

But even if your distro includes the ~/.local/bin directory to the $PATH, it might be in the following form (inside ~/.profile):

if [ -d "$HOME/.local/bin" ] ; then
    PATH="$HOME/.local/bin:$PATH"
fi

which would require you to logout and login again, if the directory was not there before.


‘str’对象没有属性’decode’。Python 3错误?

问题:’str’对象没有属性’decode’。Python 3错误?

这是我的代码:

import imaplib
from email.parser import HeaderParser

conn = imaplib.IMAP4_SSL('imap.gmail.com')
conn.login('example@gmail.com', 'password')
conn.select()
conn.search(None, 'ALL')
data = conn.fetch('1', '(BODY[HEADER])')
header_data = data[1][0][1].decode('utf-8')

此时,我收到错误消息

AttributeError: 'str' object has no attribute 'decode'

Python 3不再具有解码了,对吗?我怎样才能解决这个问题?

另外,在:

data = conn.fetch('1', '(BODY[HEADER])')

我只选择第一封电子邮件。如何全部选择?

Here is my code:

import imaplib
from email.parser import HeaderParser

conn = imaplib.IMAP4_SSL('imap.gmail.com')
conn.login('example@gmail.com', 'password')
conn.select()
conn.search(None, 'ALL')
data = conn.fetch('1', '(BODY[HEADER])')
header_data = data[1][0][1].decode('utf-8')

at this point I get the error message

AttributeError: 'str' object has no attribute 'decode'

Python 3 doesn’t have decode anymore, am I right? how can I fix this?

Also, in:

data = conn.fetch('1', '(BODY[HEADER])')

I am selecting only the 1st email. How do I select all?


回答 0

您正在尝试解码已解码的对象。您有一个str,不再需要从UTF-8解码。

只需删除.decode('utf-8')部分:

header_data = data[1][0][1]

至于您的fetch()通话,您明确要求的只是第一条消息。如果要检索更多消息,请使用范围。请参阅文档

以下命令的message_set选项是一个字符串,用于指定要执行的一条或多条消息。它可以是简单的消息号('1'),消息号的范围('2:4')或由逗号分隔的一组非连续范围('1:3,6:9')。一个范围可以包含一个星号,以指示一个无限的上限('3:*')。

You are trying to decode an object that is already decoded. You have a str, there is no need to decode from UTF-8 anymore.

Simply drop the .decode('utf-8') part:

header_data = data[1][0][1]

As for your fetch() call, you are explicitly asking for just the first message. Use a range if you want to retrieve more messages. See the documentation:

The message_set options to commands below is a string specifying one or more messages to be acted upon. It may be a simple message number ('1'), a range of message numbers ('2:4'), or a group of non-contiguous ranges separated by commas ('1:3,6:9'). A range can contain an asterisk to indicate an infinite upper bound ('3:*').


回答 1

从Python 3开始,所有字符串都是unicode对象。

  a = 'Happy New Year' # Python 3
  b = unicode('Happy New Year') # Python 2

之前的代码是相同的。因此,我认为您应该删除.decode('utf-8')。因为您已经获得了unicode对象。

Begin with Python 3, all string is unicode object.

  a = 'Happy New Year' # Python 3
  b = unicode('Happy New Year') # Python 2

the code before are same. So I think you should remove the .decode('utf-8'). Because you have already get the unicode object.


回答 2

通过以下方法使用它:

str.encode().decode()

Use it by this Method:

str.encode().decode()

回答 3

对于Python3

html = """\\u003Cdiv id=\\u0022contenedor\\u0022\\u003E \\u003Ch2 class=\\u0022text-left m-b-2\\u0022\\u003EInformaci\\u00f3n del veh\\u00edculo de patente AA345AA\\u003C\\/h2\\u003E\\n\\n\\n\\n \\u003Cdiv class=\\u0022panel panel-default panel-disabled m-b-2\\u0022\\u003E\\n \\u003Cdiv class=\\u0022panel-body\\u0022\\u003E\\n \\u003Ch2 class=\\u0022table_title m-b-2\\u0022\\u003EInformaci\\u00f3n del Registro Automotor\\u003C\\/h2\\u003E\\n \\u003Cdiv class=\\u0022col-md-6\\u0022\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003ERegistro Seccional\\u003C\\/label\\u003E\\n \\u003Cp\\u003ESAN MIGUEL N\\u00b0 1\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EDirecci\\u00f3n\\u003C\\/label\\u003E\\n \\u003Cp\\u003EMAESTRO ANGEL D\\u0027ELIA 766\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EPiso\\u003C\\/label\\u003E\\n \\u003Cp\\u003EPB\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EDepartamento\\u003C\\/label\\u003E\\n \\u003Cp\\u003E-\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EC\\u00f3digo postal\\u003C\\/label\\u003E\\n \\u003Cp\\u003E1663\\u003C\\/p\\u003E\\n \\u003C\\/div\\u003E\\n \\u003Cdiv class=\\u0022col-md-6\\u0022\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003ELocalidad\\u003C\\/label\\u003E\\n \\u003Cp\\u003ESAN MIGUEL\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EProvincia\\u003C\\/label\\u003E\\n \\u003Cp\\u003EBUENOS AIRES\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003ETel\\u00e9fono\\u003C\\/label\\u003E\\n \\u003Cp\\u003E(11)46646647\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EHorario\\u003C\\/label\\u003E\\n \\u003Cp\\u003E08:30 a 12:30\\u003C\\/p\\u003E\\n \\u003C\\/div\\u003E\\n \\u003C\\/div\\u003E\\n\\u003C\\/div\\u003E \\n\\n\\u003Cp class=\\u0022text-center m-t-3 m-b-1 hidden-print\\u0022\\u003E\\n \\u003Ca href=\\u0022javascript:window.print();\\u0022 class=\\u0022btn btn-default\\u0022\\u003EImprim\\u00ed la consulta\\u003C\\/a\\u003E \\u0026nbsp; \\u0026nbsp;\\n \\u003Ca href=\\u0022\\u0022 class=\\u0022btn use-ajax btn-primary\\u0022\\u003EHacer otra consulta\\u003C\\/a\\u003E\\n\\u003C\\/p\\u003E\\n\\u003C\\/div\\u003E"""
print(html.replace("\\/", "/").encode().decode('unicode_escape'))

For Python3

html = """\\u003Cdiv id=\\u0022contenedor\\u0022\\u003E \\u003Ch2 class=\\u0022text-left m-b-2\\u0022\\u003EInformaci\\u00f3n del veh\\u00edculo de patente AA345AA\\u003C\\/h2\\u003E\\n\\n\\n\\n \\u003Cdiv class=\\u0022panel panel-default panel-disabled m-b-2\\u0022\\u003E\\n \\u003Cdiv class=\\u0022panel-body\\u0022\\u003E\\n \\u003Ch2 class=\\u0022table_title m-b-2\\u0022\\u003EInformaci\\u00f3n del Registro Automotor\\u003C\\/h2\\u003E\\n \\u003Cdiv class=\\u0022col-md-6\\u0022\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003ERegistro Seccional\\u003C\\/label\\u003E\\n \\u003Cp\\u003ESAN MIGUEL N\\u00b0 1\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EDirecci\\u00f3n\\u003C\\/label\\u003E\\n \\u003Cp\\u003EMAESTRO ANGEL D\\u0027ELIA 766\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EPiso\\u003C\\/label\\u003E\\n \\u003Cp\\u003EPB\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EDepartamento\\u003C\\/label\\u003E\\n \\u003Cp\\u003E-\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EC\\u00f3digo postal\\u003C\\/label\\u003E\\n \\u003Cp\\u003E1663\\u003C\\/p\\u003E\\n \\u003C\\/div\\u003E\\n \\u003Cdiv class=\\u0022col-md-6\\u0022\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003ELocalidad\\u003C\\/label\\u003E\\n \\u003Cp\\u003ESAN MIGUEL\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EProvincia\\u003C\\/label\\u003E\\n \\u003Cp\\u003EBUENOS AIRES\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003ETel\\u00e9fono\\u003C\\/label\\u003E\\n \\u003Cp\\u003E(11)46646647\\u003C\\/p\\u003E\\n \\u003Clabel class=\\u0022control-label\\u0022\\u003EHorario\\u003C\\/label\\u003E\\n \\u003Cp\\u003E08:30 a 12:30\\u003C\\/p\\u003E\\n \\u003C\\/div\\u003E\\n \\u003C\\/div\\u003E\\n\\u003C\\/div\\u003E \\n\\n\\u003Cp class=\\u0022text-center m-t-3 m-b-1 hidden-print\\u0022\\u003E\\n \\u003Ca href=\\u0022javascript:window.print();\\u0022 class=\\u0022btn btn-default\\u0022\\u003EImprim\\u00ed la consulta\\u003C\\/a\\u003E \\u0026nbsp; \\u0026nbsp;\\n \\u003Ca href=\\u0022\\u0022 class=\\u0022btn use-ajax btn-primary\\u0022\\u003EHacer otra consulta\\u003C\\/a\\u003E\\n\\u003C\\/p\\u003E\\n\\u003C\\/div\\u003E"""
print(html.replace("\\/", "/").encode().decode('unicode_escape'))

回答 4

我不熟悉该库,但是如果您的问题是您不想要字节数组,则一种简单的方法是直接在类型转换中指定编码类型:

>>> my_byte_str
b'Hello World'

>>> str(my_byte_str, 'utf-8')
'Hello World'

I’m not familiar with the library, but if your problem is that you don’t want a byte array, one easy way is to specify an encoding type straight in a cast:

>>> my_byte_str
b'Hello World'

>>> str(my_byte_str, 'utf-8')
'Hello World'

回答 5

它已经在Python3中解码,请直接尝试应该可以。

It s already decoded in Python3, Try directly it should work.


回答 6

其他答案有点暗示它,但是问题可能出自于​​期望一个字节对象。在Python 3中,当您有一个类字节对象时,解码有效。在解码之前运行编码可能会“解决”问题,但这是无用的一对操作,将问题提示给上游。

Other answers sort of hint at it, but the problem may arise from expecting a bytes object. In Python 3, decode is valid when you have an object of class bytes. Running encode before decode may “fix” the problem, but it is a useless pair of operations that suggest the problem us upstream.


如何在Python中制作一个不变的对象?

问题:如何在Python中制作一个不变的对象?

尽管我从不需要它,但让我感到惊讶的是,在Python中创建不可变对象可能有些棘手。您不能仅仅重写__setattr__,因为这样您甚至都无法在中设置属性__init__。将元组子类化是一种有效的技巧:

class Immutable(tuple):

    def __new__(cls, a, b):
        return tuple.__new__(cls, (a, b))

    @property
    def a(self):
        return self[0]

    @property
    def b(self):
        return self[1]

    def __str__(self):
        return "<Immutable {0}, {1}>".format(self.a, self.b)

    def __setattr__(self, *ignored):
        raise NotImplementedError

    def __delattr__(self, *ignored):
        raise NotImplementedError

但是然后您可以通过和来访问ab变量,这很烦人。self[0]self[1]

在纯Python中这可能吗?如果没有,我将如何使用C扩展名呢?

(仅在Python 3中有效的答案是可以接受的)。

更新:

因此,将元组子类化是在纯Python 中完成此操作的方法[0],除了通过[1]等访问数据的附加可能性外,该方法行之有效。要解决此问题,所有遗漏的是如何在C中“正确”进行操作,这我怀疑这很简单,只是不执行任何geititemor setattribute等等。但是我没有为此做,而是为此提供了赏金,因为我很懒。:)

Although I have never needed this, it just struck me that making an immutable object in Python could be slightly tricky. You can’t just override __setattr__, because then you can’t even set attributes in the __init__. Subclassing a tuple is a trick that works:

class Immutable(tuple):

    def __new__(cls, a, b):
        return tuple.__new__(cls, (a, b))

    @property
    def a(self):
        return self[0]

    @property
    def b(self):
        return self[1]

    def __str__(self):
        return "<Immutable {0}, {1}>".format(self.a, self.b)

    def __setattr__(self, *ignored):
        raise NotImplementedError

    def __delattr__(self, *ignored):
        raise NotImplementedError

But then you have access to the a and b variables through self[0] and self[1], which is annoying.

Is this possible in Pure Python? If not, how would I do it with a C extension?

(Answers that work only in Python 3 are acceptable).

Update:

So subclassing tuple is the way to do it in Pure Python, which works well except for the additional possibility of accessing the data by [0], [1] etc. So, to complete this question all that is missing is howto do it “properly” in C, which I suspect would be quite simple, by just not implementing any geititem or setattribute, etc. But instead of doing it myself, I offer a bounty for that, because I’m lazy. :)


回答 0

我刚刚想到的另一种解决方案:获得与原始代码相同行为的最简单方法是

Immutable = collections.namedtuple("Immutable", ["a", "b"])

它不能解决可以通过[0]等访问属性的问题,但至少它要短得多,并具有与pickle和兼容的附加优点。copy

namedtuple创建与我在此答案中描述的类型相似的类型,即派生自tuple并使用__slots__。它在Python 2.6或更高版本中可用。

Yet another solution I just thought of: The simplest way to get the same behaviour as your original code is

Immutable = collections.namedtuple("Immutable", ["a", "b"])

It does not solve the problem that attributes can be accessed via [0] etc., but at least it’s considerably shorter and provides the additional advantage of being compatible with pickle and copy.

namedtuple creates a type similar to what I described in this answer, i.e. derived from tuple and using __slots__. It is available in Python 2.6 or above.


回答 1

最简单的方法是使用__slots__

class A(object):
    __slots__ = []

的实例A现在是不可变的,因为您无法在它们上设置任何属性。

如果您希望类实例包含数据,则可以将其与源自的数据结合使用tuple

from operator import itemgetter
class Point(tuple):
    __slots__ = []
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))
    x = property(itemgetter(0))
    y = property(itemgetter(1))

p = Point(2, 3)
p.x
# 2
p.y
# 3

编辑:如果要摆脱索引之一,则可以覆盖__getitem__()

class Point(tuple):
    __slots__ = []
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))
    @property
    def x(self):
        return tuple.__getitem__(self, 0)
    @property
    def y(self):
        return tuple.__getitem__(self, 1)
    def __getitem__(self, item):
        raise TypeError

请注意,operator.itemgetter在这种情况下,您不能使用属性,因为这将依赖Point.__getitem__()而不是tuple.__getitem__()。此外,这不会阻止对的使用tuple.__getitem__(p, 0),但是我几乎无法想象这应该如何构成问题。

我认为创建不可变对象的“正确”方法不是编写C扩展。Python通常依赖于图书馆实现者和图书馆用户征得成年人的同意,而不是真正强制执行接口,而应在文档中明确说明该接口。这就是为什么我不考虑__setattr__()通过提出object.__setattr__()问题来规避被覆盖的可能性的原因。如果有人这样做,则后果自负。

The easiest way to do this is using __slots__:

class A(object):
    __slots__ = []

Instances of A are immutable now, since you can’t set any attributes on them.

If you want the class instances to contain data, you can combine this with deriving from tuple:

from operator import itemgetter
class Point(tuple):
    __slots__ = []
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))
    x = property(itemgetter(0))
    y = property(itemgetter(1))

p = Point(2, 3)
p.x
# 2
p.y
# 3

Edit: If you want to get rid of indexing either, you can override __getitem__():

class Point(tuple):
    __slots__ = []
    def __new__(cls, x, y):
        return tuple.__new__(cls, (x, y))
    @property
    def x(self):
        return tuple.__getitem__(self, 0)
    @property
    def y(self):
        return tuple.__getitem__(self, 1)
    def __getitem__(self, item):
        raise TypeError

Note that you can’t use operator.itemgetter for the properties in thise case, since this would rely on Point.__getitem__() instead of tuple.__getitem__(). Fuerthermore this won’t prevent the use of tuple.__getitem__(p, 0), but I can hardly imagine how this should constitute a problem.

I don’t think the “right” way of creating an immutable object is writing a C extension. Python usually relies on library implementers and library users being consenting adults, and instead of really enforcing an interface, the interface should be clearly stated in the documentation. This is why I don’t consider the possibility of circumventing an overridden __setattr__() by calling object.__setattr__() a problem. If someone does this, it’s on her own risk.


回答 2

..如何在C.中正确执行

您可以使用Cython为Python创建扩展类型:

cdef class Immutable:
    cdef readonly object a, b
    cdef object __weakref__ # enable weak referencing support

    def __init__(self, a, b):
        self.a, self.b = a, b

它同时适用于Python 2.x和3。

测验

# compile on-the-fly
import pyximport; pyximport.install() # $ pip install cython
from immutable import Immutable

o = Immutable(1, 2)
assert o.a == 1, str(o.a)
assert o.b == 2

try: o.a = 3
except AttributeError:
    pass
else:
    assert 0, 'attribute must be readonly'

try: o[1]
except TypeError:
    pass
else:
    assert 0, 'indexing must not be supported'

try: o.c = 1
except AttributeError:
    pass
else:
    assert 0, 'no new attributes are allowed'

o = Immutable('a', [])
assert o.a == 'a'
assert o.b == []

o.b.append(3) # attribute may contain mutable object
assert o.b == [3]

try: o.c
except AttributeError:
    pass
else:
    assert 0, 'no c attribute'

o = Immutable(b=3,a=1)
assert o.a == 1 and o.b == 3

try: del o.b
except AttributeError:
    pass
else:
    assert 0, "can't delete attribute"

d = dict(b=3, a=1)
o = Immutable(**d)
assert o.a == d['a'] and o.b == d['b']

o = Immutable(1,b=3)
assert o.a == 1 and o.b == 3

try: object.__setattr__(o, 'a', 1)
except AttributeError:
    pass
else:
    assert 0, 'attributes are readonly'

try: object.__setattr__(o, 'c', 1)
except AttributeError:
    pass
else:
    assert 0, 'no new attributes'

try: Immutable(1,c=3)
except TypeError:
    pass
else:
    assert 0, 'accept only a,b keywords'

for kwd in [dict(a=1), dict(b=2)]:
    try: Immutable(**kwd)
    except TypeError:
        pass
    else:
        assert 0, 'Immutable requires exactly 2 arguments'

如果您不介意索引支持,那么@Sven Marnach的collections.namedtuple建议是可取的:

Immutable = collections.namedtuple("Immutable", "a b")

..howto do it “properly” in C..

You could use Cython to create an extension type for Python:

cdef class Immutable:
    cdef readonly object a, b
    cdef object __weakref__ # enable weak referencing support

    def __init__(self, a, b):
        self.a, self.b = a, b

It works both Python 2.x and 3.

Tests

# compile on-the-fly
import pyximport; pyximport.install() # $ pip install cython
from immutable import Immutable

o = Immutable(1, 2)
assert o.a == 1, str(o.a)
assert o.b == 2

try: o.a = 3
except AttributeError:
    pass
else:
    assert 0, 'attribute must be readonly'

try: o[1]
except TypeError:
    pass
else:
    assert 0, 'indexing must not be supported'

try: o.c = 1
except AttributeError:
    pass
else:
    assert 0, 'no new attributes are allowed'

o = Immutable('a', [])
assert o.a == 'a'
assert o.b == []

o.b.append(3) # attribute may contain mutable object
assert o.b == [3]

try: o.c
except AttributeError:
    pass
else:
    assert 0, 'no c attribute'

o = Immutable(b=3,a=1)
assert o.a == 1 and o.b == 3

try: del o.b
except AttributeError:
    pass
else:
    assert 0, "can't delete attribute"

d = dict(b=3, a=1)
o = Immutable(**d)
assert o.a == d['a'] and o.b == d['b']

o = Immutable(1,b=3)
assert o.a == 1 and o.b == 3

try: object.__setattr__(o, 'a', 1)
except AttributeError:
    pass
else:
    assert 0, 'attributes are readonly'

try: object.__setattr__(o, 'c', 1)
except AttributeError:
    pass
else:
    assert 0, 'no new attributes'

try: Immutable(1,c=3)
except TypeError:
    pass
else:
    assert 0, 'accept only a,b keywords'

for kwd in [dict(a=1), dict(b=2)]:
    try: Immutable(**kwd)
    except TypeError:
        pass
    else:
        assert 0, 'Immutable requires exactly 2 arguments'

If you don’t mind indexing support then collections.namedtuple suggested by @Sven Marnach is preferrable:

Immutable = collections.namedtuple("Immutable", "a b")

回答 3

另一个想法是完全禁止在构造函数中__setattr__使用object.__setattr__

class Point(object):
    def __init__(self, x, y):
        object.__setattr__(self, "x", x)
        object.__setattr__(self, "y", y)
    def __setattr__(self, *args):
        raise TypeError
    def __delattr__(self, *args):
        raise TypeError

当然,你可以使用object.__setattr__(p, "x", 3)修改Point的实例p,但同样的问题,你原来患有实施(试行tuple.__setattr__(i, "x", 42)Immutable实例)。

您可以在原始实现中应用相同的技巧:摆脱__getitem__(),并tuple.__getitem__()在属性函数中使用。

Another idea would be to completely disallow __setattr__ and use object.__setattr__ in the constructor:

class Point(object):
    def __init__(self, x, y):
        object.__setattr__(self, "x", x)
        object.__setattr__(self, "y", y)
    def __setattr__(self, *args):
        raise TypeError
    def __delattr__(self, *args):
        raise TypeError

Of course you could use object.__setattr__(p, "x", 3) to modify a Point instance p, but your original implementation suffers from the same problem (try tuple.__setattr__(i, "x", 42) on an Immutable instance).

You can apply the same trick in your original implementation: get rid of __getitem__(), and use tuple.__getitem__() in your property functions.


回答 4

您可以创建一个@immutable装饰器,该装饰器可以覆盖__setattr__ 并将其更改__slots__为一个空列表,然后__init__使用它来装饰该方法。

编辑:正如OP所指出的,更改__slots__属性只会阻止创建新属性,而不能进行修改。

Edit2:这是一个实现:

Edit3:使用__slots__会中断此代码,因为if会停止创建对象的__dict__。我正在寻找替代方案。

Edit4:就是这样。这是一个但有点骇人听闻的东西,但是可以作为练习:-)

class immutable(object):
    def __init__(self, immutable_params):
        self.immutable_params = immutable_params

    def __call__(self, new):
        params = self.immutable_params

        def __set_if_unset__(self, name, value):
            if name in self.__dict__:
                raise Exception("Attribute %s has already been set" % name)

            if not name in params:
                raise Exception("Cannot create atribute %s" % name)

            self.__dict__[name] = value;

        def __new__(cls, *args, **kws):
            cls.__setattr__ = __set_if_unset__

            return super(cls.__class__, cls).__new__(cls, *args, **kws)

        return __new__

class Point(object):
    @immutable(['x', 'y'])
    def __new__(): pass

    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2) 
p.x = 3 # Exception: Attribute x has already been set
p.z = 4 # Exception: Cannot create atribute z

You could create a @immutable decorator that either overrides the __setattr__ and change the __slots__ to an empty list, then decorate the __init__ method with it.

Edit: As the OP noted, changing the __slots__ attribute only prevents the creation of new attributes, not the modification.

Edit2: Here’s an implementation:

Edit3: Using __slots__ breaks this code, because if stops the creation of the object’s __dict__. I’m looking for an alternative.

Edit4: Well, that’s it. It’s a but hackish, but works as an exercise :-)

class immutable(object):
    def __init__(self, immutable_params):
        self.immutable_params = immutable_params

    def __call__(self, new):
        params = self.immutable_params

        def __set_if_unset__(self, name, value):
            if name in self.__dict__:
                raise Exception("Attribute %s has already been set" % name)

            if not name in params:
                raise Exception("Cannot create atribute %s" % name)

            self.__dict__[name] = value;

        def __new__(cls, *args, **kws):
            cls.__setattr__ = __set_if_unset__

            return super(cls.__class__, cls).__new__(cls, *args, **kws)

        return __new__

class Point(object):
    @immutable(['x', 'y'])
    def __new__(): pass

    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(1, 2) 
p.x = 3 # Exception: Attribute x has already been set
p.z = 4 # Exception: Cannot create atribute z

回答 5

使用冻结的数据类

对于Python 3.7+,您可以使用带有option数据类,这是一种非常Python化且可维护的方式来完成您想要的事情。frozen=True

它看起来像这样:

from dataclasses import dataclass

@dataclass(frozen=True)
class Immutable:
    a: Any
    b: Any

由于数据类的字段需要类型提示,因此我typing模块中使用了Any

不使用命名元组的原因

在Python 3.7之前,经常会看到namedtuple被用作不可变对象。在许多方面可能很棘手,其中之一是__eq__namedtuple之间的方法不考虑对象的类。例如:

from collections import namedtuple

ImmutableTuple = namedtuple("ImmutableTuple", ["a", "b"])
ImmutableTuple2 = namedtuple("ImmutableTuple2", ["a", "c"])

obj1 = ImmutableTuple(a=1, b=2)
obj2 = ImmutableTuple2(a=1, c=2)

obj1 == obj2  # will be True

如您所见,即使obj1和的类型obj2不同,即使它们的字段名称不同,obj1 == obj2仍会给出True。这是因为所__eq__使用的方法是元组的方法,该方法仅比较给定位置的字段的值。这可能是错误的巨大来源,特别是如果您将这些类作为子类。

Using a Frozen Dataclass

For Python 3.7+ you can use a Data Class with a frozen=True option, which is a very pythonic and maintainable way to do what you want.

It would look something like that:

from dataclasses import dataclass

@dataclass(frozen=True)
class Immutable:
    a: Any
    b: Any

As type hinting is required for dataclasses’ fields, I have used Any from the typing module.

Reasons NOT to use a Namedtuple

Before Python 3.7 it was frequent to see namedtuples being used as immutable objects. It can be tricky in many ways, one of them is that the __eq__ method between namedtuples does not consider the objects’ classes. For example:

from collections import namedtuple

ImmutableTuple = namedtuple("ImmutableTuple", ["a", "b"])
ImmutableTuple2 = namedtuple("ImmutableTuple2", ["a", "c"])

obj1 = ImmutableTuple(a=1, b=2)
obj2 = ImmutableTuple2(a=1, c=2)

obj1 == obj2  # will be True

As you see, even if the types of obj1 and obj2 are different, even if their fields’ names are different, obj1 == obj2 still gives True. That’s because the __eq__ method used is the tuple’s one, which compares only the values of the fields given their positions. That can be a huge source of errors, specially if you are subclassing these classes.


回答 6

除了使用元组或namedtuple,我认为这是完全不可能的。无论如何,如果您覆盖__setattr__(),用户可以随时通过object.__setattr__()直接调用绕过它。__setattr__保证所有依赖的解决方案都不会起作用。

以下是关于不使用某种元组就可以得到的最近值的信息:

class Immutable:
    __slots__ = ['a', 'b']
    def __init__(self, a, b):
        object.__setattr__(self, 'a', a)
        object.__setattr__(self, 'b', b)
    def __setattr__(self, *ignored):
        raise NotImplementedError
    __delattr__ = __setattr__

但是如果您努力尝试,它就会中断:

>>> t = Immutable(1, 2)
>>> t.a
1
>>> object.__setattr__(t, 'a', 2)
>>> t.a
2

但是Sven的使用namedtuple确实是一成不变的。

更新资料

由于问题已经更新,可以在C语言中正确执行操作,因此,这是我在Cython中如何正确执行操作的答案:

首先immutable.pyx

cdef class Immutable:
    cdef object _a, _b

    def __init__(self, a, b):
        self._a = a
        self._b = b

    property a:
        def __get__(self):
            return self._a

    property b:
        def __get__(self):
            return self._b

    def __repr__(self):
        return "<Immutable {0}, {1}>".format(self.a, self.b)

并对其setup.py进行编译(使用命令setup.py build_ext --inplace

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension("immutable", ["immutable.pyx"])]

setup(
  name = 'Immutable object',
  cmdclass = {'build_ext': build_ext},
  ext_modules = ext_modules
)

然后尝试一下:

>>> from immutable import Immutable
>>> p = Immutable(2, 3)
>>> p
<Immutable 2, 3>
>>> p.a = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> object.__setattr__(p, 'a', 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> p.a, p.b
(2, 3)
>>>      

I don’t think it is entirely possible except by using either a tuple or a namedtuple. No matter what, if you override __setattr__() the user can always bypass it by calling object.__setattr__() directly. Any solution that depends on __setattr__ is guaranteed not to work.

The following is about the nearest you can get without using some sort of tuple:

class Immutable:
    __slots__ = ['a', 'b']
    def __init__(self, a, b):
        object.__setattr__(self, 'a', a)
        object.__setattr__(self, 'b', b)
    def __setattr__(self, *ignored):
        raise NotImplementedError
    __delattr__ = __setattr__

but it breaks if you try hard enough:

>>> t = Immutable(1, 2)
>>> t.a
1
>>> object.__setattr__(t, 'a', 2)
>>> t.a
2

but Sven’s use of namedtuple is genuinely immutable.

Update

Since the question has been updated to ask how to do it properly in C, here’s my answer on how to do it properly in Cython:

First immutable.pyx:

cdef class Immutable:
    cdef object _a, _b

    def __init__(self, a, b):
        self._a = a
        self._b = b

    property a:
        def __get__(self):
            return self._a

    property b:
        def __get__(self):
            return self._b

    def __repr__(self):
        return "<Immutable {0}, {1}>".format(self.a, self.b)

and a setup.py to compile it (using the command setup.py build_ext --inplace:

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension("immutable", ["immutable.pyx"])]

setup(
  name = 'Immutable object',
  cmdclass = {'build_ext': build_ext},
  ext_modules = ext_modules
)

Then to try it out:

>>> from immutable import Immutable
>>> p = Immutable(2, 3)
>>> p
<Immutable 2, 3>
>>> p.a = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> object.__setattr__(p, 'a', 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: attribute 'a' of 'immutable.Immutable' objects is not writable
>>> p.a, p.b
(2, 3)
>>>      

回答 7

我通过重写来实现了不可变的类__setattr__,如果调用者是,则允许设置__init__

import inspect
class Immutable(object):
    def __setattr__(self, name, value):
        if inspect.stack()[2][3] != "__init__":
            raise Exception("Can't mutate an Immutable: self.%s = %r" % (name, value))
        object.__setattr__(self, name, value)

这还不够,因为它允许任何人___init__更改对象,但是您明白了。

I’ve made immutable classes by overriding __setattr__, and allowing the set if the caller is __init__:

import inspect
class Immutable(object):
    def __setattr__(self, name, value):
        if inspect.stack()[2][3] != "__init__":
            raise Exception("Can't mutate an Immutable: self.%s = %r" % (name, value))
        object.__setattr__(self, name, value)

This isn’t quite enough yet, since it allows anyone’s ___init__ to change the object, but you get the idea.


回答 8

除了出色的其他答案外,我还想为python 3.4(或3.3)添加一个方法。该答案建立在对该问题的多个先前答案的基础上。

在python 3.4中,可以使用不带setter的属性来创建无法修改的类成员。(在较早的版本中,可以不使用setter来分配属性。)

class A:
    __slots__=['_A__a']
    def __init__(self, aValue):
      self.__a=aValue
    @property
    def a(self):
        return self.__a

您可以像这样使用它:

instance=A("constant")
print (instance.a)

将打印 "constant"

但是调用instance.a=10会导致:

AttributeError: can't set attribute

解释:没有setter的属性是python 3.4(我认为3.3)的最新功能。如果您尝试分配给这样的属性,则会引发错误。使用插槽,我将membervariables限制为__A_a(是__a)。

问题:_A__a仍然可以分配到(instance._A__a=2)。但是如果您分配一个私有变量,那是您自己的错…

但是,此答案除其他外,不鼓励使用__slots__。最好使用其他方式来防止属性创建。

In addition to the excellent other answers I like to add a method for python 3.4 (or maybe 3.3). This answer builds upon several previouse answers to this question.

In python 3.4, you can use properties without setters to create class members that cannot be modified. (In earlier versions assigning to properties without a setter was possible.)

class A:
    __slots__=['_A__a']
    def __init__(self, aValue):
      self.__a=aValue
    @property
    def a(self):
        return self.__a

You can use it like this:

instance=A("constant")
print (instance.a)

which will print "constant"

But calling instance.a=10 will cause:

AttributeError: can't set attribute

Explaination: properties without setters are a very recent feature of python 3.4 (and I think 3.3). If you try to assign to such a property, an Error will be raised. Using slots I restrict the membervariables to __A_a (which is __a).

Problem: Assigning to _A__a is still possible (instance._A__a=2). But if you assign to a private variable, it is your own fault…

This answer among others, however, discourages the use of __slots__. Using other ways to prevent attribute creation might be preferrable.


回答 9

这是一个优雅的解决方案:

class Immutable(object):
    def __setattr__(self, key, value):
        if not hasattr(self, key):
            super().__setattr__(key, value)
        else:
            raise RuntimeError("Can't modify immutable object's attribute: {}".format(key))

从此类继承,初始化构造函数中的字段,一切就绪。

Here’s an elegant solution:

class Immutable(object):
    def __setattr__(self, key, value):
        if not hasattr(self, key):
            super().__setattr__(key, value)
        else:
            raise RuntimeError("Can't modify immutable object's attribute: {}".format(key))

Inherit from this class, initialize your fields in the constructor, and you’e all set.


回答 10

如果您对具有行为的对象感兴趣,那么namedtuple 几乎是您的解决方案。

如namedtuple 文档底部所述,您可以从namedtuple派生您自己的类。然后,您可以添加所需的行为。

例如(直接从文档获取的代码):

class Point(namedtuple('Point', 'x y')):
    __slots__ = ()
    @property
    def hypot(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    def __str__(self):
        return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

for p in Point(3, 4), Point(14, 5/7):
    print(p)

这将导致:

Point: x= 3.000  y= 4.000  hypot= 5.000
Point: x=14.000  y= 0.714  hypot=14.018

这种方法适用于Python 3和2.7(也已在IronPython上测试)。
唯一的缺点是继承树有点怪异。但这不是您通常玩的东西。

If you are interested in objects with behavior, then namedtuple is almost your solution.

As described at the bottom of the namedtuple documentation, you can derive your own class from namedtuple; and then, you can add the behavior you want.

For example (code taken directly from the documentation):

class Point(namedtuple('Point', 'x y')):
    __slots__ = ()
    @property
    def hypot(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5
    def __str__(self):
        return 'Point: x=%6.3f  y=%6.3f  hypot=%6.3f' % (self.x, self.y, self.hypot)

for p in Point(3, 4), Point(14, 5/7):
    print(p)

This will result in:

Point: x= 3.000  y= 4.000  hypot= 5.000
Point: x=14.000  y= 0.714  hypot=14.018

This approach works for both Python 3 and Python 2.7 (tested on IronPython as well).
The only downside is that the inheritance tree is a bit weird; but this is not something you usually play with.


回答 11

Immutable__init__方法完成执行后,继承自以下类的类及其实例是不可变的。正如其他人所指出的那样,由于它是纯python,所以没有什么可以阻止某人使用从base object和的变异特殊方法的type,但这足以阻止任何人无意间变异一个类/实例。

它通过劫持一个元类的类创建过程来工作。

"""Subclasses of class Immutable are immutable after their __init__ has run, in
the sense that all special methods with mutation semantics (in-place operators,
setattr, etc.) are forbidden.

"""  

# Enumerate the mutating special methods
mutation_methods = set()
# Arithmetic methods with in-place operations
iarithmetic = '''add sub mul div mod divmod pow neg pos abs bool invert lshift
                 rshift and xor or floordiv truediv matmul'''.split()
for op in iarithmetic:
    mutation_methods.add('__i%s__' % op)
# Operations on instance components (attributes, items, slices)
for verb in ['set', 'del']:
    for component in '''attr item slice'''.split():
        mutation_methods.add('__%s%s__' % (verb, component))
# Operations on properties
mutation_methods.update(['__set__', '__delete__'])


def checked_call(_self, name, method, *args, **kwargs):
    """Calls special method method(*args, **kw) on self if mutable."""
    self = args[0] if isinstance(_self, object) else _self
    if not getattr(self, '__mutable__', True):
        # self told us it's immutable, so raise an error
        cname= (self if isinstance(self, type) else self.__class__).__name__
        raise TypeError('%s is immutable, %s disallowed' % (cname, name))
    return method(*args, **kwargs)


def method_wrapper(_self, name):
    "Wrap a special method to check for mutability."
    method = getattr(_self, name)
    def wrapper(*args, **kwargs):
        return checked_call(_self, name, method, *args, **kwargs)
    wrapper.__name__ = name
    wrapper.__doc__ = method.__doc__
    return wrapper


def wrap_mutating_methods(_self):
    "Place the wrapper methods on mutative special methods of _self"
    for name in mutation_methods:
        if hasattr(_self, name):
            method = method_wrapper(_self, name)
            type.__setattr__(_self, name, method)


def set_mutability(self, ismutable):
    "Set __mutable__ by using the unprotected __setattr__"
    b = _MetaImmutable if isinstance(self, type) else Immutable
    super(b, self).__setattr__('__mutable__', ismutable)


class _MetaImmutable(type):

    '''The metaclass of Immutable. Wraps __init__ methods via __call__.'''

    def __init__(cls, *args, **kwargs):
        # Make class mutable for wrapping special methods
        set_mutability(cls, True)
        wrap_mutating_methods(cls)
        # Disable mutability
        set_mutability(cls, False)

    def __call__(cls, *args, **kwargs):
        '''Make an immutable instance of cls'''
        self = cls.__new__(cls)
        # Make the instance mutable for initialization
        set_mutability(self, True)
        # Execute cls's custom initialization on this instance
        self.__init__(*args, **kwargs)
        # Disable mutability
        set_mutability(self, False)
        return self

    # Given a class T(metaclass=_MetaImmutable), mutative special methods which
    # already exist on _MetaImmutable (a basic type) cannot be over-ridden
    # programmatically during _MetaImmutable's instantiation of T, because the
    # first place python looks for a method on an object is on the object's
    # __class__, and T.__class__ is _MetaImmutable. The two extant special
    # methods on a basic type are __setattr__ and __delattr__, so those have to
    # be explicitly overridden here.

    def __setattr__(cls, name, value):
        checked_call(cls, '__setattr__', type.__setattr__, cls, name, value)

    def __delattr__(cls, name, value):
        checked_call(cls, '__delattr__', type.__delattr__, cls, name, value)


class Immutable(object):

    """Inherit from this class to make an immutable object.

    __init__ methods of subclasses are executed by _MetaImmutable.__call__,
    which enables mutability for the duration.

    """

    __metaclass__ = _MetaImmutable


class T(int, Immutable):  # Checks it works with multiple inheritance, too.

    "Class for testing immutability semantics"

    def __init__(self, b):
        self.b = b

    @classmethod
    def class_mutation(cls):
        cls.a = 5

    def instance_mutation(self):
        self.c = 1

    def __iadd__(self, o):
        pass

    def not_so_special_mutation(self):
        self +=1

def immutabilityTest(f, name):
    "Call f, which should try to mutate class T or T instance."
    try:
        f()
    except TypeError, e:
        assert 'T is immutable, %s disallowed' % name in e.args
    else:
        raise RuntimeError('Immutability failed!')

immutabilityTest(T.class_mutation, '__setattr__')
immutabilityTest(T(6).instance_mutation, '__setattr__')
immutabilityTest(T(6).not_so_special_mutation, '__iadd__')

Classes which inherit from the following Immutable class are immutable, as are their instances, after their __init__ method finishes executing. Since it’s pure python, as others have pointed out, there’s nothing stopping someone from using the mutating special methods from the base object and type, but this is enough to stop anyone from mutating a class/instance by accident.

It works by hijacking the class-creation process with a metaclass.

"""Subclasses of class Immutable are immutable after their __init__ has run, in
the sense that all special methods with mutation semantics (in-place operators,
setattr, etc.) are forbidden.

"""  

# Enumerate the mutating special methods
mutation_methods = set()
# Arithmetic methods with in-place operations
iarithmetic = '''add sub mul div mod divmod pow neg pos abs bool invert lshift
                 rshift and xor or floordiv truediv matmul'''.split()
for op in iarithmetic:
    mutation_methods.add('__i%s__' % op)
# Operations on instance components (attributes, items, slices)
for verb in ['set', 'del']:
    for component in '''attr item slice'''.split():
        mutation_methods.add('__%s%s__' % (verb, component))
# Operations on properties
mutation_methods.update(['__set__', '__delete__'])


def checked_call(_self, name, method, *args, **kwargs):
    """Calls special method method(*args, **kw) on self if mutable."""
    self = args[0] if isinstance(_self, object) else _self
    if not getattr(self, '__mutable__', True):
        # self told us it's immutable, so raise an error
        cname= (self if isinstance(self, type) else self.__class__).__name__
        raise TypeError('%s is immutable, %s disallowed' % (cname, name))
    return method(*args, **kwargs)


def method_wrapper(_self, name):
    "Wrap a special method to check for mutability."
    method = getattr(_self, name)
    def wrapper(*args, **kwargs):
        return checked_call(_self, name, method, *args, **kwargs)
    wrapper.__name__ = name
    wrapper.__doc__ = method.__doc__
    return wrapper


def wrap_mutating_methods(_self):
    "Place the wrapper methods on mutative special methods of _self"
    for name in mutation_methods:
        if hasattr(_self, name):
            method = method_wrapper(_self, name)
            type.__setattr__(_self, name, method)


def set_mutability(self, ismutable):
    "Set __mutable__ by using the unprotected __setattr__"
    b = _MetaImmutable if isinstance(self, type) else Immutable
    super(b, self).__setattr__('__mutable__', ismutable)


class _MetaImmutable(type):

    '''The metaclass of Immutable. Wraps __init__ methods via __call__.'''

    def __init__(cls, *args, **kwargs):
        # Make class mutable for wrapping special methods
        set_mutability(cls, True)
        wrap_mutating_methods(cls)
        # Disable mutability
        set_mutability(cls, False)

    def __call__(cls, *args, **kwargs):
        '''Make an immutable instance of cls'''
        self = cls.__new__(cls)
        # Make the instance mutable for initialization
        set_mutability(self, True)
        # Execute cls's custom initialization on this instance
        self.__init__(*args, **kwargs)
        # Disable mutability
        set_mutability(self, False)
        return self

    # Given a class T(metaclass=_MetaImmutable), mutative special methods which
    # already exist on _MetaImmutable (a basic type) cannot be over-ridden
    # programmatically during _MetaImmutable's instantiation of T, because the
    # first place python looks for a method on an object is on the object's
    # __class__, and T.__class__ is _MetaImmutable. The two extant special
    # methods on a basic type are __setattr__ and __delattr__, so those have to
    # be explicitly overridden here.

    def __setattr__(cls, name, value):
        checked_call(cls, '__setattr__', type.__setattr__, cls, name, value)

    def __delattr__(cls, name, value):
        checked_call(cls, '__delattr__', type.__delattr__, cls, name, value)


class Immutable(object):

    """Inherit from this class to make an immutable object.

    __init__ methods of subclasses are executed by _MetaImmutable.__call__,
    which enables mutability for the duration.

    """

    __metaclass__ = _MetaImmutable


class T(int, Immutable):  # Checks it works with multiple inheritance, too.

    "Class for testing immutability semantics"

    def __init__(self, b):
        self.b = b

    @classmethod
    def class_mutation(cls):
        cls.a = 5

    def instance_mutation(self):
        self.c = 1

    def __iadd__(self, o):
        pass

    def not_so_special_mutation(self):
        self +=1

def immutabilityTest(f, name):
    "Call f, which should try to mutate class T or T instance."
    try:
        f()
    except TypeError, e:
        assert 'T is immutable, %s disallowed' % name in e.args
    else:
        raise RuntimeError('Immutability failed!')

immutabilityTest(T.class_mutation, '__setattr__')
immutabilityTest(T(6).instance_mutation, '__setattr__')
immutabilityTest(T(6).not_so_special_mutation, '__iadd__')

回答 12

前一阵子我需要这个,并决定为此制作一个Python包。初始版本现在在PyPI上:

$ pip install immutable

使用方法:

>>> from immutable import ImmutableFactory
>>> MyImmutable = ImmitableFactory.create(prop1=1, prop2=2, prop3=3)
>>> MyImmutable.prop1
1

完整的文档在这里:https : //github.com/theengineear/immutable

希望它能有所帮助,它如前所述包装了一个namedtuple,但是使实例化变得更加简单。

I needed this a little while ago and decided to make a Python package for it. The initial version is on PyPI now:

$ pip install immutable

To use:

>>> from immutable import ImmutableFactory
>>> MyImmutable = ImmitableFactory.create(prop1=1, prop2=2, prop3=3)
>>> MyImmutable.prop1
1

Full docs here: https://github.com/theengineear/immutable

Hope it helps, it wraps a namedtuple as has been discussed, but makes instantiation much simpler.


回答 13

这种方法不会停止object.__setattr__工作,但我仍然发现它很有用:

class A(object):

    def __new__(cls, children, *args, **kwargs):
        self = super(A, cls).__new__(cls)
        self._frozen = False  # allow mutation from here to end of  __init__
        # other stuff you need to do in __new__ goes here
        return self

    def __init__(self, *args, **kwargs):
        super(A, self).__init__()
        self._frozen = True  # prevent future mutation

    def __setattr__(self, name, value):
        # need to special case setting _frozen.
        if name != '_frozen' and self._frozen:
            raise TypeError('Instances are immutable.')
        else:
            super(A, self).__setattr__(name, value)

    def __delattr__(self, name):
        if self._frozen:
            raise TypeError('Instances are immutable.')
        else:
            super(A, self).__delattr__(name)

您可能需要__setitem__根据用例覆盖更多的内容(例如)。

This way doesn’t stop object.__setattr__ from working, but I’ve still found it useful:

class A(object):

    def __new__(cls, children, *args, **kwargs):
        self = super(A, cls).__new__(cls)
        self._frozen = False  # allow mutation from here to end of  __init__
        # other stuff you need to do in __new__ goes here
        return self

    def __init__(self, *args, **kwargs):
        super(A, self).__init__()
        self._frozen = True  # prevent future mutation

    def __setattr__(self, name, value):
        # need to special case setting _frozen.
        if name != '_frozen' and self._frozen:
            raise TypeError('Instances are immutable.')
        else:
            super(A, self).__setattr__(name, value)

    def __delattr__(self, name):
        if self._frozen:
            raise TypeError('Instances are immutable.')
        else:
            super(A, self).__delattr__(name)

you may need to override more stuff (like __setitem__) depending on the use case.


回答 14

从Python 3.7开始,您可以在类中使用@dataclass装饰器,它将像结构一样是不可变的!虽然,它可能会也可能不会__hash__()在您的类中添加方法。引用:

hash()由内置的hash()以及将对象添加到哈希集合(如字典和集合)时使用。带有hash()表示该类的实例是不可变的。可变性是一个复杂的属性,它取决于程序员的意图,eq()的存在和行为以及dataclass()装饰器中的eq和冻结标志的值。

默认情况下,除非这样做是安全的,否则dataclass()不会隐式添加hash()方法。它不会添加或更改现有的显式定义的hash()方法。如hash()文档中所述,设置类属性hash = None对Python具有特定的含义。

如果未明确定义hash()或将其设置为None,则dataclass()可以添加隐式hash()方法。尽管不建议这样做,但是您可以强制dataclass()创建带有unsafe_hash = True 的hash()方法。如果您的类在逻辑上是不可变的,但仍然可以进行突变,则可能是这种情况。这是一个特殊的用例,应仔细考虑。

这里是上面链接的文档中的示例:

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

As of Python 3.7, you can use the @dataclass decorator in your class and it will be immutable like a struct! Though, it may or may not add a __hash__() method to your class. Quote:

hash() is used by built-in hash(), and when objects are added to hashed collections such as dictionaries and sets. Having a hash() implies that instances of the class are immutable. Mutability is a complicated property that depends on the programmer’s intent, the existence and behavior of eq(), and the values of the eq and frozen flags in the dataclass() decorator.

By default, dataclass() will not implicitly add a hash() method unless it is safe to do so. Neither will it add or change an existing explicitly defined hash() method. Setting the class attribute hash = None has a specific meaning to Python, as described in the hash() documentation.

If hash() is not explicit defined, or if it is set to None, then dataclass() may add an implicit hash() method. Although not recommended, you can force dataclass() to create a hash() method with unsafe_hash=True. This might be the case if your class is logically immutable but can nonetheless be mutated. This is a specialized use case and should be considered carefully.

Here the example from the docs linked above:

@dataclass
class InventoryItem:
    '''Class for keeping track of an item in inventory.'''
    name: str
    unit_price: float
    quantity_on_hand: int = 0

    def total_cost(self) -> float:
        return self.unit_price * self.quantity_on_hand

回答 15

您可以覆盖setattr并仍然使用init来设置变量。您将使用超类setattr。这是代码。

不可变的类别:
    __slots__ =('a','b')
    def __init __(self,a,b):
        super().__ setattr __('a',a)
        super().__ setattr __('b',b)

    def __str __():
        返回“” .format(self.a,self.b)

    def __setattr __(自己,*忽略):
        引发NotImplementedError

    def __delattr __(自己,*忽略):
        引发NotImplementedError

You can override setattr and still use init to set the variable. You would use super class setattr. here is the code.

class Immutable:
    __slots__ = ('a','b')
    def __init__(self, a , b):
        super().__setattr__('a',a)
        super().__setattr__('b',b)

    def __str__(self):
        return "".format(self.a, self.b)

    def __setattr__(self, *ignored):
        raise NotImplementedError

    def __delattr__(self, *ignored):
        raise NotImplementedError

回答 16

第三方attr模块提供了此功能

编辑:python 3.7已将该思想采纳到stdlib中@dataclass

$ pip install attrs
$ python
>>> @attr.s(frozen=True)
... class C(object):
...     x = attr.ib()
>>> i = C(1)
>>> i.x = 2
Traceback (most recent call last):
   ...
attr.exceptions.FrozenInstanceError: can't set attribute

attr__setattr__根据文档,该类通过重写实现冻结的类,并且在每个实例化时间对性能的影响都很小。

如果您习惯于将类用作数据类型,attr那么它可能会特别有用,因为它可以为您处理样板(但不会产生任何魔力)。特别是,它为您编写了九种dunder(__X__)方法(除非您将其中的任何一种都关闭了),包括repr,init,hash和所有比较功能。

attr还为提供了帮助__slots__

The third party attr module provides this functionality.

Edit: python 3.7 has adopted this idea into the stdlib with @dataclass.

$ pip install attrs
$ python
>>> @attr.s(frozen=True)
... class C(object):
...     x = attr.ib()
>>> i = C(1)
>>> i.x = 2
Traceback (most recent call last):
   ...
attr.exceptions.FrozenInstanceError: can't set attribute

attr implements frozen classes by overriding __setattr__ and has a minor performance impact at each instantiation time, according to the documentation.

If you’re in the habit of using classes as datatypes, attr may be especially useful as it takes care of the boilerplate for you (but doesn’t do any magic). In particular, it writes nine dunder (__X__) methods for you (unless you turn any of them off), including repr, init, hash and all the comparison functions.

attr also provides a helper for __slots__.


回答 17

因此,我正在分别编写python 3:

I)在数据类修饰器的帮助下并设置Frozen = True。我们可以在python中创建不可变的对象。

为此,需要从数据类库中导入数据类,并需要设置Frozen = True

例如

从数据类导入数据类

@dataclass(frozen=True)
class Location:
    name: str
    longitude: float = 0.0
    latitude: float = 0.0

o / p:

l =位置(“ Delhi”,112.345,234.788)l.name’Delhi’l。经度112.345 l.latitude 234.788 l.name =“加尔各答”数据类。FrozenInstanceError:无法分配给字段’name’

资料来源:https : //realpython.com/python-data-classes/

So, I am writing respective of python 3:

I) with the help of data class decorator and set frozen=True. we can create immutable objects in python.

for this need to import data class from data classes lib and needs to set frozen=True

ex.

from dataclasses import dataclass

@dataclass(frozen=True)
class Location:
    name: str
    longitude: float = 0.0
    latitude: float = 0.0

o/p:

>>> l = Location("Delhi", 112.345, 234.788)
>>> l.name
'Delhi'
>>> l.longitude
112.345
>>> l.latitude
234.788
>>> l.name = "Kolkata"
dataclasses.FrozenInstanceError: cannot assign to field 'name'
>>> 

Source: https://realpython.com/python-data-classes/


回答 18

一种替代方法是创建使实例不可变的包装器。

class Immutable(object):

    def __init__(self, wrapped):
        super(Immutable, self).__init__()
        object.__setattr__(self, '_wrapped', wrapped)

    def __getattribute__(self, item):
        return object.__getattribute__(self, '_wrapped').__getattribute__(item)

    def __setattr__(self, key, value):
        raise ImmutableError('Object {0} is immutable.'.format(self._wrapped))

    __delattr__ = __setattr__

    def __iter__(self):
        return object.__getattribute__(self, '_wrapped').__iter__()

    def next(self):
        return object.__getattribute__(self, '_wrapped').next()

    def __getitem__(self, item):
        return object.__getattribute__(self, '_wrapped').__getitem__(item)

immutable_instance = Immutable(my_instance)

在仅某些实例必须是不可变的情况下(例如函数调用的默认参数),这很有用。

也可以在不可变的工厂中使用,例如:

@classmethod
def immutable_factory(cls, *args, **kwargs):
    return Immutable(cls.__init__(*args, **kwargs))

object.__setattr__由于Python的动态特性,也可以防止,但也容易出错。

An alternative approach is to create a wrapper which makes an instance immutable.

class Immutable(object):

    def __init__(self, wrapped):
        super(Immutable, self).__init__()
        object.__setattr__(self, '_wrapped', wrapped)

    def __getattribute__(self, item):
        return object.__getattribute__(self, '_wrapped').__getattribute__(item)

    def __setattr__(self, key, value):
        raise ImmutableError('Object {0} is immutable.'.format(self._wrapped))

    __delattr__ = __setattr__

    def __iter__(self):
        return object.__getattribute__(self, '_wrapped').__iter__()

    def next(self):
        return object.__getattribute__(self, '_wrapped').next()

    def __getitem__(self, item):
        return object.__getattribute__(self, '_wrapped').__getitem__(item)

immutable_instance = Immutable(my_instance)

This is useful in situations where only some instances have to be immutable (like default arguments of function calls).

Can also be used in immutable factories like:

@classmethod
def immutable_factory(cls, *args, **kwargs):
    return Immutable(cls.__init__(*args, **kwargs))

Also protects from object.__setattr__, but fallable to other tricks due to Python’s dynamic nature.


回答 19

我使用了与Alex相同的想法:一个元类和一个“初始标记”,但是结合了重写__setattr__:

>>> from abc import ABCMeta
>>> _INIT_MARKER = '_@_in_init_@_'
>>> class _ImmutableMeta(ABCMeta):
... 
...     """Meta class to construct Immutable."""
... 
...     def __call__(cls, *args, **kwds):
...         obj = cls.__new__(cls, *args, **kwds)
...         object.__setattr__(obj, _INIT_MARKER, True)
...         cls.__init__(obj, *args, **kwds)
...         object.__delattr__(obj, _INIT_MARKER)
...         return obj
...
>>> def _setattr(self, name, value):
...     if hasattr(self, _INIT_MARKER):
...         object.__setattr__(self, name, value)
...     else:
...         raise AttributeError("Instance of '%s' is immutable."
...                              % self.__class__.__name__)
...
>>> def _delattr(self, name):
...     raise AttributeError("Instance of '%s' is immutable."
...                          % self.__class__.__name__)
...
>>> _im_dict = {
...     '__doc__': "Mix-in class for immutable objects.",
...     '__copy__': lambda self: self,   # self is immutable, so just return it
...     '__setattr__': _setattr,
...     '__delattr__': _delattr}
...
>>> Immutable = _ImmutableMeta('Immutable', (), _im_dict)

注意:我直接调用元类以使其适用于Python 2.x和3.x。

>>> class T1(Immutable):
... 
...     def __init__(self, x=1, y=2):
...         self.x = x
...         self.y = y
...
>>> t1 = T1(y=8)
>>> t1.x, t1.y
(1, 8)
>>> t1.x = 7
AttributeError: Instance of 'T1' is immutable.

它也适用于插槽…:

>>> class T2(Immutable):
... 
...     __slots__ = 's1', 's2'
... 
...     def __init__(self, s1, s2):
...         self.s1 = s1
...         self.s2 = s2
...
>>> t2 = T2('abc', 'xyz')
>>> t2.s1, t2.s2
('abc', 'xyz')
>>> t2.s1 += 'd'
AttributeError: Instance of 'T2' is immutable.

…以及多重继承:

>>> class T3(T1, T2):
... 
...     def __init__(self, x, y, s1, s2):
...         T1.__init__(self, x, y)
...         T2.__init__(self, s1, s2)
...
>>> t3 = T3(12, 4, 'a', 'b')
>>> t3.x, t3.y, t3.s1, t3.s2
(12, 4, 'a', 'b')
>>> t3.y -= 3
AttributeError: Instance of 'T3' is immutable.

但是请注意,可变属性保持可变:

>>> t3 = T3(12, [4, 7], 'a', 'b')
>>> t3.y.append(5)
>>> t3.y
[4, 7, 5]

I used the same idea as Alex: a meta-class and an “init marker”, but in combination with over-writing __setattr__:

>>> from abc import ABCMeta
>>> _INIT_MARKER = '_@_in_init_@_'
>>> class _ImmutableMeta(ABCMeta):
... 
...     """Meta class to construct Immutable."""
... 
...     def __call__(cls, *args, **kwds):
...         obj = cls.__new__(cls, *args, **kwds)
...         object.__setattr__(obj, _INIT_MARKER, True)
...         cls.__init__(obj, *args, **kwds)
...         object.__delattr__(obj, _INIT_MARKER)
...         return obj
...
>>> def _setattr(self, name, value):
...     if hasattr(self, _INIT_MARKER):
...         object.__setattr__(self, name, value)
...     else:
...         raise AttributeError("Instance of '%s' is immutable."
...                              % self.__class__.__name__)
...
>>> def _delattr(self, name):
...     raise AttributeError("Instance of '%s' is immutable."
...                          % self.__class__.__name__)
...
>>> _im_dict = {
...     '__doc__': "Mix-in class for immutable objects.",
...     '__copy__': lambda self: self,   # self is immutable, so just return it
...     '__setattr__': _setattr,
...     '__delattr__': _delattr}
...
>>> Immutable = _ImmutableMeta('Immutable', (), _im_dict)

Note: I’m calling the meta-class directly to make it work both for Python 2.x and 3.x.

>>> class T1(Immutable):
... 
...     def __init__(self, x=1, y=2):
...         self.x = x
...         self.y = y
...
>>> t1 = T1(y=8)
>>> t1.x, t1.y
(1, 8)
>>> t1.x = 7
AttributeError: Instance of 'T1' is immutable.

It does work also with slots …:

>>> class T2(Immutable):
... 
...     __slots__ = 's1', 's2'
... 
...     def __init__(self, s1, s2):
...         self.s1 = s1
...         self.s2 = s2
...
>>> t2 = T2('abc', 'xyz')
>>> t2.s1, t2.s2
('abc', 'xyz')
>>> t2.s1 += 'd'
AttributeError: Instance of 'T2' is immutable.

… and multiple inheritance:

>>> class T3(T1, T2):
... 
...     def __init__(self, x, y, s1, s2):
...         T1.__init__(self, x, y)
...         T2.__init__(self, s1, s2)
...
>>> t3 = T3(12, 4, 'a', 'b')
>>> t3.x, t3.y, t3.s1, t3.s2
(12, 4, 'a', 'b')
>>> t3.y -= 3
AttributeError: Instance of 'T3' is immutable.

Note, however, that mutable attributes stay to be mutable:

>>> t3 = T3(12, [4, 7], 'a', 'b')
>>> t3.y.append(5)
>>> t3.y
[4, 7, 5]

回答 20

这里没有真正包含的一件事是完全不变性……不仅是父对象,还包括所有子对象。例如,元组/ frozensets可能是不可变的,但它所属的对象可能不是不变的。这是一个小的(不完整的)版本,可以很好地执行强制不变性的操作:

# Initialize lists
a = [1,2,3]
b = [4,5,6]
c = [7,8,9]

l = [a,b]

# We can reassign in a list 
l[0] = c

# But not a tuple
t = (a,b)
#t[0] = c -> Throws exception
# But elements can be modified
t[0][1] = 4
t
([1, 4, 3], [4, 5, 6])
# Fix it back
t[0][1] = 2

li = ImmutableObject(l)
li
[[1, 2, 3], [4, 5, 6]]
# Can't assign
#li[0] = c will fail
# Can reference
li[0]
[1, 2, 3]
# But immutability conferred on returned object too
#li[0][1] = 4 will throw an exception

# Full solution should wrap all the comparison e.g. decorators.
# Also, you'd usually want to add a hash function, i didn't put
# an interface for that.

class ImmutableObject(object):
    def __init__(self, inobj):
        self._inited = False
        self._inobj = inobj
        self._inited = True

    def __repr__(self):
        return self._inobj.__repr__()

    def __str__(self):
        return self._inobj.__str__()

    def __getitem__(self, key):
        return ImmutableObject(self._inobj.__getitem__(key))

    def __iter__(self):
        return self._inobj.__iter__()

    def __setitem__(self, key, value):
        raise AttributeError, 'Object is read-only'

    def __getattr__(self, key):
        x = getattr(self._inobj, key)
        if callable(x):
              return x
        else:
              return ImmutableObject(x)

    def __hash__(self):
        return self._inobj.__hash__()

    def __eq__(self, second):
        return self._inobj.__eq__(second)

    def __setattr__(self, attr, value):
        if attr not in  ['_inobj', '_inited'] and self._inited == True:
            raise AttributeError, 'Object is read-only'
        object.__setattr__(self, attr, value)

One thing that’s not really included here is total immutability… not just the parent object, but all the children as well. tuples/frozensets may be immutable for instance, but the objects that it’s part of may not be. Here’s a small (incomplete) version that does a decent job of enforcing immutability all the way down:

# Initialize lists
a = [1,2,3]
b = [4,5,6]
c = [7,8,9]

l = [a,b]

# We can reassign in a list 
l[0] = c

# But not a tuple
t = (a,b)
#t[0] = c -> Throws exception
# But elements can be modified
t[0][1] = 4
t
([1, 4, 3], [4, 5, 6])
# Fix it back
t[0][1] = 2

li = ImmutableObject(l)
li
[[1, 2, 3], [4, 5, 6]]
# Can't assign
#li[0] = c will fail
# Can reference
li[0]
[1, 2, 3]
# But immutability conferred on returned object too
#li[0][1] = 4 will throw an exception

# Full solution should wrap all the comparison e.g. decorators.
# Also, you'd usually want to add a hash function, i didn't put
# an interface for that.

class ImmutableObject(object):
    def __init__(self, inobj):
        self._inited = False
        self._inobj = inobj
        self._inited = True

    def __repr__(self):
        return self._inobj.__repr__()

    def __str__(self):
        return self._inobj.__str__()

    def __getitem__(self, key):
        return ImmutableObject(self._inobj.__getitem__(key))

    def __iter__(self):
        return self._inobj.__iter__()

    def __setitem__(self, key, value):
        raise AttributeError, 'Object is read-only'

    def __getattr__(self, key):
        x = getattr(self._inobj, key)
        if callable(x):
              return x
        else:
              return ImmutableObject(x)

    def __hash__(self):
        return self._inobj.__hash__()

    def __eq__(self, second):
        return self._inobj.__eq__(second)

    def __setattr__(self, attr, value):
        if attr not in  ['_inobj', '_inited'] and self._inited == True:
            raise AttributeError, 'Object is read-only'
        object.__setattr__(self, attr, value)

回答 21

您可以在init的最终声明中覆盖setAttr。这样您就可以构建但不能改变。显然,您仍然可以使用usint对象覆盖。setAttr但实际上大多数语言都有某种形式的反射,因此始终是泄漏的抽象。不变性更多是关于防止客户意外违反对象合同。我用:

============================

提供的原始解决方案不正确,已根据注释使用此处的解决方案进行了更新

原始解决方案以一种有趣的方式是错误的,因此包含在底部。

==============================

class ImmutablePair(object):

    __initialised = False # a class level variable that should always stay false.
    def __init__(self, a, b):
        try :
            self.a = a
            self.b = b
        finally:
            self.__initialised = True #an instance level variable

    def __setattr__(self, key, value):
        if self.__initialised:
            self._raise_error()
        else :
            super(ImmutablePair, self).__setattr__(key, value)

    def _raise_error(self, *args, **kw):
        raise NotImplementedError("Attempted To Modify Immutable Object")

if __name__ == "__main__":

    immutable_object = ImmutablePair(1,2)

    print immutable_object.a
    print immutable_object.b

    try :
        immutable_object.a = 3
    except Exception as e:
        print e

    print immutable_object.a
    print immutable_object.b

输出:

1
2
Attempted To Modify Immutable Object
1
2

======================================

原始实现:

注释中正确地指出,这实际上是行不通的,因为它会在覆盖类setattr方法时阻止创建多个对象,这意味着无法创建第二个对象作为self.a = will在第二次初始化时失败。

class ImmutablePair(object):

    def __init__(self, a, b):
        self.a = a
        self.b = b
        ImmutablePair.__setattr__ = self._raise_error

    def _raise_error(self, *args, **kw):
        raise NotImplementedError("Attempted To Modify Immutable Object")

You can just override setAttr in the final statement of init. THen you can construct but not change. Obviously you can still override by usint object.setAttr but in practice most languages have some form of reflection so immutablility is always a leaky abstraction. Immutability is more about preventing clients from accidentally violating the contract of an object. I use:

=============================

The original solution offered was incorrect, this was updated based on the comments using the solution from here

The original solution is wrong in an interesting way, so it is included at the bottom.

===============================

class ImmutablePair(object):

    __initialised = False # a class level variable that should always stay false.
    def __init__(self, a, b):
        try :
            self.a = a
            self.b = b
        finally:
            self.__initialised = True #an instance level variable

    def __setattr__(self, key, value):
        if self.__initialised:
            self._raise_error()
        else :
            super(ImmutablePair, self).__setattr__(key, value)

    def _raise_error(self, *args, **kw):
        raise NotImplementedError("Attempted To Modify Immutable Object")

if __name__ == "__main__":

    immutable_object = ImmutablePair(1,2)

    print immutable_object.a
    print immutable_object.b

    try :
        immutable_object.a = 3
    except Exception as e:
        print e

    print immutable_object.a
    print immutable_object.b

Output :

1
2
Attempted To Modify Immutable Object
1
2

======================================

Original Implementation:

It was pointed out in the comments, correctly, that this does not in fact work, as it prevents the creation of more than one object as you are overriding the class setattr method, which means a second cannot be created as self.a = will fail on the second initialisation.

class ImmutablePair(object):

    def __init__(self, a, b):
        self.a = a
        self.b = b
        ImmutablePair.__setattr__ = self._raise_error

    def _raise_error(self, *args, **kw):
        raise NotImplementedError("Attempted To Modify Immutable Object")

回答 22

下面的基本解决方案解决了以下情况:

  • __init__() 可以像往常一样访问属性。
  • 冻结对象以更改属性后,仅:

想法是在__setattr__每次对象冻结状态更改时重写方法并替换其实现。

因此,我们需要一些方法(_freeze),用于存储这两种实现并在需要时在它们之间进行切换。

该机制可以在用户类内部实现,也可以从特殊Freezer类继承,如下所示:

class Freezer:
    def _freeze(self, do_freeze=True):
        def raise_sa(*args):            
            raise AttributeError("Attributes are frozen and can not be changed!")
        super().__setattr__('_active_setattr', (super().__setattr__, raise_sa)[do_freeze])

    def __setattr__(self, key, value):        
        return self._active_setattr(key, value)

class A(Freezer):    
    def __init__(self):
        self._freeze(False)
        self.x = 10
        self._freeze()

The basic solution below addresses the following scenario:

  • __init__() can be written accessing the attributes as usual.
  • AFTER that the OBJECT is frozen for attributes changes only:

The idea is to override __setattr__ method and replace its implementation each time the object frozen status is changed.

So we need some method (_freeze) which stores those two implementations and switches between them when requested.

This mechanism may be implemented inside the user class or inherited from a special Freezer class as shown below:

class Freezer:
    def _freeze(self, do_freeze=True):
        def raise_sa(*args):            
            raise AttributeError("Attributes are frozen and can not be changed!")
        super().__setattr__('_active_setattr', (super().__setattr__, raise_sa)[do_freeze])

    def __setattr__(self, key, value):        
        return self._active_setattr(key, value)

class A(Freezer):    
    def __init__(self):
        self._freeze(False)
        self.x = 10
        self._freeze()

回答 23

就像一个 dict

我有一个开放源代码库,在这里我以一种功能性的方式来做事,因此在不可变对象中移动数据很有帮助。但是,我不需要转换数据对象以使客户端与之交互。因此,我想出了这一点- 它为您提供了一个像对象一样不变的字典 +一些辅助方法。

感谢斯文Marnach在他回答的基本实施限制产权更新和删除的。

import json 
# ^^ optional - If you don't care if it prints like a dict
# then rip this and __str__ and __repr__ out

class Immutable(object):

    def __init__(self, **kwargs):
        """Sets all values once given
        whatever is passed in kwargs
        """
        for k,v in kwargs.items():
            object.__setattr__(self, k, v)

    def __setattr__(self, *args):
        """Disables setting attributes via
        item.prop = val or item['prop'] = val
        """
        raise TypeError('Immutable objects cannot have properties set after init')

    def __delattr__(self, *args):
        """Disables deleting properties"""
        raise TypeError('Immutable objects cannot have properties deleted')

    def __getitem__(self, item):
        """Allows for dict like access of properties
        val = item['prop']
        """
        return self.__dict__[item]

    def __repr__(self):
        """Print to repl in a dict like fashion"""
        return self.pprint()

    def __str__(self):
        """Convert to a str in a dict like fashion"""
        return self.pprint()

    def __eq__(self, other):
        """Supports equality operator
        immutable({'a': 2}) == immutable({'a': 2})"""
        if other is None:
            return False
        return self.dict() == other.dict()

    def keys(self):
        """Paired with __getitem__ supports **unpacking
        new = { **item, **other }
        """
        return self.__dict__.keys()

    def get(self, *args, **kwargs):
        """Allows for dict like property access
        item.get('prop')
        """
        return self.__dict__.get(*args, **kwargs)

    def pprint(self):
        """Helper method used for printing that
        formats in a dict like way
        """
        return json.dumps(self,
            default=lambda o: o.__dict__,
            sort_keys=True,
            indent=4)

    def dict(self):
        """Helper method for getting the raw dict value
        of the immutable object"""
        return self.__dict__

辅助方法

def update(obj, **kwargs):
    """Returns a new instance of the given object with
    all key/val in kwargs set on it
    """
    return immutable({
        **obj,
        **kwargs
    })

def immutable(obj):
    return Immutable(**obj)

例子

obj = immutable({
    'alpha': 1,
    'beta': 2,
    'dalet': 4
})

obj.alpha # 1
obj['alpha'] # 1
obj.get('beta') # 2

del obj['alpha'] # TypeError
obj.alpha = 2 # TypeError

new_obj = update(obj, alpha=10)

new_obj is not obj # True
new_obj.get('alpha') == 10 # True

Just Like a dict

I have an open source library where I’m doing things in a functional way so moving data around in an immutable object is helpful. However, I don’t want to have to transform my data object for the client to interact with them. So, I came up with this – it gives you a dict like object thats immutable + some helper methods.

Credit to Sven Marnach in his answer for the basic implementation of restricting property updating and deleting.

import json 
# ^^ optional - If you don't care if it prints like a dict
# then rip this and __str__ and __repr__ out

class Immutable(object):

    def __init__(self, **kwargs):
        """Sets all values once given
        whatever is passed in kwargs
        """
        for k,v in kwargs.items():
            object.__setattr__(self, k, v)

    def __setattr__(self, *args):
        """Disables setting attributes via
        item.prop = val or item['prop'] = val
        """
        raise TypeError('Immutable objects cannot have properties set after init')

    def __delattr__(self, *args):
        """Disables deleting properties"""
        raise TypeError('Immutable objects cannot have properties deleted')

    def __getitem__(self, item):
        """Allows for dict like access of properties
        val = item['prop']
        """
        return self.__dict__[item]

    def __repr__(self):
        """Print to repl in a dict like fashion"""
        return self.pprint()

    def __str__(self):
        """Convert to a str in a dict like fashion"""
        return self.pprint()

    def __eq__(self, other):
        """Supports equality operator
        immutable({'a': 2}) == immutable({'a': 2})"""
        if other is None:
            return False
        return self.dict() == other.dict()

    def keys(self):
        """Paired with __getitem__ supports **unpacking
        new = { **item, **other }
        """
        return self.__dict__.keys()

    def get(self, *args, **kwargs):
        """Allows for dict like property access
        item.get('prop')
        """
        return self.__dict__.get(*args, **kwargs)

    def pprint(self):
        """Helper method used for printing that
        formats in a dict like way
        """
        return json.dumps(self,
            default=lambda o: o.__dict__,
            sort_keys=True,
            indent=4)

    def dict(self):
        """Helper method for getting the raw dict value
        of the immutable object"""
        return self.__dict__

Helper methods

def update(obj, **kwargs):
    """Returns a new instance of the given object with
    all key/val in kwargs set on it
    """
    return immutable({
        **obj,
        **kwargs
    })

def immutable(obj):
    return Immutable(**obj)

Examples

obj = immutable({
    'alpha': 1,
    'beta': 2,
    'dalet': 4
})

obj.alpha # 1
obj['alpha'] # 1
obj.get('beta') # 2

del obj['alpha'] # TypeError
obj.alpha = 2 # TypeError

new_obj = update(obj, alpha=10)

new_obj is not obj # True
new_obj.get('alpha') == 10 # True