标签归档:Python

如何使用csv.DictWriter编写标题行?

问题:如何使用csv.DictWriter编写标题行?

假设我有一个csv.DictReader对象,并且想将其写为CSV文件。我怎样才能做到这一点?

我知道我可以这样写数据行

dr = csv.DictReader(open(f), delimiter='\t')
# process my dr object
# ...
# write out object
output = csv.DictWriter(open(f2, 'w'), delimiter='\t')
for item in dr:
    output.writerow(item)

但是,如何包含字段名?

Assume I have a csv.DictReader object and I want to write it out as a CSV file. How can I do this?

I know that I can write the rows of data like this:

dr = csv.DictReader(open(f), delimiter='\t')
# process my dr object
# ...
# write out object
output = csv.DictWriter(open(f2, 'w'), delimiter='\t')
for item in dr:
    output.writerow(item)

But how can I include the fieldnames?


回答 0

编辑:
在2.7 / 3.2中,有一个新writeheader()方法。同样,John Machin的答案提供了一种更简单的写标题行的方法。现在使用2.7 / 3.2中提供
writeheader()方法的简单示例:

from collections import OrderedDict
ordered_fieldnames = OrderedDict([('field1',None),('field2',None)])
with open(outfile,'wb') as fou:
    dw = csv.DictWriter(fou, delimiter='\t', fieldnames=ordered_fieldnames)
    dw.writeheader()
    # continue on to write data

实例化DictWriter需要一个fieldnames参数。
文档中

fieldnames参数标识传递到writerow()方法的字典中的值写入csvfile的顺序。

换句话说,Fieldnames参数是必需的,因为Python字典本质上是无序的。
以下是如何将标头和数据写入文件的示例。
注意:with声明是在2.6中添加的。如果使用2.5:from __future__ import with_statement

with open(infile,'rb') as fin:
    dr = csv.DictReader(fin, delimiter='\t')

# dr.fieldnames contains values from first row of `f`.
with open(outfile,'wb') as fou:
    dw = csv.DictWriter(fou, delimiter='\t', fieldnames=dr.fieldnames)
    headers = {} 
    for n in dw.fieldnames:
        headers[n] = n
    dw.writerow(headers)
    for row in dr:
        dw.writerow(row)

正如@FM在评论中提到的,您可以将标头编写压缩为单行代码,例如:

with open(outfile,'wb') as fou:
    dw = csv.DictWriter(fou, delimiter='\t', fieldnames=dr.fieldnames)
    dw.writerow(dict((fn,fn) for fn in dr.fieldnames))
    for row in dr:
        dw.writerow(row)

Edit:
In 2.7 / 3.2 there is a new writeheader() method. Also, John Machin’s answer provides a simpler method of writing the header row.
Simple example of using the writeheader() method now available in 2.7 / 3.2:

from collections import OrderedDict
ordered_fieldnames = OrderedDict([('field1',None),('field2',None)])
with open(outfile,'wb') as fou:
    dw = csv.DictWriter(fou, delimiter='\t', fieldnames=ordered_fieldnames)
    dw.writeheader()
    # continue on to write data

Instantiating DictWriter requires a fieldnames argument.
From the documentation:

The fieldnames parameter identifies the order in which values in the dictionary passed to the writerow() method are written to the csvfile.

Put another way: The Fieldnames argument is required because Python dicts are inherently unordered.
Below is an example of how you’d write the header and data to a file.
Note: with statement was added in 2.6. If using 2.5: from __future__ import with_statement

with open(infile,'rb') as fin:
    dr = csv.DictReader(fin, delimiter='\t')

# dr.fieldnames contains values from first row of `f`.
with open(outfile,'wb') as fou:
    dw = csv.DictWriter(fou, delimiter='\t', fieldnames=dr.fieldnames)
    headers = {} 
    for n in dw.fieldnames:
        headers[n] = n
    dw.writerow(headers)
    for row in dr:
        dw.writerow(row)

As @FM mentions in a comment, you can condense header-writing to a one-liner, e.g.:

with open(outfile,'wb') as fou:
    dw = csv.DictWriter(fou, delimiter='\t', fieldnames=dr.fieldnames)
    dw.writerow(dict((fn,fn) for fn in dr.fieldnames))
    for row in dr:
        dw.writerow(row)

回答 1

一些选择:

(1)费力地在您的字段名称中做出一个身份映射(即不做任何事)命令,以便csv.DictWriter可以将其转换回列表并将其传递给csv.writer实例。

(2)文档中提到了“基础writer实例”……所以就用它(最后是示例)。

dw.writer.writerow(dw.fieldnames)

(3)避免csv.Dictwriter产生开销,并由csv.writer自己完成

写入数据:

w.writerow([d[k] for k in fieldnames])

要么

w.writerow([d.get(k, restval) for k in fieldnames])

除了extrasaction“功能”之外,我更愿意自己编写代码。这样,您可以报告所有带有键和值的“附加”,而不仅仅是第一个额外的键。DictWriter真正令人讨厌的是,如果您在构建每个dict时亲自验证了密钥,则需要记住使用extrasaction =’ignore’,否则它将缓慢进行(字段名是列表),重复检查:

wrong_fields = [k for k in rowdict if k not in self.fieldnames]

============

>>> f = open('csvtest.csv', 'wb')
>>> import csv
>>> fns = 'foo bar zot'.split()
>>> dw = csv.DictWriter(f, fns, restval='Huh?')
# dw.writefieldnames(fns) -- no such animal
>>> dw.writerow(fns) # no such luck, it can't imagine what to do with a list
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\python26\lib\csv.py", line 144, in writerow
    return self.writer.writerow(self._dict_to_list(rowdict))
  File "C:\python26\lib\csv.py", line 141, in _dict_to_list
    return [rowdict.get(key, self.restval) for key in self.fieldnames]
AttributeError: 'list' object has no attribute 'get'
>>> dir(dw)
['__doc__', '__init__', '__module__', '_dict_to_list', 'extrasaction', 'fieldnam
es', 'restval', 'writer', 'writerow', 'writerows']
# eureka
>>> dw.writer.writerow(dw.fieldnames)
>>> dw.writerow({'foo':'oof'})
>>> f.close()
>>> open('csvtest.csv', 'rb').read()
'foo,bar,zot\r\noof,Huh?,Huh?\r\n'
>>>

A few options:

(1) Laboriously make an identity-mapping (i.e. do-nothing) dict out of your fieldnames so that csv.DictWriter can convert it back to a list and pass it to a csv.writer instance.

(2) The documentation mentions “the underlying writer instance” … so just use it (example at the end).

dw.writer.writerow(dw.fieldnames)

(3) Avoid the csv.Dictwriter overhead and do it yourself with csv.writer

Writing data:

w.writerow([d[k] for k in fieldnames])

or

w.writerow([d.get(k, restval) for k in fieldnames])

Instead of the extrasaction “functionality”, I’d prefer to code it myself; that way you can report ALL “extras” with the keys and values, not just the first extra key. What is a real nuisance with DictWriter is that if you’ve verified the keys yourself as each dict was being built, you need to remember to use extrasaction=’ignore’ otherwise it’s going to SLOWLY (fieldnames is a list) repeat the check:

wrong_fields = [k for k in rowdict if k not in self.fieldnames]

============

>>> f = open('csvtest.csv', 'wb')
>>> import csv
>>> fns = 'foo bar zot'.split()
>>> dw = csv.DictWriter(f, fns, restval='Huh?')
# dw.writefieldnames(fns) -- no such animal
>>> dw.writerow(fns) # no such luck, it can't imagine what to do with a list
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\python26\lib\csv.py", line 144, in writerow
    return self.writer.writerow(self._dict_to_list(rowdict))
  File "C:\python26\lib\csv.py", line 141, in _dict_to_list
    return [rowdict.get(key, self.restval) for key in self.fieldnames]
AttributeError: 'list' object has no attribute 'get'
>>> dir(dw)
['__doc__', '__init__', '__module__', '_dict_to_list', 'extrasaction', 'fieldnam
es', 'restval', 'writer', 'writerow', 'writerows']
# eureka
>>> dw.writer.writerow(dw.fieldnames)
>>> dw.writerow({'foo':'oof'})
>>> f.close()
>>> open('csvtest.csv', 'rb').read()
'foo,bar,zot\r\noof,Huh?,Huh?\r\n'
>>>

回答 2

另一种方法是在输出中添加以下行之前添加:

output.writerow(dict(zip(dr.fieldnames, dr.fieldnames)))

zip将返回包含相同值的doublet列表。此列表可用于启动字典。

Another way to do this would be to add before adding lines in your output, the following line :

output.writerow(dict(zip(dr.fieldnames, dr.fieldnames)))

The zip would return a list of doublet containing the same value. This list could be used to initiate a dictionary.


如何解决Python中的“ ImportError:没有名为…的模块”错误?

问题:如何解决Python中的“ ImportError:没有名为…的模块”错误?

解决此ImportError错误的正确方法是什么?

我有以下目录结构:

/home/bodacydo
/home/bodacydo/work
/home/bodacydo/work/project
/home/bodacydo/work/project/programs
/home/bodacydo/work/project/foo

我在目录中

/home/bodacydo/work/project

现在,如果我输入

python ./programs/my_python_program.py

我立刻得到

ImportError: No module named foo.tasks

./programs/my_python_program.py包含以下行:

from foo.tasks import my_function

我不明白为什么python找不到./foo/tasks.py-它在那里。

如果我从Python外壳程序执行此操作,那么它将起作用:

python
>>> from foo.tasks import my_function

只有通过python ./programs/my_python_program.py脚本调用它才行。

What is the correct way to fix this ImportError error?

I have the following directory structure:

/home/bodacydo
/home/bodacydo/work
/home/bodacydo/work/project
/home/bodacydo/work/project/programs
/home/bodacydo/work/project/foo

And I am in the directory

/home/bodacydo/work/project

Now if I type

python ./programs/my_python_program.py

I instantly get

ImportError: No module named foo.tasks

The ./programs/my_python_program.py contains the following line:

from foo.tasks import my_function

I can’t understand why python won’t find ./foo/tasks.py – it’s there.

If I do it from the Python shell, then it works:

python
>>> from foo.tasks import my_function

It only doesn’t work if I call it via python ./programs/my_python_program.py script.


回答 0

Python不会将当前目录添加到sys.path,而是将脚本所在的目录添加/home/bodacydo/work/project到。添加到sys.path$PYTHONPATH

Python does not add the current directory to sys.path, but rather the directory that the script is in. Add /home/bodacydo/work/project to either sys.path or $PYTHONPATH.


回答 1

__init__.py在foo目录中是否有一个名为的文件?如果不是,则python不会将foo识别为python包。

有关更多信息,请参见python教程中有关软件包部分

Do you have a file called __init__.py in the foo directory? If not then python won’t recognise foo as a python package.

See the section on packages in the python tutorial for more information.


回答 2

以下是分步解决方案:

  1. 添加一个调用的脚本run.py/home/bodacydo/work/project,并编辑这样说:

    import programs.my_python_program
    programs.my_python_program.main()
    

    main()用中的等效方法替换my_python_program。)

  2. /home/bodacydo/work/project
  3. run.py

说明:由于python将PYTHONPATH附加到其运行脚本的路径,因此 running run.py将附加/home/bodacydo/work/project。和voilàimport foo.tasks将被发现。

Here is a step-by-step solution:

  1. Add a script called run.py in /home/bodacydo/work/project and edit it like this:

    import programs.my_python_program
    programs.my_python_program.main()
    

    (replace main() with your equivalent method in my_python_program.)

  2. Go to /home/bodacydo/work/project
  3. Run run.py

Explanation: Since python appends to PYTHONPATH the path of the script from which it runs, running run.py will append /home/bodacydo/work/project. And voilà, import foo.tasks will be found.


回答 3

将库添加到PYTHONPATH的示例解决方案。

  1. 将以下行添加到〜/ .bashrc或直接运行它:

    export PYTHONPATH="$PYTHONPATH:$HOME/.python"
  2. 然后将所需的库链接到〜/ .python文件夹中,例如

    ln -s /home/user/work/project/foo ~/.python/

Example solution for adding the library to your PYTHONPATH.

  1. Add the following line into your ~/.bashrc or just run it directly:

    export PYTHONPATH="$PYTHONPATH:$HOME/.python"
    
  2. Then link your required library into your ~/.python folder, e.g.

    ln -s /home/user/work/project/foo ~/.python/
    

回答 4

一个更好的比设定的修复PYTHONPATH方法是使用python -m module.path

这将正确设置,sys.path[0]并且是执行模块的更可靠方法。

我有一个快速的书面记录这个问题,其他的应答者提到的原因,因为这是python path/to/file.py看跌期权path/to上的开始PYTHONPATHsys.path)。

