LINQと同じ処理をPythonのリストで行う

最近、本腰を入れてPythonを基礎から勉強し直しています。 そうした中で、「LINQならああやって書くアレは、Pythonではどうやって書くのだろう?」と調べることが増えてきましたので、一旦整理しておきたいと思います。

今回はリストです。 辞書や集合などもまとめるかもです。

リストの生成(Range)

Enumerable.Rangeメソッドに対応する関数として、Pythonではrangeがあります。

# var numbers = Enumerable.Range(1, 5);
numbers = [number for number in range(1, 6)] # [1, 2, 3, 4, 5]

SelectとWhere

forの前で値を取得・変形することでSelectメソッド、if句で絞り込みでWhereメソッドと、それぞれ同じことができます。

組み合わせることで、Whereで絞った要素をSelectで取得するというのも簡単にできます。

# Select
# var squareNumbers = numbers.Select(i => i*i);
square_numbers = [i**2 for i in numbers] # [1, 4, 9, 16, 25]

# Where
# var oddNumbers = numbers.Where(i => i % 2 == 1);
odd_numbers = [i for i in numbers if i % 2 == 1] # [1, 3, 5]

# 組み合わせる
# var oddSquareNumbers = numbers.Where(i => i % 2 == 1).Select(i => i*i);
odd_square_numbers = [i**2 for i in numbers if i % 2 == 1] # [1, 9, 25]

TakeとSkip

内包表記の場合、enumerate関数でリストのインデックスと値の両方を同時に取得できます。 インデックスをif句で絞り込むことで、TakeやSkipメソッドと同じことができます。

ただ、単に「先頭から何番目まで」「末尾から何番目まで」という絞り込みだけであれば、添字指定の方が簡略で読みやすいです。

# Take
# var takeNumbers = numbers.Take(2);
take_numbers = [number for index, number in enumerate(numbers) if index < 2] # [1, 2]
# または
# take_numbers = numbers[0:2]

# Skip
# var skipNumbers = numbers.Skip(2);
skip_numbers = [number for index, number in enumerate(numbers) if index >= 2]
# または
# skip_numbers = numbers[2:]

# 組み合わせる
# var medianNumbers = numbers.Skip(2).Take(1);
median_numbers = [number for index, number in enumerate([number for index, number in enumerate(numbers) if index >= 2])  if index < 1] # 3
# または
# median_numbers = numbers[2:3]

FirstとLast

ポイントはifで絞り込んだ後に添字を指定することです。

# First
# var firstEvenNumber = numbers.First(i => i % 2 == 0);
first_even_number = [i for i in numbers if i % 2 == 0][0] # 2

# Last
# var lastEvenNumber = numbers.Last(i => i % 2 == 0);
last_even_number = [i for i in numbers if i % 2 == 0][-1] # 4

判定(Any/All/Contains)

判定用の関数としてanyとallが提供されています。 inを使うことでリスト中に同じ値があるかどうかを判定できます。

# Any
# var anyEven = numbers.Any(i => i % 2 == 0);
any_even = any([i % 2 == 0 for i in numbers]) # True

# All
# var allEven = number.All(i => i % 2 == 0);
all_even = all([i % 2 == 0 for i in numbers]) # False

# Contains
# var containsThree = numbers.Contains(3);
contains_three = 3 in numbers # True

順序(OrderBy/OrderByDescending)

リストの場合、sorted関数でデフォルトでは昇順ソートされ、引数reverseにTrueを渡すと降順でソートされます。 また、sortedの引数keyにラムダ式でソート方法を任意に設定することもできます。

# OrderBy
# var orderedNumbers = numbers.OrderBy(i => i);
ordered_numbers = sorted(numbers) # [1, 2, 3, 4, 5]

# OrderByDescending
# var disorderedNumbers = numbers.OrderByDescending(i => i);
disordered_numbers = sorted(numbers, reverse=True) # [5, 4, 3, 2, 1]

集約(Sum/Average/Min/Max)

sum、min、maxの集約関数が用意されているのでそれらを使うことで同じことができます。 Averageメソッドに対応するものはありませんが、sumの値をlenで除算することで簡単に導出できます。

# Sum
# var sumNumber = numbers.Sum(i => i);
sum_number = sum(numbers) # 15

# Average
# var averageNumber = numbers.Average(i => i);
average_number = sum(numbers) / len(numbers) # 3.0

# Min
# var minNumber = numbers.Min(i => i);
min_number = min(numbers) # 1

# Max
# var maxNumber = numbers.Max(i => i);
max_number = max(numbers) # 5

Zip

zip関数が提供されており、これを使うことで2つのリストの値を同時に取得できます。

# Zip
# var addedNumbers = numbers.Zip(squareNumbers, (first, second) => first + second);
added_numbers = [first + second for first, second in zip(numbers, square_numbers)] # [2, 6, 12, 20, 30]

C# XmlSerializerの使い方

XMLでリクエスト/レスポンスするAPIへアクセスする機会がありましたので、XmlSerializerの使い方を備忘録にしておきます。

XMLをシリアライズ/デシリアライズする

まずはC#オブジェクト(ここではBook)とXML形式の文字列でシリアライズ/デシリアライズさせる方法です。