A better fix than setting PYTHONPATH is to use python -m module.path

This will correctly set sys.path[0] and is a more reliable way to execute modules.

I have a quick writeup about this problem, as other answerers have mentioned the reason for this is python path/to/file.py puts path/to on the beginning of the PYTHONPATH (sys.path).


回答 5

在我看来,我必须考虑该foo文件夹是一个独立的库。我可能要考虑将其移动到Lib\site-packagespython安装中的文件夹中。我可能要考虑在foo.pth此处添加文件。

我知道这是一个图书馆,因为其中./programs/my_python_program.py包含以下行:

from foo.tasks import my_function

因此,这与./programs的同级文件夹无关紧要./foo。这my_python_program.py是作为如下脚本运行的事实:

python ./programs/my_python_program.py

In my mind I have to consider that the foo folder is a stand-alone library. I might want to consider moving it to the Lib\site-packages folder within a python installation. I might want to consider adding a foo.pth file there.

I know it’s a library since the ./programs/my_python_program.py contains the following line:

from foo.tasks import my_function

So it doesn’t matter that ./programs is a sibling folder to ./foo. It’s the fact that my_python_program.py is run as a script like this:

python ./programs/my_python_program.py


回答 6

如果在使用安装版本时遇到此问题,请在使用时setup.py确保模块已包含在其中packages

setup(name='Your program',
    version='0.7.0',
    description='Your desccription',
    packages=['foo', 'foo.bar'], # add `foo.bar` here

If you have this problem when using an instaled version, when using setup.py, make sure your module is included inside packages

setup(name='Your program',
    version='0.7.0',
    description='Your desccription',
    packages=['foo', 'foo.bar'], # add `foo.bar` here

使用Python argparse创建隐藏参数

问题:使用Python argparse创建隐藏参数

是否可以在Python argparse.ArgumentParser的用法或帮助(script.py --help)中不显示参数的情况下将其添加到python中?

Is it possible to add an Argument to an python argparse.ArgumentParser without it showing up in the usage or help (script.py --help)?


回答 0

是的,您可以将help选项设置add_argumentargparse.SUPPRESS。这是argparse文档中的示例:

>>> parser = argparse.ArgumentParser(prog='frobble')
>>> parser.add_argument('--foo', help=argparse.SUPPRESS)
>>> parser.print_help()
usage: frobble [-h]

optional arguments:
  -h, --help  show this help message and exit

Yes, you can set the help option to add_argument to argparse.SUPPRESS. Here’s an example from the argparse documentation:

>>> parser = argparse.ArgumentParser(prog='frobble')
>>> parser.add_argument('--foo', help=argparse.SUPPRESS)
>>> parser.print_help()
usage: frobble [-h]

optional arguments:
  -h, --help  show this help message and exit

回答 1

我通过添加启用隐藏选项的选项来做到这一点,并通过查看来获取它sysv.args

如果执行此操作,sys.argv则如果您假设选项是-s启用隐藏选项,则必须将您从中选择的特殊参数直接包含在解析列表中。

parser.add_argument('-a', '-axis',
                    dest="axis", action="store_true", default=False,
                    help="Rotate the earth")
if "-s" in sys.argv or "-secret" in sys.argv:
    parser.add_argument('-s', '-secret',
                        dest="secret", action="store_true", default=False,
                        help="Enable secret options")
    parser.add_argument('-d', '-drill',
                        dest="drill", action="store_true", default=False,
                        help="drill baby, drill")

I do it by adding an option to enable the hidden ones, and grab that by looking at sysv.args.

If you do this, you have to include the special arg you pick out of sys.argv directly in the parse list if you Assume the option is -s to enable hidden options.

parser.add_argument('-a', '-axis',
                    dest="axis", action="store_true", default=False,
                    help="Rotate the earth")
if "-s" in sys.argv or "-secret" in sys.argv:
    parser.add_argument('-s', '-secret',
                        dest="secret", action="store_true", default=False,
                        help="Enable secret options")
    parser.add_argument('-d', '-drill',
                        dest="drill", action="store_true", default=False,
                        help="drill baby, drill")

从条目长度不同的字典创建数据框

问题:从条目长度不同的字典创建数据框

假设我有一本包含10个键值对的字典。每个条目包含一个numpy数组。但是,所有数组的长度都不相同。

如何创建每个列包含不同条目的数据框?

当我尝试:

pd.DataFrame(my_dict)

我得到:

ValueError: arrays must all be the same length

有什么办法可以克服吗?我很高兴Pandas使用NaN这些列来填充较短的条目。

Say I have a dictionary with 10 key-value pairs. Each entry holds a numpy array. However, the length of the array is not the same for all of them.

How can I create a dataframe where each column holds a different entry?

When I try:

pd.DataFrame(my_dict)

I get:

ValueError: arrays must all be the same length

Any way to overcome this? I am happy to have Pandas use NaN to pad those columns for the shorter entries.


回答 0

在Python 3.x中:

In [6]: d = dict( A = np.array([1,2]), B = np.array([1,2,3,4]) )

In [7]: pd.DataFrame(dict([ (k,pd.Series(v)) for k,v in d.items() ]))
Out[7]: 
    A  B
0   1  1
1   2  2
2 NaN  3
3 NaN  4

在Python 2.x中:

替换d.items()d.iteritems()

In Python 3.x:

import pandas as pd
import numpy as np

d = dict( A = np.array([1,2]), B = np.array([1,2,3,4]) )
    
pd.DataFrame(dict([ (k,pd.Series(v)) for k,v in d.items() ]))

Out[7]: 
    A  B
0   1  1
1   2  2
2 NaN  3
3 NaN  4

In Python 2.x:

replace d.items() with d.iteritems().


回答 1

这是一种简单的方法:

In[20]: my_dict = dict( A = np.array([1,2]), B = np.array([1,2,3,4]) )
In[21]: df = pd.DataFrame.from_dict(my_dict, orient='index')
In[22]: df
Out[22]: 
   0  1   2   3
A  1  2 NaN NaN
B  1  2   3   4
In[23]: df.transpose()
Out[23]: 
    A  B
0   1  1
1   2  2
2 NaN  3
3 NaN  4

Here’s a simple way to do that:

In[20]: my_dict = dict( A = np.array([1,2]), B = np.array([1,2,3,4]) )
In[21]: df = pd.DataFrame.from_dict(my_dict, orient='index')
In[22]: df
Out[22]: 
   0  1   2   3
A  1  2 NaN NaN
B  1  2   3   4
In[23]: df.transpose()
Out[23]: 
    A  B
0   1  1
1   2  2
2 NaN  3
3 NaN  4

回答 2

下面是一种整理语法但仍能与其他答案进行相同操作的方法:

>>> mydict = {'one': [1,2,3], 2: [4,5,6,7], 3: 8}

>>> dict_df = pd.DataFrame({ key:pd.Series(value) for key, value in mydict.items() })

>>> dict_df

   one  2    3
0  1.0  4  8.0
1  2.0  5  NaN
2  3.0  6  NaN
3  NaN  7  NaN

列表也有类似的语法:

>>> mylist = [ [1,2,3], [4,5], 6 ]

>>> list_df = pd.DataFrame([ pd.Series(value) for value in mylist ])

>>> list_df

     0    1    2
0  1.0  2.0  3.0
1  4.0  5.0  NaN
2  6.0  NaN  NaN

列表的另一种语法是:

>>> mylist = [ [1,2,3], [4,5], 6 ]

>>> list_df = pd.DataFrame({ i:pd.Series(value) for i, value in enumerate(mylist) })

>>> list_df

   0    1    2
0  1  4.0  6.0
1  2  5.0  NaN
2  3  NaN  NaN

您可能还必须转置结果和/或更改列数据类型(浮点数,整数等)。

A way of tidying up your syntax, but still do essentially the same thing as these other answers, is below:

>>> mydict = {'one': [1,2,3], 2: [4,5,6,7], 3: 8}

>>> dict_df = pd.DataFrame({ key:pd.Series(value) for key, value in mydict.items() })

>>> dict_df

   one  2    3
0  1.0  4  8.0
1  2.0  5  NaN
2  3.0  6  NaN
3  NaN  7  NaN

A similar syntax exists for lists, too:

>>> mylist = [ [1,2,3], [4,5], 6 ]

>>> list_df = pd.DataFrame([ pd.Series(value) for value in mylist ])

>>> list_df

     0    1    2
0  1.0  2.0  3.0
1  4.0  5.0  NaN
2  6.0  NaN  NaN

Another syntax for lists is:

>>> mylist = [ [1,2,3], [4,5], 6 ]

>>> list_df = pd.DataFrame({ i:pd.Series(value) for i, value in enumerate(mylist) })

>>> list_df

   0    1    2
0  1  4.0  6.0
1  2  5.0  NaN
2  3  NaN  NaN

You may additionally have to transpose the result and/or change the column data types (float, integer, etc).


回答 3

虽然这不能直接回答OP的问题。当我有不相等的数组并且我想分享的时候,我发现这是一个很好的解决方案:

从熊猫文档

In [31]: d = {'one' : Series([1., 2., 3.], index=['a', 'b', 'c']),
   ....:      'two' : Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}
   ....: 

In [32]: df = DataFrame(d)

In [33]: df
Out[33]: 
   one  two
a    1    1
b    2    2
c    3    3
d  NaN    4

While this does not directly answer the OP’s question. I found this to be an excellent solution for my case when I had unequal arrays and I’d like to share:

from pandas documentation

In [31]: d = {'one' : Series([1., 2., 3.], index=['a', 'b', 'c']),
   ....:      'two' : Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}
   ....: 

In [32]: df = DataFrame(d)

In [33]: df
Out[33]: 
   one  two
a    1    1
b    2    2
c    3    3
d  NaN    4

回答 4

您还可以将其与对象列表pd.concat一起axis=1使用pd.Series

import pandas as pd, numpy as np

d = {'A': np.array([1,2]), 'B': np.array([1,2,3,4])}

res = pd.concat([pd.Series(v, name=k) for k, v in d.items()], axis=1)

print(res)

     A  B
0  1.0  1
1  2.0  2
2  NaN  3
3  NaN  4

You can also use pd.concat along axis=1 with a list of pd.Series objects:

import pandas as pd, numpy as np

d = {'A': np.array([1,2]), 'B': np.array([1,2,3,4])}

res = pd.concat([pd.Series(v, name=k) for k, v in d.items()], axis=1)

print(res)

     A  B
0  1.0  1
1  2.0  2
2  NaN  3
3  NaN  4

回答 5

以下两行均能完美运行:

pd.DataFrame.from_dict(df, orient='index').transpose() #A

pd.DataFrame(dict([ (k,pd.Series(v)) for k,v in df.items() ])) #B (Better)

但是在Jupyter上使用%timeit时,B与A的速度之比为4倍,这在使用庞大的数据集(主要是具有大量列/功能)时尤其令人印象深刻。

Both the following lines work perfectly :

pd.DataFrame.from_dict(df, orient='index').transpose() #A

pd.DataFrame(dict([ (k,pd.Series(v)) for k,v in df.items() ])) #B (Better)

But with %timeit on Jupyter, I’ve got a ratio of 4x speed for B vs A, which is quite impressive especially when working with a huge data set (mainly with a big number of columns/features).


回答 6

如果您不想显示它,NaN并且有两个特定的长度,则在每个剩余的单元格中添加一个“空格”也可以。

import pandas

long = [6, 4, 7, 3]
short = [5, 6]

for n in range(len(long) - len(short)):
    short.append(' ')

df = pd.DataFrame({'A':long, 'B':short}]
# Make sure Excel file exists in the working directory
datatoexcel = pd.ExcelWriter('example1.xlsx',engine = 'xlsxwriter')
df.to_excel(datatoexcel,sheet_name = 'Sheet1')
datatoexcel.save()

   A  B
0  6  5
1  4  6
2  7   
3  3   

如果条目长度超过2个,建议您制作一个使用类似方法的函数。

If you don’t want it to show NaN and you have two particular lengths, adding a ‘space’ in each remaining cell would also work.

import pandas

long = [6, 4, 7, 3]
short = [5, 6]

for n in range(len(long) - len(short)):
    short.append(' ')

df = pd.DataFrame({'A':long, 'B':short}]
# Make sure Excel file exists in the working directory
datatoexcel = pd.ExcelWriter('example1.xlsx',engine = 'xlsxwriter')
df.to_excel(datatoexcel,sheet_name = 'Sheet1')
datatoexcel.save()

   A  B
0  6  5
1  4  6
2  7   
3  3   

If you have more than 2 lengths of entries, it is advisable to make a function which uses a similar method.


回答 7

pd.DataFrame([my_dict])会做!

pd.DataFrame([my_dict]) will do!


从PHP运行Python脚本

问题:从PHP运行Python脚本

我正在尝试使用以下命令从PHP运行Python脚本:

exec('/usr/bin/python2.7 /srv/http/assets/py/switch.py arg1 arg2');

但是,PHP根本不会产生任何输出。错误报告设置为E_ALL,并且display_errors打开。

这是我尝试过的:

  • 我使用python2/usr/bin/python2python2.7不是/usr/bin/python2.7
  • 我还使用了相对路径而不是绝对路径,它也没有改变任何东西。
  • 我试着使用的命令execshell_execsystem

但是,如果我跑步

if (exec('echo TEST') == 'TEST')
{
    echo 'exec works!';
}

shutdown now什么也没做,却可以正常工作。

PHP有权访问和执行文件。

编辑:感谢亚历杭德罗,我能够解决此问题。如果您遇到相同的问题,请不要忘记您的Web服务器可能/希望不是以root用户身份运行。尝试以您的Web服务器用户或具有类似权限的用户身份登录,然后尝试自己运行命令。

I’m trying to run a Python script from PHP using the following command:

exec('/usr/bin/python2.7 /srv/http/assets/py/switch.py arg1 arg2');

However, PHP simply doesn’t produce any output. Error reporting is set to E_ALL and display_errors is on.

Here’s what I’ve tried:

  • I used python2, /usr/bin/python2 and python2.7 instead of /usr/bin/python2.7
  • I also used a relative path instead of an absolute path which didn’t change anything either.
  • I tried using the commands exec, shell_exec, system.

However, if I run

if (exec('echo TEST') == 'TEST')
{
    echo 'exec works!';
}

it works perfectly fine while shutdown now doesn’t do anything.

PHP has the permissions to access and execute the file.

EDIT: Thanks to Alejandro, I was able to fix the problem. If you have the same problem, don’t forget that your webserver probably/hopefully doesn’t run as root. Try logging in as your webserver’s user or a user with similar permissions and try to run the commands yourself.


回答 0

在Ubuntu Server 10.04上测试。希望它对Arch Linux也有帮助。

在PHP中使用shell_exec函数

通过shell执行命令并以字符串形式返回完整的输出。

它从执行的命令返回输出,如果发生错误或命令不产生任何输出,则返回NULL。

<?php 

$command = escapeshellcmd('/usr/custom/test.py');
$output = shell_exec($command);
echo $output;

?>

在Python文件中test.py,在第一行中验证以下文本:(请参见shebang解释)

#!/usr/bin/env python

此外,Python文件必须具有正确的特权(如果PHP脚本在浏览器或curl中运行,则对用户www-data / apache的执行)和/或必须是“可执行的”。此外,所有进入.py文件的命令都必须具有正确的特权:

采取从PHP手册

对于那些试图在unix类型的平台上使用shell_exec并且似乎无法使其正常工作的人,请快速提醒一下。PHP以系统上的Web用户身份执行(对于Apache,通常为www),因此您需要确保Web用户对您在shell_exec命令中尝试使用的任何文件或目录具有权限。否则,它似乎什么也没做。

为了使在UNIX型平台上的可执行文件

chmod +x myscript.py

Tested on Ubuntu Server 10.04. I hope it helps you also on Arch Linux.

In PHP use shell_exec function:

Execute command via shell and return the complete output as a string.

It returns the output from the executed command or NULL if an error occurred or the command produces no output.

<?php 

$command = escapeshellcmd('/usr/custom/test.py');
$output = shell_exec($command);
echo $output;

?>

In Python file test.py, verify this text in first line: (see shebang explain):

#!/usr/bin/env python

Also Python file must have correct privileges (execution for user www-data / apache if PHP script runs in browser or curl) and/or must be “executable”. Also all commands into .py file must have correct privileges:

Taken from php manual:

Just a quick reminder for those trying to use shell_exec on a unix-type platform and can’t seem to get it to work. PHP executes as the web user on the system (generally www for Apache), so you need to make sure that the web user has rights to whatever files or directories that you are trying to use in the shell_exec command. Other wise, it won’t appear to be doing anything.

To make executable a file on unix-type platforms:

chmod +x myscript.py

回答 1

我建议passthru直接使用和处理输出缓冲区:

ob_start();
passthru('/usr/bin/python2.7 /srv/http/assets/py/switch.py arg1 arg2');
$output = ob_get_clean(); 

I recommend using passthru and handling the output buffer directly:

ob_start();
passthru('/usr/bin/python2.7 /srv/http/assets/py/switch.py arg1 arg2');
$output = ob_get_clean(); 

回答 2

如果您想知道命令的返回状态并获取整个stdout输出,则可以实际使用exec

$command = 'ls';
exec($command, $out, $status);

$out是所有行的数组。$status是退货状态。对于调试非常有用。

如果您还想查看stderr输出,则可以使用proc_open或直接将其添加2>&1到中$command。后者通常足以使事情正常运行,并更快地实现。

If you want to know the return status of the command and get the entire stdout output you can actually use exec:

$command = 'ls';
exec($command, $out, $status);

$out is an array of all lines. $status is the return status. Very useful for debugging.

If you also want to see the stderr output you can either play with proc_open or simply add 2>&1 to your $command. The latter is often sufficient to get things working and way faster to “implement”.


回答 3

亚历杭德罗(Alejandro)钉上了钉子,为异常(Ubuntu或Debian)添加了说明-我没有代表要添加到答案本身:

sudoers文件: sudo visudo

添加了异常: www-data ALL=(ALL) NOPASSWD: ALL

Alejandro nailed it, adding clarification to the exception (Ubuntu or Debian) – I don’t have the rep to add to the answer itself:

sudoers file: sudo visudo

exception added: www-data ALL=(ALL) NOPASSWD: ALL


回答 4

根据情况明确使用哪个命令

exec() -执行外部程序

system() -执行外部程序并显示输出

passthru() -执行外部程序并显示原始输出

资料来源:http : //php.net/manual/en/function.exec.php

To clarify which command to use based on the situation

exec() – Execute an external program

system() – Execute an external program and display the output

passthru() – Execute an external program and display raw output

Source: http://php.net/manual/en/function.exec.php


回答 5

就我而言,我需要在www名为的目录中创建一个新文件夹scripts。在其中scripts添加了一个名为的新文件test.py

然后sudo chown www-data:root scripts,我使用和sudo chown www-data:root test.py

然后我转到新scripts目录并使用sudo chmod +x test.py

我的test.py文件看起来像这样。请注意不同的Python版本:

#!/usr/bin/env python3.5
print("Hello World!")

从PHP,我现在这样做:

$message = exec("/var/www/scripts/test.py 2>&1");
print_r($message);

您应该看到:Hello World!

In my case I needed to create a new folder in the www directory called scripts. Within scripts I added a new file called test.py.

I then used sudo chown www-data:root scripts and sudo chown www-data:root test.py.

Then I went to the new scripts directory and used sudo chmod +x test.py.

My test.py file it looks like this. Note the different Python version:

#!/usr/bin/env python3.5
print("Hello World!")

From php I now do this:

$message = exec("/var/www/scripts/test.py 2>&1");
print_r($message);

And you should see: Hello World!


回答 6

上述方法似乎很复杂。使用我的方法作为参考。

我有两个文件:

  • 运行.php

  • mkdir.py

在这里,我创建了一个包含GO按钮的HTML页面。每当您按下此按钮时,都会在您提到的路径中的目录中创建一个新文件夹。

运行.php

<html>
 <body>
  <head>
   <title>
     run
   </title>
  </head>

   <form method="post">

    <input type="submit" value="GO" name="GO">
   </form>
 </body>
</html>

<?php
	if(isset($_POST['GO']))
	{
		shell_exec("python /var/www/html/lab/mkdir.py");
		echo"success";
	}
?>

mkdir.py

#!/usr/bin/env python    
import os    
os.makedirs("thisfolder");

The above methods seem to be complex. Use my method as a reference.

I have these two files:

  • run.php

  • mkdir.py

Here, I’ve created an HTML page which contains a GO button. Whenever you press this button a new folder will be created in directory whose path you have mentioned.

run.php

<html>
 <body>
  <head>
   <title>
     run
   </title>
  </head>

   <form method="post">

    <input type="submit" value="GO" name="GO">
   </form>
 </body>
</html>

<?php
	if(isset($_POST['GO']))
	{
		shell_exec("python /var/www/html/lab/mkdir.py");
		echo"success";
	}
?>

mkdir.py

#!/usr/bin/env python    
import os    
os.makedirs("thisfolder");

回答 7

这很琐碎,但只是想帮助已经遵循亚历杭德罗建议但遇到此错误的任何人:

sh:blabla.py:找不到命令

如果有人遇到该错误,那么Alejandro需要对php文件进行一些更改:

$command = escapeshellcmd('python blabla.py');

This is so trivial, but just wanted to help anyone who already followed along Alejandro’s suggestion but encountered this error:

sh: blabla.py: command not found

If anyone encountered that error, then a little change needs to be made to the php file by Alejandro:

$command = escapeshellcmd('python blabla.py');

Python请求包:处理xml响应

问题:Python请求包:处理xml响应

我非常喜欢该requests程序包及其舒适的方式来处理JSON响应。

不幸的是,我不知道是否还可以处理XML响应。有没有人体验过如何使用该requests包处理XML响应?是否需要包括另一个用于XML解码的包?

I like very much the requests package and its comfortable way to handle JSON responses.

Unfortunately, I did not understand if I can also process XML responses. Has anybody experience how to handle XML responses with the requests package? Is it necessary to include another package for the XML decoding?


回答 0

requests不处理解析XML响应,否。XML响应本质上比JSON响应复杂得多,如何将XML数据序列化为Python结构几乎不那么简单。

Python带有内置的XML解析器。我建议您使用ElementTree API

import requests
from xml.etree import ElementTree

response = requests.get(url)

tree = ElementTree.fromstring(response.content)

或者,如果响应特别大,请使用增量方法:

response = requests.get(url, stream=True)
# if the server sent a Gzip or Deflate compressed response, decompress
# as we read the raw stream:
response.raw.decode_content = True

events = ElementTree.iterparse(response.raw)
for event, elem in events:
    # do something with `elem`

外部lxml项目建立在相同的API上,仍然为您提供更多功能和强大功能。

requests does not handle parsing XML responses, no. XML responses are much more complex in nature than JSON responses, how you’d serialize XML data into Python structures is not nearly as straightforward.

Python comes with built-in XML parsers. I recommend you use the ElementTree API:

import requests
from xml.etree import ElementTree

response = requests.get(url)

tree = ElementTree.fromstring(response.content)

or, if the response is particularly large, use an incremental approach:

    response = requests.get(url, stream=True)
    # if the server sent a Gzip or Deflate compressed response, decompress
    # as we read the raw stream:
    response.raw.decode_content = True

    events = ElementTree.iterparse(response.raw)
    for event, elem in events:
        # do something with `elem`

The external lxml project builds on the same API to give you more features and power still.


“失火” Python异步/等待

问题:“失火” Python异步/等待

有时需要执行一些非关键性的异步操作,但我不想等待它完成。在Tornado的协程实现中,您可以通过简单地省略yield关键字来“触发并忘记”一个异步函数。

我一直在尝试找出如何使用Python 3.5中发布的新语法async/ 来“激发并忘记” await。例如,一个简化的代码片段:

async def async_foo():
    print("Do some stuff asynchronously here...")

def bar():
    async_foo()  # fire and forget "async_foo()"

bar()

但是,发生的事情是bar()永远不会执行,而是收到运行时警告:

RuntimeWarning: coroutine 'async_foo' was never awaited
  async_foo()  # fire and forget "async_foo()"

Sometimes there is some non-critical asynchronous operation that needs to happen but I don’t want to wait for it to complete. In Tornado’s coroutine implementation you can “fire & forget” an asynchronous function by simply ommitting the yield key-word.

I’ve been trying to figure out how to “fire & forget” with the new async/await syntax released in Python 3.5. E.g., a simplified code snippet:

async def async_foo():
    print("Do some stuff asynchronously here...")

def bar():
    async_foo()  # fire and forget "async_foo()"

bar()

What happens though is that bar() never executes and instead we get a runtime warning:

RuntimeWarning: coroutine 'async_foo' was never awaited
  async_foo()  # fire and forget "async_foo()"

回答 0

更新:

如果您使用的是Python> = 3.7,请在任何地方替换asyncio.ensure_futureasyncio.create_task最新的,更好的派生task的方法


asyncio。“解雇”的任务

根据python docs的asyncio.Task说法,有可能启动一些协程以“在后台”执行asyncio.ensure_future 函数创建的任务不会阻止执行(因此函数将立即返回!)。这似乎是您要求的一种“解雇”的方法。

import asyncio


async def async_foo():
    print("async_foo started")
    await asyncio.sleep(1)
    print("async_foo done")


async def main():
    asyncio.ensure_future(async_foo())  # fire and forget async_foo()

    # btw, you can also create tasks inside non-async funcs

    print('Do some actions 1')
    await asyncio.sleep(1)
    print('Do some actions 2')
    await asyncio.sleep(1)
    print('Do some actions 3')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

输出:

Do some actions 1
async_foo started
Do some actions 2
async_foo done
Do some actions 3

如果事件循环完成后正在执行任务怎么办?

请注意,asyncio期望任务将在事件循环完成时完成。因此,如果您更改main()为:

async def main():
    asyncio.ensure_future(async_foo())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(0.1)
    print('Do some actions 2')

程序完成后,您会收到以下警告:

Task was destroyed but it is pending!
task: <Task pending coro=<async_foo() running at [...]

为防止这种情况,您可以在事件循环完成后等待所有待处理的任务

async def main():
    asyncio.ensure_future(async_foo())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(0.1)
    print('Do some actions 2')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

    # Let's also finish all running tasks:
    pending = asyncio.Task.all_tasks()
    loop.run_until_complete(asyncio.gather(*pending))

杀死任务而不是等待任务

有时,您不想等待任务完成(例如,某些任务可能创建为永久运行)。在这种情况下,您可以只取消()而不是等待它们:

import asyncio
from contextlib import suppress


async def echo_forever():
    while True:
        print("echo")
        await asyncio.sleep(1)


async def main():
    asyncio.ensure_future(echo_forever())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(1)
    print('Do some actions 2')
    await asyncio.sleep(1)
    print('Do some actions 3')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

    # Let's also cancel all running tasks:
    pending = asyncio.Task.all_tasks()
    for task in pending:
        task.cancel()
        # Now we should await task to execute it's cancellation.
        # Cancelled task raises asyncio.CancelledError that we can suppress:
        with suppress(asyncio.CancelledError):
            loop.run_until_complete(task)

输出:

Do some actions 1
echo
Do some actions 2
echo
Do some actions 3
echo

Upd:

Replace asyncio.ensure_future with asyncio.create_task everywhere if you’re using Python >= 3.7 It’s newer, nicer way to spawn task.


asyncio.Task to “fire and forget”

According to python docs for asyncio.Task it is possible to start some coroutine to execute “in background”. The task created by asyncio.ensure_future function won’t block the execution (therefore the function will return immediately!). This looks like a way to “fire and forget” as you requested.

import asyncio


async def async_foo():
    print("async_foo started")
    await asyncio.sleep(1)
    print("async_foo done")


async def main():
    asyncio.ensure_future(async_foo())  # fire and forget async_foo()

    # btw, you can also create tasks inside non-async funcs

    print('Do some actions 1')
    await asyncio.sleep(1)
    print('Do some actions 2')
    await asyncio.sleep(1)
    print('Do some actions 3')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Output:

Do some actions 1
async_foo started
Do some actions 2
async_foo done
Do some actions 3

What if tasks are executing after event loop complete?

Note that asyncio expects task would be completed at the moment event loop completed. So if you’ll change main() to:

async def main():
    asyncio.ensure_future(async_foo())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(0.1)
    print('Do some actions 2')

You’ll get this warning after the program finished:

Task was destroyed but it is pending!
task: <Task pending coro=<async_foo() running at [...]

To prevent that you can just await all pending tasks after event loop completed:

async def main():
    asyncio.ensure_future(async_foo())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(0.1)
    print('Do some actions 2')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

    # Let's also finish all running tasks:
    pending = asyncio.Task.all_tasks()
    loop.run_until_complete(asyncio.gather(*pending))

Kill tasks instead of awaiting them

Sometimes you don’t want to await tasks to be done (for example, some tasks may be created to run forever). In that case, you can just cancel() them instead of awaiting them:

import asyncio
from contextlib import suppress


async def echo_forever():
    while True:
        print("echo")
        await asyncio.sleep(1)


async def main():
    asyncio.ensure_future(echo_forever())  # fire and forget

    print('Do some actions 1')
    await asyncio.sleep(1)
    print('Do some actions 2')
    await asyncio.sleep(1)
    print('Do some actions 3')


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

    # Let's also cancel all running tasks:
    pending = asyncio.Task.all_tasks()
    for task in pending:
        task.cancel()
        # Now we should await task to execute it's cancellation.
        # Cancelled task raises asyncio.CancelledError that we can suppress:
        with suppress(asyncio.CancelledError):
            loop.run_until_complete(task)

Output:

Do some actions 1
echo
Do some actions 2
echo
Do some actions 3
echo

回答 1

谢谢谢尔盖的简洁回答。这是相同的装饰版。

import asyncio
import time

def fire_and_forget(f):
    def wrapped(*args, **kwargs):
        return asyncio.get_event_loop().run_in_executor(None, f, *args, *kwargs)

    return wrapped

@fire_and_forget
def foo():
    time.sleep(1)
    print("foo() completed")

print("Hello")
foo()
print("I didn't wait for foo()")

产生

>>> Hello
>>> foo() started
>>> I didn't wait for foo()
>>> foo() completed

注意:检查我的其他答案,使用普通线程也可以做到这一点。

Thank you Sergey for the succint answer. Here is the decorated version of the same.

import asyncio
import time

def fire_and_forget(f):
    def wrapped(*args, **kwargs):
        return asyncio.get_event_loop().run_in_executor(None, f, *args, *kwargs)

    return wrapped

@fire_and_forget
def foo():
    time.sleep(1)
    print("foo() completed")

print("Hello")
foo()
print("I didn't wait for foo()")

Produces

>>> Hello
>>> foo() started
>>> I didn't wait for foo()
>>> foo() completed

Note: Check my other answer which does the same using plain threads.


回答 2

这不是完全异步执行,但也许run_in_executor()适合您。

def fire_and_forget(task, *args, **kwargs):
    loop = asyncio.get_event_loop()
    if callable(task):
        return loop.run_in_executor(None, task, *args, **kwargs)
    else:    
        raise TypeError('Task must be a callable')

def foo():
    #asynchronous stuff here


fire_and_forget(foo)

This is not entirely asynchronous execution, but maybe run_in_executor() is suitable for you.

def fire_and_forget(task, *args, **kwargs):
    loop = asyncio.get_event_loop()
    if callable(task):
        return loop.run_in_executor(None, task, *args, **kwargs)
    else:    
        raise TypeError('Task must be a callable')

def foo():
    #asynchronous stuff here


fire_and_forget(foo)

回答 3

由于某种原因,如果您无法使用,asyncio那么这里是使用普通线程的实现。检查我的其他答案和谢尔盖的答案。

import threading

def fire_and_forget(f):
    def wrapped():
        threading.Thread(target=f).start()

    return wrapped

@fire_and_forget
def foo():
    time.sleep(1)
    print("foo() completed")

print("Hello")
foo()
print("I didn't wait for foo()")

For some reason if you are unable to use asyncio then here is the implementation using plain threads. Check my other answers and Sergey’s answer too.

import threading

def fire_and_forget(f):
    def wrapped():
        threading.Thread(target=f).start()

    return wrapped

@fire_and_forget
def foo():
    time.sleep(1)
    print("foo() completed")

print("Hello")
foo()
print("I didn't wait for foo()")

以Object为参数的类

问题:以Object为参数的类

我正在尝试将一些python代码转换为scala代码。所以我对Python完全陌生。

但是,为什么有些类将对象作为参数却从未明确使用它呢?首先将其作为参数的原因是什么?

例:

class Table(object)

感谢您的时间。

I’m trying to translate some python code to scala code. So I’m a total noob in Python.

But why do some classes have object as a parameter but never explicitly use it? What’s the reasoning for having it as a parameter in the first place?

Example:

class Table(object)

Thank you for your time.


回答 0

在Python2中,此方法声明Table为一种新型类(与“经典”类相对)。在Python3中,所有类都是新型类,因此不再需要。

新样式类具有一些经典类所缺少的特殊属性。

class Classic: pass
class NewStyle(object): pass

print(dir(Classic))
# ['__doc__', '__module__']

print(dir(NewStyle))
# ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

此外,属性超级不适用于经典类。

在Python2中,最好将所有类都设为新样式。(尽管为了向后兼容,标准库中的许多类仍然是经典类。)

通常,在诸如

class Foo(Base1, Base2):

Foo被声明为从基类Base1和继承的类Base2

object是Python所有类的母亲。它是一种新型的类,从这么继承object品牌Table新风格的类。

In Python2 this declares Table to be a new-style class (as opposed to “classic” class). In Python3 all classes are new-style classes, so this is no longer necessary.

New style classes have a few special attributes that classic classes lack.

class Classic: pass
class NewStyle(object): pass

print(dir(Classic))
# ['__doc__', '__module__']

print(dir(NewStyle))
# ['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']

Also, properties and super do not work with classic classes.

In Python2 it is a good idea to make all classes new-style classes. (Though a lot of classes in the standard library are still classic classes, for the sake of backward-compatibility.)

In general, in a statement such as

class Foo(Base1, Base2):

Foo is being declared as a class inheriting from base classes Base1 and Base2.

object is the mother of all classes in Python. It is a new-style class, so inheriting from object makes Table a new-style class.


回答 1

Table类扩展了名为的类object。这不是参数。您可能要object显式扩展的原因是,它将类变成了新样式。如果您未明确指定它扩展object,直到Python 3,它将默认为旧类。(从Python 3开始,无论您是否显式扩展,所有类都是新样式object。)

有关新式和旧式类的更多信息,请参见此问题

The Table class is extending a class called object. It’s not an argument. The reason you may want to extend object explicitly is it turns the class into a new-style class. If you don’t explicitly specify it extends object, until Python 3, it will default to being an old-style class. (Since Python 3, all classes are new-style, whether you explicitly extend object or not.)

For more information on new-style and old-style classes, please see this question.


回答 2

请注意,“新样式”与“旧样式”类的区别特定于Python 2.x;在3.x中,所有类都是“新样式”。

Just a note that the “new-style” vs “old-style” class distinction is specific to Python 2.x; in 3.x, all classes are “new-style”.


回答 3

对于Python,类Table和类Table(object)相同。

它不是参数,而是从对象(与许多其他语言一样为基类)扩展的。

它只说它继承了“对象”中定义的任何内容。这是默认行为。

class Table and class Table(object) are no different for Python.

It’s not a parameter, its extending from object (which is base Class like many other languages).

All it says is that it inherits whatever is defined in “object”. This is the default behaviour.


回答 4

object是python中定义的类对象的最基本类型。对象的属性如下所示

** >>>目录(对象)

[‘ class ‘,’ delattr ‘,’ doc ‘,’ format ‘,’ getattribute ‘,’ hash ‘,’ init ‘,’ new ‘,’ reduce ‘,’ reduce_ex ‘,’ repr ‘,’ setattr ‘,’ sizeof ‘,’ str ‘,’ subclasshook ‘] **

所以Table(object)只是继承。

object is the most base type of class object defined in python. Attributes of object can be seen as below

**>>> dir(object)

[‘class‘, ‘delattr‘, ‘doc‘, ‘format‘, ‘getattribute‘, ‘hash‘, ‘init‘, ‘new‘, ‘reduce‘, ‘reduce_ex‘, ‘repr‘, ‘setattr‘, ‘sizeof‘, ‘str‘, ‘subclasshook‘]**

So Table(object) is just inheritance.!


回答 5

1)类名(对象):2)类名:它们都是相同的,但第一个类在编写方面相当好,在继承其他类到另一个类时看起来更好,看起来很同质。