Bookクラス用のXmlSerializer(System.Xml.Serialization名前空間)を定義して、Serializeメソッドでシリアライズできます。

  • publicのフィールドまたはプロパティがXML要素になりますが、XmlRootAttribute、XmlElementAttributeで要素名を指定しています
    • これらの属性が無い場合は、フィールド名・プロパティ名がそのまま要素名になります
  • XMLの繰り返し構造を定義することもでき、その場合はXmlArrayAttributeで親要素名、XmlArrayItemAttributeで子要素名を指定できます
  • クラスは入れ子構造にできます(Book内にListを定義しています)

デシリアライズは、同様にBookクラス用のXmlSerializerを定義し、Deserializeメソッドでデシリアライズします。

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;

namespace ConsoleApplication
{
    [XmlRoot("book")]
    public class Book
    {
        [XmlElement("title")]
        public string Title { get; set; }

        [XmlElement("publised")]
        public DateTime Published { get; set; }

        [XmlArray("authors")]
        [XmlArrayItem("author")]
        public List<Author> Authors { get; set; }
    }

    public class Author
    {
        [XmlElement("authorName")]
        public string AuthorName { get; set; }
    }

    class Program
    {
        public static void Main(string[] args)
        {
            var book = new Book
            {
                Title =
                    "The Art of Readable Code: Simple and Practical Techniques for Writing Better Code (Theory in Practice)",
                Published = new DateTime(2011, 11, 3),
                Authors = new List<Author>
                {
                    new Author {AuthorName = "Dustin Boswell"},
                    new Author {AuthorName = "Trevor Foucher"}
                }
            };

            // シリアライズ
            var writer = new StringWriter(); // 出力先のWriterを定義
            var serializer = new XmlSerializer(typeof(Book)); // Bookクラスのシリアライザを定義
            serializer.Serialize(writer, book);

            var xml = writer.ToString(); 
            Console.WriteLine(xml);

            // デシリアライズ
            var deserializedBook = (Book)serializer.Deserialize(new StringReader(xml));

            Console.WriteLine($"Title: {deserializedBook.Title}");
            Console.WriteLine($"Published: {deserializedBook.Published}");
            foreach (var author in deserializedBook.Authors)
            {
                Console.WriteLine($"AuthorName: {author.AuthorName}");
            }
        }
    }
}

実行すると、以下のように出力されます。

  • ルート要素にデフォルトで名前空間(xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://ww w.w3.org/2001/XMLSchema")が設定されます
<?xml version="1.0" encoding="utf-16"?>
<book xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://ww
w.w3.org/2001/XMLSchema">
  <title>The Art of Readable Code: Simple and Practical Techniques for Writing B
etter Code (Theory in Practice)</title>
  <publised>2011-11-03T00:00:00</publised>
  <authors>
    <author>
      <authorName>Dustin Boswell</authorName>
    </author>
    <author>
      <authorName>Trevor Foucher</authorName>
    </author>
  </authors>
</book>
Title: The Art of Readable Code: Simple and Practical Techniques for Writing Bet
ter Code (Theory in Practice)
Published: 2011/11/03 0:00:00
AuthorName: Dustin Boswell
AuthorName: Trevor Foucher

大まかな使い方は以上で、以下はtipsです。

デフォルトの名前空間を削除する

Serializeメソッドでは、XmlSerializerNamespacesオブジェクトを引数にとることができ、任意のXML名前空間を定義できます。 先程のデフォルトの名前空間が不要の場合、空のXmlSerializerNamespacesを渡すことで削除できます。

var namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
serializer.Serialize(writer, book, namespaces);

book要素から名前空間が削除されていることが確認できます。

<?xml version="1.0" encoding="utf-16"?>
<book>
  <title>The Art of Readable Code: Simple and Practical Techniques for Writing B
etter Code (Theory in Practice)</title>
・・・

encoding="utf-8"にする

出力のXML宣言を見ますと、encoding="utf-16"となっています。 StringWriterのEncodingプロパティの値がEncoding.UTF16になっているためですが、このプロパティはsetアクセサを持たないため、直接書き換えることができません。

そのため、StringWriterを継承したクラスを新たに作り、Encodingプロパティをオーバライドすることで、Encoding.UTF8に変えることができます。

private sealed class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding => Encoding.UTF8;
}

var writer = new Utf8StringWriter();
serializer.Serialize(writer, book, namespaces);

シリアライズされたXMLを見ると、encoding="utf-8"と出力されることが確認できます。

<?xml version="1.0" encoding="utf-8"?>
<book>
  <title>The Art of Readable Code: Simple and Practical Techniques for Writing B
etter Code (Theory in Practice)</title>
・・・

C# HttpClientでBasic認証する

今回はHttpClientでBasic認証を行います。

Basic認証するユーザ名とパスワードをコロン":“でつないでBase64形式にエンコーディングして、その値をAuthorizationヘッダに詰めればOKです。

using System;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

// Basic認証するユーザ名とパスワード
var userName = "ohke";
var userPassword = "ohkepassword";

// リクエストの生成
var request = new HttpRequestMessage
{
    Method = HttpMethod.Post,
    RequestUri = new Uri("http://ohke.hateblo.jp/")
};

// Basic認証ヘッダを付与する
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
    "Basic",
    Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", userName, userPassword))));

// リクエストの送信
using (var httpClient = new HttpClient())
{
    var response = await httpClient.SendAsync(request);
}

試しにこのブログにリクエストを飛ばして、wiresharkでリクエストパケットをキャプチャしてみますと、Authorizationに設定したユーザ名・パスワードが送られていることが確認できます。

f:id:ohke:20170520155308p:plain:w500