一样吗?因此,Python中的所有事物都属于对象,这意味着Python中的所有事物都具有对象的属性,无论是否写入,它都会理解它。首先,我们明确地告诉第二个我们没有。

1)Class name (object): 2) class name: They both are same but first one quite better in terms of writing,it look better while inheriting other classes to another,i t looks homogeneous.

How same ? So,every thing in Python is come under object means every thing in Python has property of object,if write or don’t it will understand it. In first we explicitly tell it in second we didn’t.


解析日期字符串并更改格式

问题:解析日期字符串并更改格式

我有一个日期字符串,格式为“ 2010年2月15日星期一”。我想将格式更改为“ 15/02/2010”。我怎样才能做到这一点?

I have a date string with the format ‘Mon Feb 15 2010′. I want to change the format to ’15/02/2010’. How can I do this?


回答 0

datetime 模块可以帮助您:

datetime.datetime.strptime(date_string, format1).strftime(format2)

对于特定示例,您可以执行

>>> datetime.datetime.strptime('Mon Feb 15 2010', '%a %b %d %Y').strftime('%d/%m/%Y')
'15/02/2010'
>>>

datetime module could help you with that:

datetime.datetime.strptime(date_string, format1).strftime(format2)

For the specific example you could do

>>> datetime.datetime.strptime('Mon Feb 15 2010', '%a %b %d %Y').strftime('%d/%m/%Y')
'15/02/2010'
>>>

回答 1

您可以安装dateutil库。它的parse功能可以弄清楚字符串的格式,而不必像使用那样指定格式datetime.strptime

from dateutil.parser import parse
dt = parse('Mon Feb 15 2010')
print(dt)
# datetime.datetime(2010, 2, 15, 0, 0)
print(dt.strftime('%d/%m/%Y'))
# 15/02/2010

You can install the dateutil library. Its parse function can figure out what format a string is in without having to specify the format like you do with datetime.strptime.

from dateutil.parser import parse
dt = parse('Mon Feb 15 2010')
print(dt)
# datetime.datetime(2010, 2, 15, 0, 0)
print(dt.strftime('%d/%m/%Y'))
# 15/02/2010

回答 2

>>> from_date="Mon Feb 15 2010"
>>> import time                
>>> conv=time.strptime(from_date,"%a %b %d %Y")
>>> time.strftime("%d/%m/%Y",conv)
'15/02/2010'
>>> from_date="Mon Feb 15 2010"
>>> import time                
>>> conv=time.strptime(from_date,"%a %b %d %Y")
>>> time.strftime("%d/%m/%Y",conv)
'15/02/2010'

回答 3

将字符串转换为日期时间对象

from datetime import datetime
s = "2016-03-26T09:25:55.000Z"
f = "%Y-%m-%dT%H:%M:%S.%fZ"
out = datetime.strptime(s, f)
print(out)
output:
2016-03-26 09:25:55

convert string to datetime object

from datetime import datetime
s = "2016-03-26T09:25:55.000Z"
f = "%Y-%m-%dT%H:%M:%S.%fZ"
out = datetime.strptime(s, f)
print(out)
output:
2016-03-26 09:25:55

回答 4

由于这个问题经常出现,因此这里是简单的解释。

datetimetime模块具有两个重要功能。

  • strftime-从日期时间或时间对象创建日期或时间的字符串表示形式。
  • strptime-从字符串创建日期时间或时间对象。

在这两种情况下,我们都需要一个格式字符串。它是表示如何在字符串中格式化日期或时间的表示形式。

现在假设我们有一个日期对象。

>>> from datetime import datetime
>>> d = datetime(2010, 2, 15)
>>> d
datetime.datetime(2010, 2, 15, 0, 0)

如果要从该日期开始以以下格式创建字符串 'Mon Feb 15 2010'

>>> s = d.strftime('%a %b %d %y')
>>> print s
Mon Feb 15 10

假设我们想s再次将其转换为datetime对象。

>>> new_date = datetime.strptime(s, '%a %b %d %y')
>>> print new_date
2010-02-15 00:00:00

请参阅文档中有关日期时间的所有格式指令。

As this question comes often, here is the simple explanation.

datetime or time module has two important functions.

  • strftime – creates a string representation of date or time from a datetime or time object.
  • strptime – creates a datetime or time object from a string.

In both cases, we need a formating string. It is the representation that tells how the date or time is formatted in your string.

Now lets assume we have a date object.

>>> from datetime import datetime
>>> d = datetime(2010, 2, 15)
>>> d
datetime.datetime(2010, 2, 15, 0, 0)

If we want to create a string from this date in the format 'Mon Feb 15 2010'

>>> s = d.strftime('%a %b %d %y')
>>> print s
Mon Feb 15 10

Lets assume we want to convert this s again to a datetime object.

>>> new_date = datetime.strptime(s, '%a %b %d %y')
>>> print new_date
2010-02-15 00:00:00

Refer This document all formatting directives regarding datetime.


回答 5

仅出于完成的目的:使用解析日期时,strptime()并且该日期包含日,月等的名称时,请注意,您必须考虑语言环境。

文档中也将其作为脚注提及。

举个例子:

import locale
print(locale.getlocale())

>> ('nl_BE', 'ISO8859-1')

from datetime import datetime
datetime.strptime('6-Mar-2016', '%d-%b-%Y').strftime('%Y-%m-%d')

>> ValueError: time data '6-Mar-2016' does not match format '%d-%b-%Y'

locale.setlocale(locale.LC_ALL, 'en_US')
datetime.strptime('6-Mar-2016', '%d-%b-%Y').strftime('%Y-%m-%d')

>> '2016-03-06'

Just for the sake of completion: when parsing a date using strptime() and the date contains the name of a day, month, etc, be aware that you have to account for the locale.

It’s mentioned as a footnote in the docs as well.

As an example:

import locale
print(locale.getlocale())

>> ('nl_BE', 'ISO8859-1')

from datetime import datetime
datetime.strptime('6-Mar-2016', '%d-%b-%Y').strftime('%Y-%m-%d')

>> ValueError: time data '6-Mar-2016' does not match format '%d-%b-%Y'

locale.setlocale(locale.LC_ALL, 'en_US')
datetime.strptime('6-Mar-2016', '%d-%b-%Y').strftime('%Y-%m-%d')

>> '2016-03-06'

回答 6

@codeling和@ user1767754:以下两行将起作用。我没有看到有人针对所提出的示例问题发布完整的解决方案。希望这是足够的解释。

import datetime

x = datetime.datetime.strptime("Mon Feb 15 2010", "%a %b %d %Y").strftime("%d/%m/%Y")
print(x)

输出:

15/02/2010

@codeling and @user1767754 : The following two lines will work. I saw no one posted the complete solution for the example problem that was asked. Hopefully this is enough explanation.

import datetime

x = datetime.datetime.strptime("Mon Feb 15 2010", "%a %b %d %Y").strftime("%d/%m/%Y")
print(x)

Output:

15/02/2010

回答 7

您也可以使用熊猫来实现:

import pandas as pd

pd.to_datetime('Mon Feb 15 2010', format='%a %b %d %Y').strftime('%d/%m/%Y')

输出:

'15/02/2010'

您可以将pandas方法应用于不同的数据类型,例如:

import pandas as pd
import numpy as np

def reformat_date(date_string, old_format, new_format):
    return pd.to_datetime(date_string, format=old_format, errors='ignore').strftime(new_format)

date_string = 'Mon Feb 15 2010'
date_list = ['Mon Feb 15 2010', 'Wed Feb 17 2010']
date_array = np.array(date_list)
date_series = pd.Series(date_list)

old_format = '%a %b %d %Y'
new_format = '%d/%m/%Y'

print(reformat_date(date_string, old_format, new_format))
print(reformat_date(date_list, old_format, new_format).values)
print(reformat_date(date_array, old_format, new_format).values)
print(date_series.apply(lambda x: reformat_date(x, old_format, new_format)).values)

输出:

15/02/2010
['15/02/2010' '17/02/2010']
['15/02/2010' '17/02/2010']
['15/02/2010' '17/02/2010']

You may achieve this using pandas as well:

import pandas as pd

pd.to_datetime('Mon Feb 15 2010', format='%a %b %d %Y').strftime('%d/%m/%Y')

Output:

'15/02/2010'

You may apply pandas approach for different datatypes as:

import pandas as pd
import numpy as np

def reformat_date(date_string, old_format, new_format):
    return pd.to_datetime(date_string, format=old_format, errors='ignore').strftime(new_format)

date_string = 'Mon Feb 15 2010'
date_list = ['Mon Feb 15 2010', 'Wed Feb 17 2010']
date_array = np.array(date_list)
date_series = pd.Series(date_list)

old_format = '%a %b %d %Y'
new_format = '%d/%m/%Y'

print(reformat_date(date_string, old_format, new_format))
print(reformat_date(date_list, old_format, new_format).values)
print(reformat_date(date_array, old_format, new_format).values)
print(date_series.apply(lambda x: reformat_date(x, old_format, new_format)).values)

Output:

15/02/2010
['15/02/2010' '17/02/2010']
['15/02/2010' '17/02/2010']
['15/02/2010' '17/02/2010']

回答 8

使用datetime库 http://docs.python.org/library/datetime.html查找9.1.7。especiall strptime()strftime()行为¶示例 http://pleac.sourceforge.net/pleac_python/datesandtimes.html

use datetime library http://docs.python.org/library/datetime.html look up 9.1.7. especiall strptime() strftime() Behavior¶ examples http://pleac.sourceforge.net/pleac_python/datesandtimes.html


将参数传递给灯具功能

问题:将参数传递给灯具功能

我正在使用py.test来测试包装在python类MyTester中的某些DLL代码。为了进行验证,我需要在测试期间记录一些测试数据,然后再进行更多处理。由于我有许多test _…文件,因此我想对大多数测试重用测试器对象的创建(例如MyTester的实例)。

由于tester对象是获得DLL变量和函数的引用的对象,因此我需要将DLL变量列表传递给每个测试文件的tester对象(要记录的变量对于test_是相同的。 。文件)。列表的内容将用于记录指定的数据。

我的想法是像这样做:

import pytest

class MyTester():
    def __init__(self, arg = ["var0", "var1"]):
        self.arg = arg
        # self.use_arg_to_init_logging_part()

    def dothis(self):
        print "this"

    def dothat(self):
        print "that"

# located in conftest.py (because other test will reuse it)

@pytest.fixture()
def tester(request):
    """ create tester object """
    # how to use the list below for arg?
    _tester = MyTester()
    return _tester

# located in test_...py

# @pytest.mark.usefixtures("tester") 
class TestIt():

    # def __init__(self):
    #     self.args_for_tester = ["var1", "var2"]
    #     # how to pass this list to the tester fixture?

    def test_tc1(self, tester):
       tester.dothis()
       assert 0 # for demo purpose

    def test_tc2(self, tester):
       tester.dothat()
       assert 0 # for demo purpose

是否有可能实现这种目标,或者还有更优雅的方式?

通常,我可以使用某种设置功能(xUnit样式)针对每种测试方法执行此操作。但是我想获得某种重用。有谁知道灯具是否可以做到这一点?

我知道我可以做这样的事情:(来自文档)

@pytest.fixture(scope="module", params=["merlinux.eu", "mail.python.org"])

但是我需要直接在测试模块中进行参数化。 是否可以从测试模块访问灯具的params属性?

I am using py.test to test some DLL code wrapped in a python class MyTester. For validating purpose I need to log some test data during the tests and do more processing afterwards. As I have many test_… files I want to reuse the tester object creation (instance of MyTester) for most of my tests.

As the tester object is the one which got the references to the DLL’s variables and functions I need to pass a list of the DLL’s variables to the tester object for each of the test files (variables to be logged are the same for a test_… file). The content of the list is shall be used to log the specified data.

My idea is to do it somehow like this:

import pytest

class MyTester():
    def __init__(self, arg = ["var0", "var1"]):
        self.arg = arg
        # self.use_arg_to_init_logging_part()

    def dothis(self):
        print "this"

    def dothat(self):
        print "that"

# located in conftest.py (because other test will reuse it)

@pytest.fixture()
def tester(request):
    """ create tester object """
    # how to use the list below for arg?
    _tester = MyTester()
    return _tester

# located in test_...py

# @pytest.mark.usefixtures("tester") 
class TestIt():

    # def __init__(self):
    #     self.args_for_tester = ["var1", "var2"]
    #     # how to pass this list to the tester fixture?

    def test_tc1(self, tester):
       tester.dothis()
       assert 0 # for demo purpose

    def test_tc2(self, tester):
       tester.dothat()
       assert 0 # for demo purpose

Is it possible to achieve it like this or is there even a more elegant way?

Usually I could do it for each test method with some kind of setup function (xUnit-style). But I want to gain some kind of reuse. Does anyone know if this is possible with fixtures at all?

I know I can do something like this: (from the docs)

@pytest.fixture(scope="module", params=["merlinux.eu", "mail.python.org"])

But I need to the parametrization directly in the test module. Is it possible to access the params attribute of the fixture from the test module?


回答 0

更新:由于这是该问题的公认答案,并且有时仍然会被反对,因此我应该添加一个更新。尽管我的原始答案(如下)是在较旧版本的pytest中执行此操作的唯一方法,因为其他人已经指出 pytest现在支持对灯具进行间接参数化。例如,您可以执行以下操作(通过@imiric):

# test_parameterized_fixture.py
import pytest

class MyTester:
    def __init__(self, x):
        self.x = x

    def dothis(self):
        assert self.x

@pytest.fixture
def tester(request):
    """Create tester object"""
    return MyTester(request.param)


class TestIt:
    @pytest.mark.parametrize('tester', [True, False], indirect=['tester'])
    def test_tc1(self, tester):
       tester.dothis()
       assert 1
$ pytest -v test_parameterized_fixture.py
================================================================================= test session starts =================================================================================
platform cygwin -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: .
collected 2 items

test_parameterized_fixture.py::TestIt::test_tc1[True] PASSED                                                                                                                    [ 50%]
test_parameterized_fixture.py::TestIt::test_tc1[False] FAILED

但是,尽管这种形式的间接参数化是明确的,但正如@Yukihiko Shinoda 指出的那样,它现在支持一种形式的隐式间接参数化(尽管我在官方文档中找不到对此的任何明显引用):

# test_parameterized_fixture2.py
import pytest

class MyTester:
    def __init__(self, x):
        self.x = x

    def dothis(self):
        assert self.x

@pytest.fixture
def tester(tester_arg):
    """Create tester object"""
    return MyTester(tester_arg)


class TestIt:
    @pytest.mark.parametrize('tester_arg', [True, False])
    def test_tc1(self, tester):
       tester.dothis()
       assert 1
$ pytest -v test_parameterized_fixture2.py
================================================================================= test session starts =================================================================================
platform cygwin -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: .
collected 2 items

test_parameterized_fixture2.py::TestIt::test_tc1[True] PASSED                                                                                                                   [ 50%]
test_parameterized_fixture2.py::TestIt::test_tc1[False] FAILED

我不确切知道这种形式的语义是什么,但是似乎可以pytest.mark.parametrize识别出,尽管该test_tc1方法不接受名为的参数,但它使用tester_argtester夹具却可以,因此它通过tester夹具传递参数化的参数。


我有一个类似的问题-我有一个称为的夹具test_package,后来我希望能够在特定测试中运行该夹具时将可选参数传递给该夹具。例如:

@pytest.fixture()
def test_package(request, version='1.0'):
    ...
    request.addfinalizer(fin)
    ...
    return package

(对于这些目的,夹具是做什么的或返回的对象的类型无关紧要package)。

然后希望以某种方式在测试功能中使用此固定装置,这样我也可以指定该version固定装置的参数以用于该测试。尽管这可能是一个不错的功能,但目前尚不可能。

同时,很容易使我的夹具简单地返回一个函数,该函数完成夹具先前所做的所有工作,但允许我指定version参数:

@pytest.fixture()
def test_package(request):
    def make_test_package(version='1.0'):
        ...
        request.addfinalizer(fin)
        ...
        return test_package

    return make_test_package

现在,我可以在测试函数中使用它,例如:

def test_install_package(test_package):
    package = test_package(version='1.1')
    ...
    assert ...

等等。

OP的尝试解决方案朝着正确的方向发展,正如@ hpk42的答案所暗示的那样,MyTester.__init__可以仅存储对请求的引用,例如:

class MyTester(object):
    def __init__(self, request, arg=["var0", "var1"]):
        self.request = request
        self.arg = arg
        # self.use_arg_to_init_logging_part()

    def dothis(self):
        print "this"

    def dothat(self):
        print "that"

然后使用它来实现固定装置,例如:

@pytest.fixture()
def tester(request):
    """ create tester object """
    # how to use the list below for arg?
    _tester = MyTester(request)
    return _tester

如果需要,MyTester可以对类进行一些重构,以便.args在创建其属性后可以对其进行更新,以调整各个测试的行为。

Update: Since this the accepted answer to this question and still gets upvoted sometimes, I should add an update. Although my original answer (below) was the only way to do this in older versions of pytest as others have noted pytest now supports indirect parametrization of fixtures. For example you can do something like this (via @imiric):

# test_parameterized_fixture.py
import pytest

class MyTester:
    def __init__(self, x):
        self.x = x

    def dothis(self):
        assert self.x

@pytest.fixture
def tester(request):
    """Create tester object"""
    return MyTester(request.param)


class TestIt:
    @pytest.mark.parametrize('tester', [True, False], indirect=['tester'])
    def test_tc1(self, tester):
       tester.dothis()
       assert 1
$ pytest -v test_parameterized_fixture.py
================================================================================= test session starts =================================================================================
platform cygwin -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: .
collected 2 items

test_parameterized_fixture.py::TestIt::test_tc1[True] PASSED                                                                                                                    [ 50%]
test_parameterized_fixture.py::TestIt::test_tc1[False] FAILED

However, although this form of indirect parametrization is explicit, as @Yukihiko Shinoda points out it now supports a form of implicit indirect parametrization (though I couldn’t find any obvious reference to this in the official docs):

# test_parameterized_fixture2.py
import pytest

class MyTester:
    def __init__(self, x):
        self.x = x

    def dothis(self):
        assert self.x

@pytest.fixture
def tester(tester_arg):
    """Create tester object"""
    return MyTester(tester_arg)


class TestIt:
    @pytest.mark.parametrize('tester_arg', [True, False])
    def test_tc1(self, tester):
       tester.dothis()
       assert 1
$ pytest -v test_parameterized_fixture2.py
================================================================================= test session starts =================================================================================
platform cygwin -- Python 3.6.8, pytest-5.3.1, py-1.8.0, pluggy-0.13.1 -- /usr/bin/python3
cachedir: .pytest_cache
rootdir: .
collected 2 items

test_parameterized_fixture2.py::TestIt::test_tc1[True] PASSED                                                                                                                   [ 50%]
test_parameterized_fixture2.py::TestIt::test_tc1[False] FAILED

I don’t know exactly what are the semantics of this form, but it seems that pytest.mark.parametrize recognizes that although the test_tc1 method does not take an argument named tester_arg, the tester fixture that it’s using does, so it passes the parametrized argument on through the tester fixture.


I had a similar problem–I have a fixture called test_package, and I later wanted to be able to pass an optional argument to that fixture when running it in specific tests. For example:

@pytest.fixture()
def test_package(request, version='1.0'):
    ...
    request.addfinalizer(fin)
    ...
    return package

(It doesn’t matter for these purposes what the fixture does or what type of object the returned package) is.

It would then be desirable to somehow use this fixture in a test function in such a way that I can also specify the version argument to that fixture to use with that test. This is currently not possible, though might make a nice feature.

In the meantime it was easy enough to make my fixture simply return a function that does all the work the fixture previously did, but allows me to specify the version argument:

@pytest.fixture()
def test_package(request):
    def make_test_package(version='1.0'):
        ...
        request.addfinalizer(fin)
        ...
        return test_package

    return make_test_package

Now I can use this in my test function like:

def test_install_package(test_package):
    package = test_package(version='1.1')
    ...
    assert ...

and so on.

The OP’s attempted solution was headed in the right direction, and as @hpk42’s answer suggests, the MyTester.__init__ could just store off a reference to the request like:

class MyTester(object):
    def __init__(self, request, arg=["var0", "var1"]):
        self.request = request
        self.arg = arg
        # self.use_arg_to_init_logging_part()

    def dothis(self):
        print "this"

    def dothat(self):
        print "that"

Then use this to implement the fixture like:

@pytest.fixture()
def tester(request):
    """ create tester object """
    # how to use the list below for arg?
    _tester = MyTester(request)
    return _tester

If desired the MyTester class could be restructured a bit so that its .args attribute can be updated after it has been created, to tweak the behavior for individual tests.


回答 1

实际上,py.test中通过间接参数化本身支持此功能。

就您而言,您将:

@pytest.fixture
def tester(request):
    """Create tester object"""
    return MyTester(request.param)


class TestIt:
    @pytest.mark.parametrize('tester', [['var1', 'var2']], indirect=True)
    def test_tc1(self, tester):
       tester.dothis()
       assert 1

This is actually supported natively in py.test via indirect parametrization.

In your case, you would have:

@pytest.fixture
def tester(request):
    """Create tester object"""
    return MyTester(request.param)


class TestIt:
    @pytest.mark.parametrize('tester', [['var1', 'var2']], indirect=True)
    def test_tc1(self, tester):
       tester.dothis()
       assert 1

回答 2

您可以从Fixture函数(从而从Tester类)访问请求的模块/类/函数,请参见与来自Fixture函数的请求测试上下文进行交互。因此,您可以在类或模块上声明一些参数,然后测试仪固定装置即可进行拾取。

You can access the requesting module/class/function from fixture functions (and thus from your Tester class), see interacting with requesting test context from a fixture function. So you could declare some parameters on a class or module and the tester fixture can pick it up.


回答 3

我找不到任何文档,但是,它似乎可以在最新版本的pytest中使用。

@pytest.fixture
def tester(tester_arg):
    """Create tester object"""
    return MyTester(tester_arg)


class TestIt:
    @pytest.mark.parametrize('tester_arg', [['var1', 'var2']])
    def test_tc1(self, tester):
       tester.dothis()
       assert 1

I couldn’t find any document, however, it seems to work in latest version of pytest.

@pytest.fixture
def tester(tester_arg):
    """Create tester object"""
    return MyTester(tester_arg)


class TestIt:
    @pytest.mark.parametrize('tester_arg', [['var1', 'var2']])
    def test_tc1(self, tester):
       tester.dothis()
       assert 1

回答 4

改善imiric的答案:解决此问题的另一种优雅方法是创建“参数夹具”。我个人更喜欢它的indirect功能pytest。此功能可从中获得pytest_cases,最初的想法由Sup3rGeo提出。

import pytest
from pytest_cases import param_fixture

# create a single parameter fixture
var = param_fixture("var", [['var1', 'var2']], ids=str)

@pytest.fixture
def tester(var):
    """Create tester object"""
    return MyTester(var)

class TestIt:
    def test_tc1(self, tester):
       tester.dothis()
       assert 1

请注意,这pytest-cases@pytest_fixture_plus允许您在灯具上使用参数化标记,并@cases_data允许您从单独模块中的函数中获取参数。有关详细信息,请参见doc。我是作者;)

To improve a little bit imiric’s answer: another elegant way to solve this problem is to create “parameter fixtures”. I personally prefer it over the indirect feature of pytest. This feature is available from pytest_cases, and the original idea was suggested by Sup3rGeo.

import pytest
from pytest_cases import param_fixture

# create a single parameter fixture
var = param_fixture("var", [['var1', 'var2']], ids=str)

@pytest.fixture
def tester(var):
    """Create tester object"""
    return MyTester(var)

class TestIt:
    def test_tc1(self, tester):
       tester.dothis()
       assert 1

Note that pytest-cases also provides @fixture that allow you to use parametrization marks directly on your fixtures instead of having to use @pytest.fixture(params=...)

from pytest_cases import fixture, parametrize

@fixture
@parametrize("var", [['var1', 'var2']], ids=str)
def tester(var):
    """Create tester object"""
    return MyTester(var)

and @parametrize_with_cases that allows you to source your parameters from “case functions” that may be grouped in a class or even a separate module. See doc for details. I’m the author by the way ;)


回答 5

我做了一个有趣的装饰器,可以编写如下的灯具:

@fixture_taking_arguments
def dog(request, /, name, age=69):
    return f"{name} the dog aged {age}"

在这里,您的左侧/还有其他固定装置,在右边,您可以使用以下参数提供:

@dog.arguments("Buddy", age=7)
def test_with_dog(dog):
    assert dog == "Buddy the dog aged 7"

这与函数参数的工作方式相同。如果不提供age参数,则使用默认值69。如果您不提供name或省略dog.arguments装饰器,则会得到常规的TypeError: dog() missing 1 required positional argument: 'name'。如果您有另一个接受参数的固定装置name,那么它与此不会冲突。

还支持异步装置。

此外,这为您提供了一个不错的设置计划:

$ pytest test_dogs_and_owners.py --setup-plan

SETUP    F dog['Buddy', age=7]
...
SETUP    F dog['Champion']
SETUP    F owner (fixtures used: dog)['John Travolta']

一个完整的例子:

from plugin import fixture_taking_arguments

@fixture_taking_arguments
def dog(request, /, name, age=69):
    return f"{name} the dog aged {age}"


@fixture_taking_arguments
def owner(request, dog, /, name="John Doe"):
    yield f"{name}, owner of {dog}"


@dog.arguments("Buddy", age=7)
def test_with_dog(dog):
    assert dog == "Buddy the dog aged 7"


@dog.arguments("Champion")
class TestChampion:
    def test_with_dog(self, dog):
        assert dog == "Champion the dog aged 69"

    def test_with_default_owner(self, owner, dog):
        assert owner == "John Doe, owner of Champion the dog aged 69"
        assert dog == "Champion the dog aged 69"

    @owner.arguments("John Travolta")
    def test_with_named_owner(self, owner):
        assert owner == "John Travolta, owner of Champion the dog aged 69"

装饰器的代码:

import pytest
from dataclasses import dataclass
from functools import wraps
from inspect import signature, Parameter, isgeneratorfunction, iscoroutinefunction, isasyncgenfunction
from itertools import zip_longest, chain


_NOTHING = object()


def _omittable_parentheses_decorator(decorator):
    @wraps(decorator)
    def wrapper(*args, **kwargs):
        if not kwargs and len(args) == 1 and callable(args[0]):
            return decorator()(args[0])
        else:
            return decorator(*args, **kwargs)
    return wrapper


@dataclass
class _ArgsKwargs:
    args: ...
    kwargs: ...

    def __repr__(self):
        return ", ".join(chain(
               (repr(v) for v in self.args), 
               (f"{k}={v!r}" for k, v in self.kwargs.items())))


def _flatten_arguments(sig, args, kwargs):
    assert len(sig.parameters) == len(args) + len(kwargs)
    for name, arg in zip_longest(sig.parameters, args, fillvalue=_NOTHING):
        yield arg if arg is not _NOTHING else kwargs[name]


def _get_actual_args_kwargs(sig, args, kwargs):
    request = kwargs["request"]
    try:
        request_args, request_kwargs = request.param.args, request.param.kwargs
    except AttributeError:
        request_args, request_kwargs = (), {}
    return tuple(_flatten_arguments(sig, args, kwargs)) + request_args, request_kwargs


@_omittable_parentheses_decorator
def fixture_taking_arguments(*pytest_fixture_args, **pytest_fixture_kwargs):
    def decorator(func):
        original_signature = signature(func)

        def new_parameters():
            for param in original_signature.parameters.values():
                if param.kind == Parameter.POSITIONAL_ONLY:
                    yield param.replace(kind=Parameter.POSITIONAL_OR_KEYWORD)

        new_signature = original_signature.replace(parameters=list(new_parameters()))

        if "request" not in new_signature.parameters:
            raise AttributeError("Target function must have positional-only argument `request`")

        is_async_generator = isasyncgenfunction(func)
        is_async = is_async_generator or iscoroutinefunction(func)
        is_generator = isgeneratorfunction(func)

        if is_async:
            @wraps(func)
            async def wrapper(*args, **kwargs):
                args, kwargs = _get_actual_args_kwargs(new_signature, args, kwargs)
                if is_async_generator:
                    async for result in func(*args, **kwargs):
                        yield result
                else:
                    yield await func(*args, **kwargs)
        else:
            @wraps(func)
            def wrapper(*args, **kwargs):
                args, kwargs = _get_actual_args_kwargs(new_signature, args, kwargs)
                if is_generator:
                    yield from func(*args, **kwargs)
                else:
                    yield func(*args, **kwargs)

        wrapper.__signature__ = new_signature
        fixture = pytest.fixture(*pytest_fixture_args, **pytest_fixture_kwargs)(wrapper)
        fixture_name = pytest_fixture_kwargs.get("name", fixture.__name__)

        def parametrizer(*args, **kwargs):
            return pytest.mark.parametrize(fixture_name, [_ArgsKwargs(args, kwargs)], indirect=True)

        fixture.arguments = parametrizer

        return fixture
    return decorator

I made a funny decorator that allows writing fixtures like this:

@fixture_taking_arguments
def dog(request, /, name, age=69):
    return f"{name} the dog aged {age}"

Here, to the left of / you have other fixtures, and to the right you have parameters that are supplied using:

@dog.arguments("Buddy", age=7)
def test_with_dog(dog):
    assert dog == "Buddy the dog aged 7"

This works the same way function arguments work. If you don’t supply the age argument, the default one, 69, is used instead. if you don’t supply name, or omit the dog.arguments decorator, you get the regular TypeError: dog() missing 1 required positional argument: 'name'. If you have another fixture that takes argument name, it doesn’t conflict with this one.

Async fixtures are also supported.

Additionally, this gives you a nice setup plan:

$ pytest test_dogs_and_owners.py --setup-plan

SETUP    F dog['Buddy', age=7]
...
SETUP    F dog['Champion']
SETUP    F owner (fixtures used: dog)['John Travolta']

A full example:

from plugin import fixture_taking_arguments

@fixture_taking_arguments
def dog(request, /, name, age=69):
    return f"{name} the dog aged {age}"


@fixture_taking_arguments
def owner(request, dog, /, name="John Doe"):
    yield f"{name}, owner of {dog}"


@dog.arguments("Buddy", age=7)
def test_with_dog(dog):
    assert dog == "Buddy the dog aged 7"


@dog.arguments("Champion")
class TestChampion:
    def test_with_dog(self, dog):
        assert dog == "Champion the dog aged 69"

    def test_with_default_owner(self, owner, dog):
        assert owner == "John Doe, owner of Champion the dog aged 69"
        assert dog == "Champion the dog aged 69"

    @owner.arguments("John Travolta")
    def test_with_named_owner(self, owner):
        assert owner == "John Travolta, owner of Champion the dog aged 69"

The code for the decorator:

import pytest
from dataclasses import dataclass
from functools import wraps
from inspect import signature, Parameter, isgeneratorfunction, iscoroutinefunction, isasyncgenfunction
from itertools import zip_longest, chain


_NOTHING = object()


def _omittable_parentheses_decorator(decorator):
    @wraps(decorator)
    def wrapper(*args, **kwargs):
        if not kwargs and len(args) == 1 and callable(args[0]):
            return decorator()(args[0])
        else:
            return decorator(*args, **kwargs)
    return wrapper


@dataclass
class _ArgsKwargs:
    args: ...
    kwargs: ...

    def __repr__(self):
        return ", ".join(chain(
               (repr(v) for v in self.args), 
               (f"{k}={v!r}" for k, v in self.kwargs.items())))


def _flatten_arguments(sig, args, kwargs):
    assert len(sig.parameters) == len(args) + len(kwargs)
    for name, arg in zip_longest(sig.parameters, args, fillvalue=_NOTHING):
        yield arg if arg is not _NOTHING else kwargs[name]


def _get_actual_args_kwargs(sig, args, kwargs):
    request = kwargs["request"]
    try:
        request_args, request_kwargs = request.param.args, request.param.kwargs
    except AttributeError:
        request_args, request_kwargs = (), {}
    return tuple(_flatten_arguments(sig, args, kwargs)) + request_args, request_kwargs


@_omittable_parentheses_decorator
def fixture_taking_arguments(*pytest_fixture_args, **pytest_fixture_kwargs):
    def decorator(func):
        original_signature = signature(func)

        def new_parameters():
            for param in original_signature.parameters.values():
                if param.kind == Parameter.POSITIONAL_ONLY:
                    yield param.replace(kind=Parameter.POSITIONAL_OR_KEYWORD)

        new_signature = original_signature.replace(parameters=list(new_parameters()))

        if "request" not in new_signature.parameters:
            raise AttributeError("Target function must have positional-only argument `request`")

        is_async_generator = isasyncgenfunction(func)
        is_async = is_async_generator or iscoroutinefunction(func)
        is_generator = isgeneratorfunction(func)

        if is_async:
            @wraps(func)
            async def wrapper(*args, **kwargs):
                args, kwargs = _get_actual_args_kwargs(new_signature, args, kwargs)
                if is_async_generator:
                    async for result in func(*args, **kwargs):
                        yield result
                else:
                    yield await func(*args, **kwargs)
        else:
            @wraps(func)
            def wrapper(*args, **kwargs):
                args, kwargs = _get_actual_args_kwargs(new_signature, args, kwargs)
                if is_generator:
                    yield from func(*args, **kwargs)
                else:
                    yield func(*args, **kwargs)

        wrapper.__signature__ = new_signature
        fixture = pytest.fixture(*pytest_fixture_args, **pytest_fixture_kwargs)(wrapper)
        fixture_name = pytest_fixture_kwargs.get("name", fixture.__name__)

        def parametrizer(*args, **kwargs):
            return pytest.mark.parametrize(fixture_name, [_ArgsKwargs(args, kwargs)], indirect=True)

        fixture.arguments = parametrizer

        return fixture
    return decorator